@ankhorage/data-sources 1.0.1 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/README.md +6 -41
- package/dist/ankh.provider.js +1 -1
- package/dist/ankh.provider.js.map +1 -1
- package/dist/discovery/http.d.ts +10 -0
- package/dist/discovery/http.d.ts.map +1 -0
- package/dist/discovery/http.js +35 -0
- package/dist/discovery/http.js.map +1 -0
- package/dist/discovery/index.d.ts +9 -9
- package/dist/discovery/index.d.ts.map +1 -1
- package/dist/discovery/index.js +27 -55
- package/dist/discovery/index.js.map +1 -1
- package/dist/graphql/index.d.ts +8 -76
- package/dist/graphql/index.d.ts.map +1 -1
- package/dist/graphql/index.js +17 -197
- package/dist/graphql/index.js.map +1 -1
- package/dist/graphql/introspection.d.ts +5 -0
- package/dist/graphql/introspection.d.ts.map +1 -0
- package/dist/graphql/introspection.js +136 -0
- package/dist/graphql/introspection.js.map +1 -0
- package/dist/graphql/operation.d.ts +5 -0
- package/dist/graphql/operation.d.ts.map +1 -0
- package/dist/graphql/operation.js +39 -0
- package/dist/graphql/operation.js.map +1 -0
- package/dist/graphql/types.d.ts +71 -0
- package/dist/graphql/types.d.ts.map +1 -0
- package/dist/graphql/types.js +2 -0
- package/dist/graphql/types.js.map +1 -0
- package/dist/index.d.ts +1 -17
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -34
- package/dist/index.js.map +1 -1
- package/dist/openapi/endpoints.d.ts +5 -0
- package/dist/openapi/endpoints.d.ts.map +1 -0
- package/dist/openapi/endpoints.js +77 -0
- package/dist/openapi/endpoints.js.map +1 -0
- package/dist/openapi/index.d.ts +7 -87
- package/dist/openapi/index.d.ts.map +1 -1
- package/dist/openapi/index.js +8 -267
- package/dist/openapi/index.js.map +1 -1
- package/dist/openapi/operation.d.ts +5 -0
- package/dist/openapi/operation.d.ts.map +1 -0
- package/dist/openapi/operation.js +141 -0
- package/dist/openapi/operation.js.map +1 -0
- package/dist/openapi/schema.d.ts +5 -0
- package/dist/openapi/schema.d.ts.map +1 -0
- package/dist/openapi/schema.js +84 -0
- package/dist/openapi/schema.js.map +1 -0
- package/dist/openapi/types.d.ts +82 -0
- package/dist/openapi/types.d.ts.map +1 -0
- package/dist/openapi/types.js +2 -0
- package/dist/openapi/types.js.map +1 -0
- package/dist/packageInfo.d.ts +18 -0
- package/dist/packageInfo.d.ts.map +1 -0
- package/dist/packageInfo.js +30 -0
- package/dist/packageInfo.js.map +1 -0
- package/dist/rest/index.d.ts +5 -5
- package/dist/rest/index.d.ts.map +1 -1
- package/dist/rest/index.js +49 -48
- package/dist/rest/index.js.map +1 -1
- package/dist/test-runner/index.d.ts +2 -57
- package/dist/test-runner/index.d.ts.map +1 -1
- package/dist/test-runner/index.js +58 -160
- package/dist/test-runner/index.js.map +1 -1
- package/dist/test-runner/response.d.ts +3 -0
- package/dist/test-runner/response.d.ts.map +1 -0
- package/dist/test-runner/response.js +29 -0
- package/dist/test-runner/response.js.map +1 -0
- package/dist/test-runner/result.d.ts +6 -0
- package/dist/test-runner/result.d.ts.map +1 -0
- package/dist/test-runner/result.js +17 -0
- package/dist/test-runner/result.js.map +1 -0
- package/dist/test-runner/serialization.d.ts +12 -0
- package/dist/test-runner/serialization.d.ts.map +1 -0
- package/dist/test-runner/serialization.js +99 -0
- package/dist/test-runner/serialization.js.map +1 -0
- package/dist/test-runner/types.d.ts +58 -0
- package/dist/test-runner/types.d.ts.map +1 -0
- package/dist/test-runner/types.js +2 -0
- package/dist/test-runner/types.js.map +1 -0
- package/package.json +5 -5
- package/src/ankh.provider.ts +1 -1
- package/src/api-phase1.contract.test.ts +24 -0
- package/src/discovery/http.ts +38 -0
- package/src/discovery/index.test.ts +76 -75
- package/src/discovery/index.ts +49 -72
- package/src/graphql/index.test.ts +10 -11
- package/src/graphql/index.ts +35 -315
- package/src/graphql/introspection.ts +163 -0
- package/src/graphql/operation.ts +60 -0
- package/src/graphql/types.ts +79 -0
- package/src/index.test.ts +21 -13
- package/src/index.ts +1 -54
- package/src/openapi/endpoints.ts +124 -0
- package/src/openapi/index.test.ts +47 -47
- package/src/openapi/index.ts +12 -467
- package/src/openapi/operation.ts +231 -0
- package/src/openapi/schema.ts +127 -0
- package/src/openapi/types.ts +93 -0
- package/src/packageInfo.ts +50 -0
- package/src/rest/index.test.ts +17 -14
- package/src/rest/index.ts +67 -61
- package/src/test-runner/index.test.ts +153 -120
- package/src/test-runner/index.ts +97 -299
- package/src/test-runner/response.ts +32 -0
- package/src/test-runner/result.ts +35 -0
- package/src/test-runner/serialization.ts +151 -0
- package/src/test-runner/types.ts +81 -0
- package/dist/generated-api/index.d.ts +0 -10
- package/dist/generated-api/index.d.ts.map +0 -1
- package/dist/generated-api/index.js +0 -274
- package/dist/generated-api/index.js.map +0 -1
- package/dist/generated-api/validateGeneratedApiCollection.d.ts +0 -3
- package/dist/generated-api/validateGeneratedApiCollection.d.ts.map +0 -1
- package/dist/generated-api/validateGeneratedApiCollection.js +0 -35
- package/dist/generated-api/validateGeneratedApiCollection.js.map +0 -1
- package/src/generated-api/index.test.ts +0 -173
- package/src/generated-api/index.ts +0 -414
- package/src/generated-api/validateGeneratedApiCollection.test.ts +0 -91
- package/src/generated-api/validateGeneratedApiCollection.ts +0 -76
package/src/test-runner/index.ts
CHANGED
|
@@ -1,87 +1,31 @@
|
|
|
1
1
|
import type {
|
|
2
|
-
|
|
3
|
-
DataContractValue,
|
|
2
|
+
ApiDefinition,
|
|
4
3
|
DataEndpointConfig,
|
|
5
4
|
DataOperationConfig,
|
|
6
|
-
DataOperationParameter,
|
|
7
|
-
DataSourceConfig,
|
|
8
5
|
DataSourceDiagnostic,
|
|
9
|
-
EndpointId,
|
|
10
|
-
OperationId,
|
|
11
6
|
} from '@ankhorage/contracts/data';
|
|
12
7
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
readonly status: number;
|
|
33
|
-
readonly headers?: EndpointTestHeaders;
|
|
34
|
-
text(): Promise<string>;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export type EndpointTestFetch = (
|
|
38
|
-
url: string,
|
|
39
|
-
init: EndpointTestFetchInit,
|
|
40
|
-
) => Promise<EndpointTestFetchResponse>;
|
|
41
|
-
|
|
42
|
-
export interface EndpointTestInput {
|
|
43
|
-
readonly dataSource: DataSourceConfig;
|
|
44
|
-
readonly endpointId: EndpointId;
|
|
45
|
-
readonly operationId: OperationId;
|
|
46
|
-
readonly values?: EndpointTestInputValues;
|
|
47
|
-
readonly dryRun?: boolean;
|
|
48
|
-
readonly fetch?: EndpointTestFetch;
|
|
49
|
-
readonly credentialResolver?: EndpointTestCredentialResolver;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export interface EndpointTestRequestDiagnostic {
|
|
53
|
-
readonly dataSourceId: string;
|
|
54
|
-
readonly endpointId: EndpointId;
|
|
55
|
-
readonly operationId: OperationId;
|
|
56
|
-
readonly url: string;
|
|
57
|
-
readonly method: string;
|
|
58
|
-
readonly headers: EndpointTestHeaders;
|
|
59
|
-
readonly body?: string;
|
|
60
|
-
readonly dryRun: boolean;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
export interface EndpointTestResponseDiagnostic {
|
|
64
|
-
readonly status: number;
|
|
65
|
-
readonly ok: boolean;
|
|
66
|
-
readonly headers?: EndpointTestHeaders;
|
|
67
|
-
readonly bodyText?: string;
|
|
68
|
-
readonly parsedBody?: DataContractValue;
|
|
69
|
-
}
|
|
8
|
+
import { createResponseDiagnostic } from './response';
|
|
9
|
+
import { createDiagnostic, createNetworkErrorMessage, failure } from './result';
|
|
10
|
+
import {
|
|
11
|
+
appendQuery,
|
|
12
|
+
collectHeaders,
|
|
13
|
+
collectQuery,
|
|
14
|
+
createHttpBody,
|
|
15
|
+
getRecordValue,
|
|
16
|
+
getStringMetadataValue,
|
|
17
|
+
getStringValue,
|
|
18
|
+
interpolatePath,
|
|
19
|
+
joinUrl,
|
|
20
|
+
} from './serialization';
|
|
21
|
+
import type {
|
|
22
|
+
EndpointTestCredential,
|
|
23
|
+
EndpointTestInput,
|
|
24
|
+
EndpointTestRequestDiagnostic,
|
|
25
|
+
EndpointTestResult,
|
|
26
|
+
} from './types';
|
|
70
27
|
|
|
71
|
-
export type
|
|
72
|
-
| {
|
|
73
|
-
readonly ok: true;
|
|
74
|
-
readonly request: EndpointTestRequestDiagnostic;
|
|
75
|
-
readonly response?: EndpointTestResponseDiagnostic;
|
|
76
|
-
readonly data?: DataContractValue;
|
|
77
|
-
readonly diagnostics: readonly DataSourceDiagnostic[];
|
|
78
|
-
}
|
|
79
|
-
| {
|
|
80
|
-
readonly ok: false;
|
|
81
|
-
readonly request?: EndpointTestRequestDiagnostic;
|
|
82
|
-
readonly response?: EndpointTestResponseDiagnostic;
|
|
83
|
-
readonly diagnostics: readonly DataSourceDiagnostic[];
|
|
84
|
-
};
|
|
28
|
+
export type * from './types';
|
|
85
29
|
|
|
86
30
|
interface EndpointOperationSelection {
|
|
87
31
|
readonly endpoint: DataEndpointConfig;
|
|
@@ -109,26 +53,7 @@ export async function testEndpoint(input: EndpointTestInput): Promise<EndpointTe
|
|
|
109
53
|
headers: built.request.headers,
|
|
110
54
|
body: built.request.body,
|
|
111
55
|
});
|
|
112
|
-
|
|
113
|
-
const diagnostics = [...built.diagnostics];
|
|
114
|
-
if (!responseDiagnostic.ok) {
|
|
115
|
-
diagnostics.push(
|
|
116
|
-
createDiagnostic(
|
|
117
|
-
input,
|
|
118
|
-
'http-error',
|
|
119
|
-
`Endpoint test request returned HTTP status ${responseDiagnostic.status}.`,
|
|
120
|
-
),
|
|
121
|
-
);
|
|
122
|
-
}
|
|
123
|
-
return responseDiagnostic.ok
|
|
124
|
-
? {
|
|
125
|
-
ok: true,
|
|
126
|
-
request: built.request,
|
|
127
|
-
response: responseDiagnostic,
|
|
128
|
-
data: responseDiagnostic.parsedBody,
|
|
129
|
-
diagnostics,
|
|
130
|
-
}
|
|
131
|
-
: failure(built.request, diagnostics, responseDiagnostic);
|
|
56
|
+
return createEndpointTestResult(input, built, await createResponseDiagnostic(response));
|
|
132
57
|
} catch (error) {
|
|
133
58
|
return failure(built.request, [
|
|
134
59
|
...built.diagnostics,
|
|
@@ -140,38 +65,64 @@ export async function testEndpoint(input: EndpointTestInput): Promise<EndpointTe
|
|
|
140
65
|
export async function buildEndpointTestRequest(
|
|
141
66
|
input: EndpointTestInput,
|
|
142
67
|
): Promise<EndpointTestResult> {
|
|
68
|
+
if (input.api.origin === 'internal') {
|
|
69
|
+
return failure(undefined, [
|
|
70
|
+
createDiagnostic(input, 'invalid-config', 'Internal APIs are not executable in API phase 1.'),
|
|
71
|
+
]);
|
|
72
|
+
}
|
|
73
|
+
|
|
143
74
|
const selection = selectEndpointOperation(input);
|
|
144
75
|
if (selection === undefined) {
|
|
145
76
|
return failure(undefined, [
|
|
146
77
|
createDiagnostic(input, 'missing-operation', 'Endpoint or operation could not be found.'),
|
|
147
78
|
]);
|
|
148
79
|
}
|
|
149
|
-
|
|
150
|
-
if (selection.operation.protocol === 'database') {
|
|
80
|
+
if (selection.operation.protocol !== 'http' && selection.operation.protocol !== 'graphql') {
|
|
151
81
|
return failure(undefined, [
|
|
152
82
|
createDiagnostic(
|
|
153
83
|
input,
|
|
154
84
|
'invalid-config',
|
|
155
|
-
|
|
85
|
+
`API operation protocol '${selection.operation.protocol}' is not supported.`,
|
|
156
86
|
),
|
|
157
87
|
]);
|
|
158
88
|
}
|
|
159
89
|
|
|
160
90
|
const diagnostics: DataSourceDiagnostic[] = [];
|
|
161
91
|
const credential = await resolveEndpointCredential(input, selection, diagnostics);
|
|
162
|
-
const request =
|
|
163
|
-
selection.operation.protocol === 'graphql'
|
|
164
|
-
? buildGraphQlRequest(input, selection, credential, diagnostics)
|
|
165
|
-
: buildHttpRequest(input, selection, credential, diagnostics);
|
|
166
|
-
|
|
92
|
+
const request = buildProtocolRequest(input, selection, credential, diagnostics);
|
|
167
93
|
if (request === undefined || diagnostics.some((diagnostic) => diagnostic.severity === 'error')) {
|
|
168
94
|
return failure(request, diagnostics);
|
|
169
95
|
}
|
|
170
96
|
return { ok: true, request, diagnostics };
|
|
171
97
|
}
|
|
172
98
|
|
|
99
|
+
function createEndpointTestResult(
|
|
100
|
+
input: EndpointTestInput,
|
|
101
|
+
built: Extract<EndpointTestResult, { readonly ok: true }>,
|
|
102
|
+
response: Awaited<ReturnType<typeof createResponseDiagnostic>>,
|
|
103
|
+
): EndpointTestResult {
|
|
104
|
+
const diagnostics = [...built.diagnostics];
|
|
105
|
+
if (!response.ok) {
|
|
106
|
+
diagnostics.push(
|
|
107
|
+
createDiagnostic(
|
|
108
|
+
input,
|
|
109
|
+
'http-error',
|
|
110
|
+
`Endpoint test request returned HTTP status ${response.status}.`,
|
|
111
|
+
),
|
|
112
|
+
);
|
|
113
|
+
return failure(built.request, diagnostics, response);
|
|
114
|
+
}
|
|
115
|
+
return {
|
|
116
|
+
ok: true,
|
|
117
|
+
request: built.request,
|
|
118
|
+
response,
|
|
119
|
+
data: response.parsedBody,
|
|
120
|
+
diagnostics,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
173
124
|
function selectEndpointOperation(input: EndpointTestInput): EndpointOperationSelection | undefined {
|
|
174
|
-
const endpoint = input.
|
|
125
|
+
const endpoint = input.api.endpoints[input.endpointId];
|
|
175
126
|
const operation = endpoint?.operations[input.operationId];
|
|
176
127
|
return endpoint === undefined || operation === undefined ? undefined : { endpoint, operation };
|
|
177
128
|
}
|
|
@@ -182,7 +133,7 @@ async function resolveEndpointCredential(
|
|
|
182
133
|
diagnostics: DataSourceDiagnostic[],
|
|
183
134
|
): Promise<EndpointTestCredential | undefined> {
|
|
184
135
|
const ref =
|
|
185
|
-
selection.operation.credential ?? selection.endpoint.credential ?? input.
|
|
136
|
+
selection.operation.credential ?? selection.endpoint.credential ?? input.api.credential;
|
|
186
137
|
if (ref === undefined) return undefined;
|
|
187
138
|
if (input.credentialResolver === undefined) {
|
|
188
139
|
diagnostics.push(
|
|
@@ -203,22 +154,31 @@ async function resolveEndpointCredential(
|
|
|
203
154
|
return credential;
|
|
204
155
|
}
|
|
205
156
|
|
|
157
|
+
function buildProtocolRequest(
|
|
158
|
+
input: EndpointTestInput,
|
|
159
|
+
selection: EndpointOperationSelection,
|
|
160
|
+
credential: EndpointTestCredential | undefined,
|
|
161
|
+
diagnostics: DataSourceDiagnostic[],
|
|
162
|
+
): EndpointTestRequestDiagnostic | undefined {
|
|
163
|
+
return selection.operation.protocol === 'graphql'
|
|
164
|
+
? buildGraphQlRequest(input, selection, credential, diagnostics)
|
|
165
|
+
: buildHttpRequest(input, selection, credential, diagnostics);
|
|
166
|
+
}
|
|
167
|
+
|
|
206
168
|
function buildHttpRequest(
|
|
207
169
|
input: EndpointTestInput,
|
|
208
170
|
selection: EndpointOperationSelection,
|
|
209
171
|
credential: EndpointTestCredential | undefined,
|
|
210
172
|
diagnostics: DataSourceDiagnostic[],
|
|
211
173
|
): EndpointTestRequestDiagnostic | undefined {
|
|
212
|
-
const baseUrl =
|
|
174
|
+
const baseUrl = getRestBaseUrl(input.api, selection.endpoint);
|
|
213
175
|
const path = selection.operation.path ?? selection.endpoint.path;
|
|
214
|
-
if (
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
if (path === undefined) {
|
|
221
|
-
diagnostics.push(createDiagnostic(input, 'invalid-config', 'HTTP operation requires a path.'));
|
|
176
|
+
if (baseUrl === undefined || path === undefined) {
|
|
177
|
+
const message =
|
|
178
|
+
baseUrl === undefined
|
|
179
|
+
? 'HTTP endpoint requires a base URL.'
|
|
180
|
+
: 'HTTP operation requires a path.';
|
|
181
|
+
diagnostics.push(createDiagnostic(input, 'invalid-config', message));
|
|
222
182
|
return undefined;
|
|
223
183
|
}
|
|
224
184
|
|
|
@@ -226,17 +186,25 @@ function buildHttpRequest(
|
|
|
226
186
|
const parameters = selection.operation.request?.parameters ?? [];
|
|
227
187
|
const resolvedPath = interpolatePath(path, parameters, values, input, diagnostics);
|
|
228
188
|
const query = collectQuery(parameters, values, credential?.query);
|
|
229
|
-
const
|
|
230
|
-
const
|
|
231
|
-
|
|
232
|
-
|
|
189
|
+
const headers = collectHeaders(parameters, values, credential?.headers);
|
|
190
|
+
const body = createHttpBody(selection.operation, parameters, values);
|
|
191
|
+
const requestHeaders =
|
|
192
|
+
body === undefined
|
|
193
|
+
? headers
|
|
194
|
+
: {
|
|
195
|
+
...headers,
|
|
196
|
+
'content-type':
|
|
197
|
+
headers['content-type'] ??
|
|
198
|
+
selection.operation.request?.contentType ??
|
|
199
|
+
'application/json',
|
|
200
|
+
};
|
|
233
201
|
return {
|
|
234
|
-
|
|
202
|
+
apiId: input.api.id,
|
|
235
203
|
endpointId: input.endpointId,
|
|
236
204
|
operationId: input.operationId,
|
|
237
205
|
url: appendQuery(joinUrl(baseUrl, resolvedPath), query),
|
|
238
206
|
method: selection.operation.method ?? 'GET',
|
|
239
|
-
headers,
|
|
207
|
+
headers: requestHeaders,
|
|
240
208
|
body,
|
|
241
209
|
dryRun: input.dryRun === true,
|
|
242
210
|
};
|
|
@@ -248,8 +216,8 @@ function buildGraphQlRequest(
|
|
|
248
216
|
credential: EndpointTestCredential | undefined,
|
|
249
217
|
diagnostics: DataSourceDiagnostic[],
|
|
250
218
|
): EndpointTestRequestDiagnostic | undefined {
|
|
251
|
-
const endpointUrl = getGraphQlEndpointUrl(input.
|
|
252
|
-
if (
|
|
219
|
+
const endpointUrl = getGraphQlEndpointUrl(input.api, selection.endpoint);
|
|
220
|
+
if (endpointUrl === undefined) {
|
|
253
221
|
diagnostics.push(
|
|
254
222
|
createDiagnostic(input, 'invalid-config', 'GraphQL endpoint requires an endpoint URL.'),
|
|
255
223
|
);
|
|
@@ -268,7 +236,7 @@ function buildGraphQlRequest(
|
|
|
268
236
|
|
|
269
237
|
const variables = getRecordValue(input.values, 'variables');
|
|
270
238
|
return {
|
|
271
|
-
|
|
239
|
+
apiId: input.api.id,
|
|
272
240
|
endpointId: input.endpointId,
|
|
273
241
|
operationId: input.operationId,
|
|
274
242
|
url: appendQuery(endpointUrl, credential?.query ?? {}),
|
|
@@ -279,185 +247,15 @@ function buildGraphQlRequest(
|
|
|
279
247
|
};
|
|
280
248
|
}
|
|
281
249
|
|
|
282
|
-
function
|
|
283
|
-
source: DataSourceConfig,
|
|
284
|
-
endpoint: DataEndpointConfig,
|
|
285
|
-
): string | undefined {
|
|
250
|
+
function getRestBaseUrl(api: ApiDefinition, endpoint: DataEndpointConfig): string | undefined {
|
|
286
251
|
if (endpoint.baseUrl !== undefined) return endpoint.baseUrl;
|
|
287
|
-
|
|
288
|
-
return source.protocol === 'rest' ? source.baseUrl : source.endpointUrl;
|
|
252
|
+
return api.origin === 'external' && api.protocol === 'rest' ? api.baseUrl : undefined;
|
|
289
253
|
}
|
|
290
254
|
|
|
291
255
|
function getGraphQlEndpointUrl(
|
|
292
|
-
|
|
256
|
+
api: ApiDefinition,
|
|
293
257
|
endpoint: DataEndpointConfig,
|
|
294
258
|
): string | undefined {
|
|
295
259
|
if (endpoint.baseUrl !== undefined) return endpoint.baseUrl;
|
|
296
|
-
return
|
|
297
|
-
? source.endpointUrl
|
|
298
|
-
: undefined;
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
function interpolatePath(
|
|
302
|
-
path: string,
|
|
303
|
-
parameters: readonly DataOperationParameter[],
|
|
304
|
-
values: EndpointTestInputValues,
|
|
305
|
-
input: EndpointTestInput,
|
|
306
|
-
diagnostics: DataSourceDiagnostic[],
|
|
307
|
-
): string {
|
|
308
|
-
let next = path;
|
|
309
|
-
for (const parameter of parameters.filter((item) => item.location === 'path')) {
|
|
310
|
-
const value = values[parameter.name] ?? parameter.default;
|
|
311
|
-
if (value === undefined) {
|
|
312
|
-
diagnostics.push(
|
|
313
|
-
createDiagnostic(
|
|
314
|
-
input,
|
|
315
|
-
'invalid-config',
|
|
316
|
-
`Missing required path parameter '${parameter.name}'.`,
|
|
317
|
-
),
|
|
318
|
-
);
|
|
319
|
-
continue;
|
|
320
|
-
}
|
|
321
|
-
const encoded = encodeURIComponent(serializeEndpointUrlValue(value));
|
|
322
|
-
next = next
|
|
323
|
-
.replaceAll(`{${parameter.name}}`, encoded)
|
|
324
|
-
.replaceAll(`:${parameter.name}`, encoded);
|
|
325
|
-
}
|
|
326
|
-
return next;
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
function collectQuery(
|
|
330
|
-
parameters: readonly DataOperationParameter[],
|
|
331
|
-
values: EndpointTestInputValues,
|
|
332
|
-
credentialQuery: EndpointTestInputValues | undefined,
|
|
333
|
-
): EndpointTestInputValues {
|
|
334
|
-
const query: Record<string, DataContractValue> = { ...(credentialQuery ?? {}) };
|
|
335
|
-
for (const parameter of parameters) {
|
|
336
|
-
if (parameter.location !== 'query') continue;
|
|
337
|
-
const value = values[parameter.name] ?? parameter.default;
|
|
338
|
-
if (value !== undefined) query[parameter.name] = value;
|
|
339
|
-
}
|
|
340
|
-
return query;
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
function createHttpBody(
|
|
344
|
-
operation: DataOperationConfig,
|
|
345
|
-
values: EndpointTestInputValues,
|
|
346
|
-
): string | undefined {
|
|
347
|
-
if (operation.method === 'GET' || operation.method === 'HEAD') return undefined;
|
|
348
|
-
return values.body === undefined ? undefined : JSON.stringify(values.body);
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
async function createResponseDiagnostic(
|
|
352
|
-
response: EndpointTestFetchResponse,
|
|
353
|
-
): Promise<EndpointTestResponseDiagnostic> {
|
|
354
|
-
const bodyText = await response.text();
|
|
355
|
-
return {
|
|
356
|
-
status: response.status,
|
|
357
|
-
ok: response.status >= 200 && response.status < 300,
|
|
358
|
-
headers: response.headers,
|
|
359
|
-
bodyText,
|
|
360
|
-
parsedBody: parseResponseBody(bodyText),
|
|
361
|
-
};
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
function parseResponseBody(bodyText: string): DataContractValue | undefined {
|
|
365
|
-
if (bodyText.trim().length === 0) return undefined;
|
|
366
|
-
try {
|
|
367
|
-
const parsed: unknown = JSON.parse(bodyText);
|
|
368
|
-
return isDataContractValue(parsed) ? parsed : { raw: bodyText };
|
|
369
|
-
} catch {
|
|
370
|
-
return { raw: bodyText };
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
function appendQuery(url: string, query: EndpointTestInputValues): string {
|
|
375
|
-
const entries = Object.entries(query);
|
|
376
|
-
if (entries.length === 0) return url;
|
|
377
|
-
const search = entries
|
|
378
|
-
.map(
|
|
379
|
-
([key, value]) =>
|
|
380
|
-
`${encodeURIComponent(key)}=${encodeURIComponent(serializeEndpointUrlValue(value))}`,
|
|
381
|
-
)
|
|
382
|
-
.join('&');
|
|
383
|
-
return `${url}${url.includes('?') ? '&' : '?'}${search}`;
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
function serializeEndpointUrlValue(value: DataContractValue): string {
|
|
387
|
-
if (typeof value === 'string') return value;
|
|
388
|
-
if (typeof value === 'number' || typeof value === 'boolean') return value.toString();
|
|
389
|
-
if (value === null) return '';
|
|
390
|
-
return JSON.stringify(value);
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
function joinUrl(baseUrl: string, path: string): string {
|
|
394
|
-
const normalizedBaseUrl = baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl;
|
|
395
|
-
const normalizedPath = path.startsWith('/') ? path : `/${path}`;
|
|
396
|
-
return `${normalizedBaseUrl}${normalizedPath}`;
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
function getStringValue(
|
|
400
|
-
values: EndpointTestInputValues | undefined,
|
|
401
|
-
key: string,
|
|
402
|
-
): string | undefined {
|
|
403
|
-
const value = values?.[key];
|
|
404
|
-
return typeof value === 'string' ? value : undefined;
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
function getRecordValue(
|
|
408
|
-
values: EndpointTestInputValues | undefined,
|
|
409
|
-
key: string,
|
|
410
|
-
): DataContractValue | undefined {
|
|
411
|
-
const value = values?.[key];
|
|
412
|
-
return isDataContractValue(value) ? value : undefined;
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
function getStringMetadataValue(
|
|
416
|
-
metadata: DataContractValue | undefined,
|
|
417
|
-
key: string,
|
|
418
|
-
): string | undefined {
|
|
419
|
-
if (!isDataContractRecord(metadata)) return undefined;
|
|
420
|
-
const value = metadata[key];
|
|
421
|
-
return typeof value === 'string' ? value : undefined;
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
function createDiagnostic(
|
|
425
|
-
input: EndpointTestInput,
|
|
426
|
-
code: string,
|
|
427
|
-
message: string,
|
|
428
|
-
): DataSourceDiagnostic {
|
|
429
|
-
return {
|
|
430
|
-
code,
|
|
431
|
-
dataSourceId: input.dataSource.id,
|
|
432
|
-
endpointId: input.endpointId,
|
|
433
|
-
operationId: input.operationId,
|
|
434
|
-
message,
|
|
435
|
-
severity: code === 'missing-schema' ? 'info' : 'error',
|
|
436
|
-
};
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
function failure(
|
|
440
|
-
request: EndpointTestRequestDiagnostic | undefined,
|
|
441
|
-
diagnostics: readonly DataSourceDiagnostic[],
|
|
442
|
-
response?: EndpointTestResponseDiagnostic,
|
|
443
|
-
): EndpointTestResult {
|
|
444
|
-
return { ok: false, request, response, diagnostics };
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
function createNetworkErrorMessage(error: unknown): string {
|
|
448
|
-
return error instanceof Error ? error.message : 'Endpoint test request failed.';
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
function isDataContractValue(value: unknown): value is DataContractValue {
|
|
452
|
-
if (value === null || ['string', 'number', 'boolean'].includes(typeof value)) return true;
|
|
453
|
-
if (Array.isArray(value)) return value.every(isDataContractValue);
|
|
454
|
-
return typeof value === 'object' && Object.values(value).every(isDataContractValue);
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
function isDataContractRecord(
|
|
458
|
-
value: DataContractValue | undefined,
|
|
459
|
-
): value is Record<string, DataContractValue> {
|
|
460
|
-
return (
|
|
461
|
-
value !== undefined && typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
462
|
-
);
|
|
260
|
+
return api.origin === 'external' && api.protocol === 'graphql' ? api.endpointUrl : undefined;
|
|
463
261
|
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { DataContractValue } from '@ankhorage/contracts/data';
|
|
2
|
+
|
|
3
|
+
import type { EndpointTestFetchResponse, EndpointTestResponseDiagnostic } from './types';
|
|
4
|
+
|
|
5
|
+
export async function createResponseDiagnostic(
|
|
6
|
+
response: EndpointTestFetchResponse,
|
|
7
|
+
): Promise<EndpointTestResponseDiagnostic> {
|
|
8
|
+
const bodyText = await response.text();
|
|
9
|
+
return {
|
|
10
|
+
status: response.status,
|
|
11
|
+
ok: response.status >= 200 && response.status < 300,
|
|
12
|
+
headers: response.headers,
|
|
13
|
+
bodyText,
|
|
14
|
+
parsedBody: parseResponseBody(bodyText),
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function parseResponseBody(bodyText: string): DataContractValue | undefined {
|
|
19
|
+
if (bodyText.trim().length === 0) return undefined;
|
|
20
|
+
try {
|
|
21
|
+
const parsed: unknown = JSON.parse(bodyText);
|
|
22
|
+
return isDataContractValue(parsed) ? parsed : { raw: bodyText };
|
|
23
|
+
} catch {
|
|
24
|
+
return { raw: bodyText };
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function isDataContractValue(value: unknown): value is DataContractValue {
|
|
29
|
+
if (value === null || ['string', 'number', 'boolean'].includes(typeof value)) return true;
|
|
30
|
+
if (Array.isArray(value)) return value.every(isDataContractValue);
|
|
31
|
+
return typeof value === 'object' && Object.values(value).every(isDataContractValue);
|
|
32
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { DataSourceDiagnostic } from '@ankhorage/contracts/data';
|
|
2
|
+
|
|
3
|
+
import type {
|
|
4
|
+
EndpointTestInput,
|
|
5
|
+
EndpointTestRequestDiagnostic,
|
|
6
|
+
EndpointTestResponseDiagnostic,
|
|
7
|
+
EndpointTestResult,
|
|
8
|
+
} from './types';
|
|
9
|
+
|
|
10
|
+
export function createDiagnostic(
|
|
11
|
+
input: EndpointTestInput,
|
|
12
|
+
code: string,
|
|
13
|
+
message: string,
|
|
14
|
+
): DataSourceDiagnostic {
|
|
15
|
+
return {
|
|
16
|
+
code,
|
|
17
|
+
apiId: input.api.id,
|
|
18
|
+
endpointId: input.endpointId,
|
|
19
|
+
operationId: input.operationId,
|
|
20
|
+
message,
|
|
21
|
+
severity: code === 'missing-schema' ? 'info' : 'error',
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function failure(
|
|
26
|
+
request: EndpointTestRequestDiagnostic | undefined,
|
|
27
|
+
diagnostics: readonly DataSourceDiagnostic[],
|
|
28
|
+
response?: EndpointTestResponseDiagnostic,
|
|
29
|
+
): EndpointTestResult {
|
|
30
|
+
return { ok: false, request, response, diagnostics };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function createNetworkErrorMessage(error: unknown): string {
|
|
34
|
+
return error instanceof Error ? error.message : 'Endpoint test request failed.';
|
|
35
|
+
}
|