-
Notifications
You must be signed in to change notification settings - Fork 151
/
federation-introspection.ts
122 lines (104 loc) · 4.02 KB
/
federation-introspection.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
import { DocumentNode, GraphQLSchema } from 'graphql';
import { AxiosError, AxiosRequestConfig, AxiosResponse } from 'axios';
import { GraphQLApi, GraphQLFederationIntrospection, GraphQLIntrospection } from './index';
import { loadFile } from '../codegen/templates/typescript';
import { resolveVariable } from '../configure/variables';
import { parse } from 'graphql/index';
import { mergeApis } from './merge';
import { introspectWithCache } from './introspection-cache';
import { introspectGraphql, resolveGraphqlIntrospectionHeaders } from './graphql-introspection';
import { HeadersBuilder, mapHeaders } from './headers-builder';
import { Fetcher } from './introspection-fetcher';
import { Logger } from '../logger';
export const isFederationService = (schema: GraphQLSchema): boolean => {
const queryType = schema.getQueryType();
if (queryType === undefined || queryType === null) {
return false;
}
const fields = queryType.getFields();
if (fields === undefined) {
return false;
}
return Object.keys(fields).indexOf('_service') !== -1;
};
export const fetchFederationServiceSDL = async (
url: string,
headers?: Record<string, string>,
meta?: { apiNamespace?: string; upstreamName?: string }
): Promise<string> => {
const data = JSON.stringify({
query: '{_service{sdl}}',
});
let opts: AxiosRequestConfig = {
headers,
'axios-retry': {
onRetry: (retryCount: number, error: AxiosError, requestConfig: AxiosRequestConfig) => {
let msg = `failed to fetch federation service sdl: ${requestConfig.method} url: ${requestConfig.url}`;
if (meta?.apiNamespace) {
msg += ` apiNamespace: ${meta.apiNamespace}`;
}
if (meta?.upstreamName) {
msg += ` upstreamName: ${meta.upstreamName}`;
}
msg += ` retryAttempt: ${retryCount}`;
Logger.info(msg);
},
},
};
let res: AxiosResponse | undefined;
try {
res = await Fetcher().post(url, data, opts);
} catch (e: any) {
throw new Error(`failed to fetch federation service sdl (url: ${url}), error: ${e.message}`);
}
if (res === undefined) {
throw new Error(`failed to fetch federation service sdl (url: ${url}), no response`);
}
if (res.status !== 200) {
throw new Error(
`failed to fetch federation service sdl (url: ${url}), response code: ${res.status}, message: ${res.statusText}`
);
}
return res.data.data._service.sdl;
};
export interface ServiceDefinition {
typeDefs: DocumentNode;
name: string;
}
export const introspectFederation = async (introspection: GraphQLFederationIntrospection): Promise<GraphQLApi> =>
introspectWithCache(introspection, async (introspection: GraphQLFederationIntrospection): Promise<GraphQLApi> => {
const upstreams = introspection.upstreams.map(async (upstream, i) => {
let schema = upstream.loadSchemaFromString ? loadFile(upstream.loadSchemaFromString) : '';
const name = upstream.name ?? i.toString();
if (schema === '' && upstream.url) {
const introspectionHeadersBuilder = new HeadersBuilder();
if (upstream.headers !== undefined) {
upstream.headers(introspectionHeadersBuilder);
}
if (upstream.introspection?.headers !== undefined) {
upstream.introspection?.headers(introspectionHeadersBuilder);
}
const introspectionHeaders = resolveGraphqlIntrospectionHeaders(mapHeaders(introspectionHeadersBuilder));
// upstream.url is truthy at this point, no need to check
schema = await fetchFederationServiceSDL(resolveVariable(upstream.url), introspectionHeaders, {
apiNamespace: introspection.apiNamespace,
upstreamName: name,
});
}
if (schema == '') {
throw new Error(`Subgraph ${name} has not provided a schema`);
}
return {
name,
typeDefs: parse(schema),
};
});
const graphQLIntrospections: GraphQLIntrospection[] = introspection.upstreams.map((upstream) => ({
...upstream,
isFederation: true,
apiNamespace: introspection.apiNamespace,
id: upstream.id ?? introspection.id,
}));
const apis = await Promise.all(graphQLIntrospections.map((i) => introspectGraphql(i)));
return mergeApis([], ...apis) as GraphQLApi;
});