@@ -9,13 +9,28 @@ import { sqlmeshLspExec } from '../utilities/sqlmesh/sqlmesh'
99import { err , isErr , ok , Result } from '@bus/result'
1010import { getWorkspaceFolders } from '../utilities/common/vscodeapi'
1111import { traceError , traceInfo } from '../utilities/common/log'
12- import { ErrorType } from '../utilities/errors'
12+ import {
13+ ErrorType ,
14+ ErrorTypeGeneric ,
15+ ErrorTypeInvalidState ,
16+ ErrorTypeSQLMeshOutdated ,
17+ } from '../utilities/errors'
1318import { CustomLSPMethods } from './custom'
1419
20+ type SupportedMethodsState =
21+ | { type : 'not-fetched' }
22+ | { type : 'fetched' ; methods : Set < string > }
23+ | { type : 'endpoint-not-supported' }
24+
1525let outputChannel : OutputChannel | undefined
1626
1727export class LSPClient implements Disposable {
1828 private client : LanguageClient | undefined
29+ /**
30+ * State to track whether the supported methods have been fetched. These are used to determine if a method is supported
31+ * by the LSP server and return an error if not.
32+ */
33+ private supportedMethodsState : SupportedMethodsState = { type : 'not-fetched' }
1934
2035 constructor ( ) {
2136 this . client = undefined
@@ -98,21 +113,123 @@ export class LSPClient implements Disposable {
98113 if ( this . client ) {
99114 await this . client . stop ( )
100115 this . client = undefined
116+ // Reset supported methods state when the client stops
117+ this . supportedMethodsState = { type : 'not-fetched' }
101118 }
102119 }
103120
104121 public async dispose ( ) {
105122 await this . stop ( )
106123 }
107124
125+ private async fetchSupportedMethods ( ) : Promise < void > {
126+ if ( ! this . client || this . supportedMethodsState . type !== 'not-fetched' ) {
127+ return
128+ }
129+ try {
130+ const result = await this . internal_call_custom_method (
131+ 'sqlmesh/supported_methods' ,
132+ { } ,
133+ )
134+ if ( isErr ( result ) ) {
135+ traceError ( `Failed to fetch supported methods: ${ result . error } ` )
136+ this . supportedMethodsState = { type : 'endpoint-not-supported' }
137+ return
138+ }
139+ const methodNames = new Set ( result . value . methods . map ( m => m . name ) )
140+ this . supportedMethodsState = { type : 'fetched' , methods : methodNames }
141+ traceInfo (
142+ `Fetched supported methods: ${ Array . from ( methodNames ) . join ( ', ' ) } ` ,
143+ )
144+ } catch {
145+ // If the supported_methods endpoint doesn't exist, mark it as not supported
146+ this . supportedMethodsState = { type : 'endpoint-not-supported' }
147+ traceInfo (
148+ 'Supported methods endpoint not available, proceeding without validation' ,
149+ )
150+ }
151+ }
152+
108153 public async call_custom_method <
154+ Method extends Exclude <
155+ CustomLSPMethods [ 'method' ] ,
156+ 'sqlmesh/supported_methods'
157+ > ,
158+ Request extends Extract < CustomLSPMethods , { method : Method } > [ 'request' ] ,
159+ Response extends Extract < CustomLSPMethods , { method : Method } > [ 'response' ] ,
160+ > (
161+ method : Method ,
162+ request : Request ,
163+ ) : Promise <
164+ Result <
165+ Response ,
166+ ErrorTypeGeneric | ErrorTypeInvalidState | ErrorTypeSQLMeshOutdated
167+ >
168+ > {
169+ if ( ! this . client ) {
170+ return err ( {
171+ type : 'generic' ,
172+ message : 'LSP client not ready.' ,
173+ } )
174+ }
175+ await this . fetchSupportedMethods ( )
176+
177+ const supportedState = this . supportedMethodsState
178+ switch ( supportedState . type ) {
179+ case 'not-fetched' :
180+ return err ( {
181+ type : 'invalid_state' ,
182+ message : 'Supported methods not fetched yet where as they should.' ,
183+ } )
184+ case 'fetched' : {
185+ // If we have fetched the supported methods, we can check if the method is supported
186+ if ( ! supportedState . methods . has ( method ) ) {
187+ return err ( {
188+ type : 'sqlmesh_outdated' ,
189+ message : `Method '${ method } ' is not supported by this LSP server.` ,
190+ } )
191+ }
192+ const response = await this . internal_call_custom_method (
193+ method ,
194+ request as any ,
195+ )
196+ if ( isErr ( response ) ) {
197+ return err ( {
198+ type : 'generic' ,
199+ message : response . error ,
200+ } )
201+ }
202+ return ok ( response . value as Response )
203+ }
204+ case 'endpoint-not-supported' : {
205+ const response = await this . internal_call_custom_method (
206+ method ,
207+ request as any ,
208+ )
209+ if ( isErr ( response ) ) {
210+ return err ( {
211+ type : 'generic' ,
212+ message : response . error ,
213+ } )
214+ }
215+ return ok ( response . value as Response )
216+ }
217+ }
218+ }
219+
220+ /**
221+ * Internal method to call a custom LSP method without checking if the method is supported. It is used for
222+ * the class where as the `call_custom_method` checks if the method is supported.
223+ */
224+ public async internal_call_custom_method <
109225 Method extends CustomLSPMethods [ 'method' ] ,
110226 Request extends Extract < CustomLSPMethods , { method : Method } > [ 'request' ] ,
111227 Response extends Extract < CustomLSPMethods , { method : Method } > [ 'response' ] ,
112228 > ( method : Method , request : Request ) : Promise < Result < Response , string > > {
113229 if ( ! this . client ) {
114230 return err ( 'lsp client not ready' )
115231 }
232+
116233 try {
117234 const result = await this . client . sendRequest < Response > ( method , request )
118235 return ok ( result )
0 commit comments