apollo-language-server
Version:
A language server for Apollo GraphQL projects
158 lines (141 loc) • 5.23 kB
text/typescript
// IntrospectionSchemaProvider (http => IntrospectionResult => schema)
import { NotificationHandler } from "vscode-languageserver";
import { execute as linkExecute, toPromise } from "apollo-link";
import { createHttpLink, HttpLink } from "apollo-link-http";
import {
GraphQLSchema,
buildClientSchema,
getIntrospectionQuery,
ExecutionResult,
IntrospectionQuery,
parse,
} from "graphql";
import { Agent as HTTPSAgent } from "https";
import { fetch } from "apollo-env";
import { RemoteServiceConfig, DefaultServiceConfig } from "../../config";
import { GraphQLSchemaProvider, SchemaChangeUnsubscribeHandler } from "./base";
import { Debug } from "../../utilities";
import { isString } from "util";
export class EndpointSchemaProvider implements GraphQLSchemaProvider {
private schema?: GraphQLSchema;
private federatedServiceSDL?: string;
constructor(private config: Exclude<RemoteServiceConfig, "name">) {}
async resolveSchema() {
if (this.schema) return this.schema;
const { skipSSLValidation, url, headers } = this.config;
const options: HttpLink.Options = {
uri: url,
fetch,
};
if (url.startsWith("https:") && skipSSLValidation) {
options.fetchOptions = {
agent: new HTTPSAgent({ rejectUnauthorized: false }),
};
}
const { data, errors } = (await toPromise(
linkExecute(createHttpLink(options), {
query: parse(getIntrospectionQuery()),
context: { headers },
})
).catch((e) => {
// html response from introspection
if (isString(e.message) && e.message.includes("token <")) {
throw new Error(
"Apollo tried to introspect a running GraphQL service at " +
url +
"\nIt expected a JSON schema introspection result, but got an HTML response instead." +
"\nYou may need to add headers to your request or adjust your endpoint url.\n" +
"-----------------------------\n" +
"For more information, please refer to: https://go.apollo.dev/t/config \n\n" +
"The following error occurred:\n-----------------------------\n" +
e.message
);
}
// 404 encountered with the default url
if (
url === DefaultServiceConfig.endpoint.url &&
isString(e.message) &&
e.message.includes("ECONNREFUSED")
) {
throw new Error(
"Failed to connect to a running GraphQL endpoint at " +
url +
"\nThis may be because you didn't start your service.\n" +
"By default, when an endpoint, Apollo API key, or localSchemaFile isn't provided, Apollo tries to fetch a schema from " +
DefaultServiceConfig.endpoint.url +
"\n-----------------------------\n" +
"\nFor more information, please refer to: https://go.apollo.dev/t/config \n\n" +
"The following error occurred: \n" +
"-----------------------------\n" +
e.message
);
}
// 404 with a non-default url
if (isString(e.message) && e.message.includes("ECONNREFUSED")) {
throw new Error(
"Failed to connect to a running GraphQL endpoint at " +
url +
"\nThis may be because you didn't start your service or the endpoint URL is incorrect."
);
}
throw new Error(e);
})) as ExecutionResult<IntrospectionQuery>;
if (errors && errors.length) {
// XXX better error handling of GraphQL errors
throw new Error(errors.map(({ message }: Error) => message).join("\n"));
}
if (!data) {
throw new Error("No data received from server introspection.");
}
this.schema = buildClientSchema(data);
return this.schema;
}
onSchemaChange(
_handler: NotificationHandler<GraphQLSchema>
): SchemaChangeUnsubscribeHandler {
throw new Error("Polling of endpoint not implemented yet");
return () => {};
}
async resolveFederatedServiceSDL() {
if (this.federatedServiceSDL) return this.federatedServiceSDL;
const { skipSSLValidation, url, headers } = this.config;
const options: HttpLink.Options = {
uri: url,
fetch,
};
if (url.startsWith("https:") && skipSSLValidation) {
options.fetchOptions = {
agent: new HTTPSAgent({ rejectUnauthorized: false }),
};
}
const getFederationInfoQuery = `
query getFederationInfo {
_service {
sdl
}
}
`;
const { data, errors } = (await toPromise(
linkExecute(createHttpLink(options), {
query: parse(getFederationInfoQuery),
context: { headers },
})
)) as ExecutionResult<{ _service: { sdl: string } }>;
if (errors && errors.length) {
return Debug.error(
errors.map(({ message }: Error) => message).join("\n")
);
}
if (!data || !data._service) {
return Debug.error(
"No data received from server when querying for _service."
);
}
this.federatedServiceSDL = data._service.sdl;
return data._service.sdl;
}
// public async isFederatedSchema() {
// const schema = this.schema || (await this.resolveSchema());
// return false;
// }
}