UNPKG

apollo-language-server

Version:

A language server for Apollo GraphQL projects

161 lines (137 loc) 5.06 kB
// FileSchemaProvider (FileProvider (SDL || IntrospectionResult) => schema) import { GraphQLSchema, buildClientSchema, Source, printSchema, parse, } from "graphql"; import { readFileSync } from "fs"; import { extname, resolve } from "path"; import { GraphQLSchemaProvider, SchemaChangeUnsubscribeHandler } from "./base"; import { NotificationHandler } from "vscode-languageserver"; import { Debug } from "../../utilities"; import { buildSchemaFromSDL } from "apollo-graphql"; import { buildFederatedSchema } from "@apollo/federation"; import URI from "vscode-uri"; export interface FileSchemaProviderConfig { path?: string; paths?: string[]; } // XXX file subscription export class FileSchemaProvider implements GraphQLSchemaProvider { private schema?: GraphQLSchema; private federatedServiceSDL?: string; constructor(private config: FileSchemaProviderConfig) {} async resolveSchema() { if (this.schema) return this.schema; const { path, paths } = this.config; // load each path and get sdl string from each, if a list, concatenate them all const documents = path ? [this.loadFileAndGetDocument(path)] : paths ? paths.map(this.loadFileAndGetDocument, this) : undefined; if (!documents) throw new Error( `Schema could not be loaded for [${ path ? path : paths ? paths.join(", ") : "undefined" }]` ); this.schema = buildSchemaFromSDL(documents); if (!this.schema) throw new Error(`Schema could not be loaded for ${path}`); return this.schema; } // load a graphql file or introspection result and return the GraphQL DocumentNode // this is the mechanism for loading a single file's DocumentNode loadFileAndGetDocument(path: string) { let result; try { result = readFileSync(path, { encoding: "utf-8", }); } catch (err) { throw new Error(`Unable to read file ${path}. ${err.message}`); } const ext = extname(path); // an actual introspection query result, convert to DocumentNode if (ext === ".json") { const parsed = JSON.parse(result); const __schema = parsed.data ? parsed.data.__schema : parsed.__schema ? parsed.__schema : parsed; const schema = buildClientSchema({ __schema }); return parse(printSchema(schema)); } else if (ext === ".graphql" || ext === ".graphqls" || ext === ".gql") { const uri = URI.file(resolve(path)).toString(); return parse(new Source(result, uri)); } throw new Error( "File Type not supported for schema loading. Must be a .json, .graphql, .gql, or .graphqls file" ); } onSchemaChange( _handler: NotificationHandler<GraphQLSchema> ): SchemaChangeUnsubscribeHandler { throw new Error("File watching not implemented yet"); return () => {}; } // Load SDL from files. This is only used with federated services, // since they need full SDL and not the printout of GraphQLSchema async resolveFederatedServiceSDL() { if (this.federatedServiceSDL) return this.federatedServiceSDL; const { path, paths } = this.config; // load each path and get sdl string from each, if a list, concatenate them all const SDLs = path ? [this.loadFileAndGetSDL(path)] : paths ? paths.map(this.loadFileAndGetSDL, this) : undefined; if (!SDLs || SDLs.filter((s) => !Boolean(s)).length > 0) return Debug.error( `SDL could not be loaded for one of more files: [${ path ? path : paths ? paths.join(", ") : "undefined" }]` ); const federatedSchema = buildFederatedSchema( SDLs.map((sdl) => ({ typeDefs: parse(sdl as string) })) ); // call the `Query._service` resolver to get the actual printed sdl const queryType = federatedSchema.getQueryType(); if (!queryType) return Debug.error("No query type found for federated schema"); const serviceField = queryType.getFields()["_service"]; const serviceResults = serviceField && serviceField.resolve && serviceField.resolve(null, {}, null, {} as any); if (!serviceResults || !serviceResults.sdl) return Debug.error( "No SDL resolver or result from federated schema after building" ); this.federatedServiceSDL = serviceResults.sdl; return this.federatedServiceSDL; } // this is the mechanism for loading a single file's SDL loadFileAndGetSDL(path: string) { let result; try { result = readFileSync(path, { encoding: "utf-8", }); } catch (err) { return Debug.error(`Unable to read file ${path}. ${err.message}`); } const ext = extname(path); // this file should already be in sdl format if (ext === ".graphql" || ext === ".graphqls" || ext === ".gql") { return result as string; } else { return Debug.error( "When using localSchemaFile to check or push a federated service, you can only use .graphql, .gql, and .graphqls files" ); } } }