@scalar/mock-server 0.16.0 → 0.17.1
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 +20 -0
- package/dist/create-asyncapi-mock-server.d.ts +2 -0
- package/dist/create-asyncapi-mock-server.d.ts.map +1 -1
- package/dist/create-asyncapi-mock-server.js +1 -1
- package/dist/create-mock-server.js +3 -3
- package/dist/routes/mock-any-response.d.ts +1 -1
- package/dist/routes/mock-any-response.d.ts.map +1 -1
- package/dist/routes/mock-any-response.js +20 -5
- package/dist/routes/mock-handler-response.d.ts +1 -1
- package/dist/routes/mock-handler-response.d.ts.map +1 -1
- package/dist/routes/mock-handler-response.js +33 -6
- package/dist/utils/process-asyncapi-document.d.ts +2 -1
- package/dist/utils/process-asyncapi-document.d.ts.map +1 -1
- package/dist/utils/process-asyncapi-document.js +10 -2
- package/dist/utils/select-response-example.d.ts +7 -3
- package/dist/utils/select-response-example.d.ts.map +1 -1
- package/dist/utils/select-response-example.js +16 -6
- package/dist/utils/serialize-response-body.d.ts +1 -1
- package/dist/utils/serialize-response-body.d.ts.map +1 -1
- package/dist/utils/serialize-response-body.js +15 -11
- package/package.json +7 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# @scalar/mock-server
|
|
2
2
|
|
|
3
|
+
## 0.17.1
|
|
4
|
+
|
|
5
|
+
## 0.17.0
|
|
6
|
+
|
|
7
|
+
### Minor Changes
|
|
8
|
+
|
|
9
|
+
- [#10192](https://github.com/scalar/scalar/pull/10192): Mock-server XML response bytes now use the shared schema-aware serializer instead of `json2xml`, including attributes, namespaces, and root naming. Existing XML response snapshots may need updating. Supplied serialized XML remains unchanged.
|
|
10
|
+
|
|
11
|
+
Generate XML examples from schema metadata, preserving attributes, namespaces, array wrappers, repeated elements, and OpenAPI 3.2 text and CDATA nodes. Use the same XML serialization for request bodies, code snippets, response examples, mock responses, and Markdown documentation. Preserve serialized media examples and escape schema string examples as element text.
|
|
12
|
+
|
|
13
|
+
Explain XML generation failures in response example panels, including the serialized-example escape hatch for large payloads. Expose XML generation failures in mock response headers with `X-Scalar-XML-Error`, containing the first error diagnostic code. Report diagnostics to other consumers through a callback or the developer console, and format element-only descendants within mixed content without changing text values.
|
|
14
|
+
|
|
15
|
+
### Patch Changes
|
|
16
|
+
|
|
17
|
+
- [#10343](https://github.com/scalar/scalar/pull/10343): Restrict AsyncAPI external references to the source directory and public network addresses, and preserve the source location for preloaded documents.
|
|
18
|
+
|
|
19
|
+
URL inputs to `createAsyncApiMockServer` also reject private network addresses, including localhost. Load local files or pass preloaded document content for local development. Docker documents supplied through `OPENAPI_DOCUMENT` resolve relative references from their temporary `/tmp/openapi.json` or `/tmp/openapi.yaml` file, confined to `/tmp`. Both OpenAPI and AsyncAPI Docker documents preserve the selected source origin.
|
|
20
|
+
|
|
21
|
+
- [#10192](https://github.com/scalar/scalar/pull/10192): Apply edited XML bodies instead of their original serialized or data examples, render schema-free XML examples in Markdown, and share reference decoding and response provenance selection.
|
|
22
|
+
|
|
3
23
|
## 0.16.0
|
|
4
24
|
|
|
5
25
|
### Minor Changes
|
|
@@ -9,6 +9,8 @@ export type AsyncApiMockServerOptions = {
|
|
|
9
9
|
* string, or an already-parsed object.
|
|
10
10
|
*/
|
|
11
11
|
document?: string | Record<string, any>;
|
|
12
|
+
/** Source file path or URL for resolving references in an already loaded document. */
|
|
13
|
+
origin?: string;
|
|
12
14
|
/**
|
|
13
15
|
* Additional transports appended after the built-in WebSocket and SSE transports. Use this to
|
|
14
16
|
* support extra protocols (for example SignalR) without changing the core. The first transport
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-asyncapi-mock-server.d.ts","sourceRoot":"","sources":["../src/create-asyncapi-mock-server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,mBAAmB,EAAoB,MAAM,mBAAmB,CAAA;AAC9E,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAK3B,OAAO,KAAK,EAAE,gBAAgB,EAAE,aAAa,EAAoB,MAAM,oBAAoB,CAAA;AAC3F,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAM/C,oDAAoD;AACpD,MAAM,MAAM,yBAAyB,GAAG;IACtC;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IAEvC;;;;OAIG;IACH,UAAU,CAAC,EAAE,aAAa,EAAE,CAAA;IAE5B,yFAAyF;IACzF,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,gBAAgB,CAAC;QAAC,OAAO,EAAE,OAAO,CAAA;KAAE,KAAK,IAAI,CAAA;IAE/F;;;;;OAKG;IACH,MAAM,CAAC,EAAE,OAAO,GAAG,gBAAgB,CAAA;CACpC,CAAA;AAED,sDAAsD;AACtD,MAAM,MAAM,kBAAkB,GAAG;IAC/B,sEAAsE;IACtE,GAAG,EAAE,IAAI,CAAA;IACT,wFAAwF;IACxF,SAAS,EAAE;QAAE,MAAM,EAAE,mBAAmB,CAAA;KAAE,CAAA;CAC3C,CAAA;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,wBAAwB,CAAC,OAAO,EAAE,yBAAyB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAqC9G"}
|
|
1
|
+
{"version":3,"file":"create-asyncapi-mock-server.d.ts","sourceRoot":"","sources":["../src/create-asyncapi-mock-server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,mBAAmB,EAAoB,MAAM,mBAAmB,CAAA;AAC9E,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAK3B,OAAO,KAAK,EAAE,gBAAgB,EAAE,aAAa,EAAoB,MAAM,oBAAoB,CAAA;AAC3F,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAM/C,oDAAoD;AACpD,MAAM,MAAM,yBAAyB,GAAG;IACtC;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IAEvC,sFAAsF;IACtF,MAAM,CAAC,EAAE,MAAM,CAAA;IAEf;;;;OAIG;IACH,UAAU,CAAC,EAAE,aAAa,EAAE,CAAA;IAE5B,yFAAyF;IACzF,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,gBAAgB,CAAC;QAAC,OAAO,EAAE,OAAO,CAAA;KAAE,KAAK,IAAI,CAAA;IAE/F;;;;;OAKG;IACH,MAAM,CAAC,EAAE,OAAO,GAAG,gBAAgB,CAAA;CACpC,CAAA;AAED,sDAAsD;AACtD,MAAM,MAAM,kBAAkB,GAAG;IAC/B,sEAAsE;IACtE,GAAG,EAAE,IAAI,CAAA;IACT,wFAAwF;IACxF,SAAS,EAAE;QAAE,MAAM,EAAE,mBAAmB,CAAA;KAAE,CAAA;CAC3C,CAAA;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,wBAAwB,CAAC,OAAO,EAAE,yBAAyB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAqC9G"}
|
|
@@ -22,7 +22,7 @@ import { resolveLogger } from './utils/resolve-logger.js';
|
|
|
22
22
|
*/
|
|
23
23
|
export async function createAsyncApiMockServer(options) {
|
|
24
24
|
const app = new Hono();
|
|
25
|
-
const document = await processAsyncApiDocument(options.document);
|
|
25
|
+
const document = await processAsyncApiDocument(options.document, options.origin);
|
|
26
26
|
const channels = resolveChannels(document);
|
|
27
27
|
const transports = [...defaultTransports, ...(options.transports ?? [])];
|
|
28
28
|
const log = resolveLogger(options.logger, false);
|
|
@@ -109,7 +109,7 @@ export async function createMockServer(configuration) {
|
|
|
109
109
|
allowedMethods.add(method);
|
|
110
110
|
}
|
|
111
111
|
}
|
|
112
|
-
app.use(cors({ origin: '*', allowMethods: [...allowedMethods] }));
|
|
112
|
+
app.use(cors({ origin: '*', allowMethods: [...allowedMethods], exposeHeaders: ['X-Scalar-XML-Error'] }));
|
|
113
113
|
/** Authentication methods defined in the OpenAPI document */
|
|
114
114
|
setUpAuthenticationRoutes(app, schema);
|
|
115
115
|
// Only the instructions honor `logger` (on by default); the util still prints warnings and errors
|
|
@@ -192,10 +192,10 @@ export async function createMockServer(configuration) {
|
|
|
192
192
|
const hasHandler = handlerCode && typeof handlerCode === 'string' && handlerCode.trim().length > 0;
|
|
193
193
|
// Route to appropriate handler
|
|
194
194
|
if (hasHandler) {
|
|
195
|
-
handlers.push(async (c) => await mockHandlerResponse(c, operation, pathItem?.parameters));
|
|
195
|
+
handlers.push(async (c) => await mockHandlerResponse(c, operation, pathItem?.parameters, schema.openapi));
|
|
196
196
|
}
|
|
197
197
|
else {
|
|
198
|
-
handlers.push(async (c) => await mockAnyResponse(c, operation));
|
|
198
|
+
handlers.push(async (c) => await mockAnyResponse(c, operation, schema.openapi));
|
|
199
199
|
}
|
|
200
200
|
// The pinned query parameters are not part of the route, so they are checked here. A request
|
|
201
201
|
// that does not carry them is handed on to the next matching route — usually the sibling path
|
|
@@ -3,5 +3,5 @@ import type { Context } from 'hono';
|
|
|
3
3
|
/**
|
|
4
4
|
* Mock any response
|
|
5
5
|
*/
|
|
6
|
-
export declare function mockAnyResponse(c: Context, operation: OpenAPIV3_1.OperationObject): Response;
|
|
6
|
+
export declare function mockAnyResponse(c: Context, operation: OpenAPIV3_1.OperationObject, openapiVersion?: string): Response;
|
|
7
7
|
//# sourceMappingURL=mock-any-response.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mock-any-response.d.ts","sourceRoot":"","sources":["../../src/routes/mock-any-response.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"mock-any-response.d.ts","sourceRoot":"","sources":["../../src/routes/mock-any-response.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AAKxD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAA;AAenC;;GAEG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAAE,OAAO,EAAE,SAAS,EAAE,WAAW,CAAC,eAAe,EAAE,cAAc,CAAC,EAAE,MAAM,YA4J1G"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import { isXmlMediaType } from '@scalar/helpers/http/is-xml-media-type';
|
|
1
2
|
import { getResolvedRef } from '@scalar/workspace-store/helpers/get-resolved-ref';
|
|
2
3
|
import { getResolvedRefDeep } from '@scalar/workspace-store/helpers/get-resolved-ref-deep';
|
|
3
|
-
import { getExampleFromSchema } from '@scalar/workspace-store/request-example';
|
|
4
|
+
import { getExampleFromSchema, getXmlBodyExample } from '@scalar/workspace-store/request-example';
|
|
4
5
|
import { streamSSE } from 'hono/streaming';
|
|
5
6
|
import { collectSseEvents, isEventStreamContentType } from '../utils/collect-sse-events.js';
|
|
6
7
|
import { findPreferredResponseKey } from '../utils/find-preferred-response-key.js';
|
|
@@ -15,7 +16,7 @@ import { getStreamingResponse, sendStreamingResponse } from '../utils/streaming-
|
|
|
15
16
|
/**
|
|
16
17
|
* Mock any response
|
|
17
18
|
*/
|
|
18
|
-
export function mockAnyResponse(c, operation) {
|
|
19
|
+
export function mockAnyResponse(c, operation, openapiVersion) {
|
|
19
20
|
// Note: the `onRequest` callback runs as middleware (see `create-mock-server`) so it also fires
|
|
20
21
|
// for requests rejected before reaching this handler.
|
|
21
22
|
// Parse the Prefer header (RFC 7240) so clients can request a specific
|
|
@@ -102,9 +103,24 @@ export function mockAnyResponse(c, operation) {
|
|
|
102
103
|
// Body: a named/singular/first example if one is defined, otherwise generate
|
|
103
104
|
// a value from the schema. `Prefer: example=<name>` picks a named example.
|
|
104
105
|
const selectedExample = selectResponseExample(acceptedResponse, prefer.example);
|
|
106
|
+
c.status(statusCode);
|
|
107
|
+
if (isXmlMediaType(acceptedContentType)) {
|
|
108
|
+
const result = getXmlBodyExample(acceptedResponse?.schema, selectedExample, {
|
|
109
|
+
openapiVersion,
|
|
110
|
+
emptyString: 'string',
|
|
111
|
+
variables: pathParameters(c),
|
|
112
|
+
mode: 'read',
|
|
113
|
+
});
|
|
114
|
+
const error = result.diagnostics.find((diagnostic) => diagnostic.severity === 'error');
|
|
115
|
+
if (error) {
|
|
116
|
+
c.header('X-Scalar-XML-Error', error.code);
|
|
117
|
+
}
|
|
118
|
+
return result.xml === undefined ? c.body(null) : c.body(result.xml);
|
|
119
|
+
}
|
|
120
|
+
const provenance = selectedExample?.provenance;
|
|
105
121
|
const body = (() => {
|
|
106
122
|
if (selectedExample) {
|
|
107
|
-
return normalizeResponseBody(selectedExample.value, responseSchema);
|
|
123
|
+
return provenance ? selectedExample.value : normalizeResponseBody(selectedExample.value, responseSchema);
|
|
108
124
|
}
|
|
109
125
|
if (!responseSchema) {
|
|
110
126
|
return null;
|
|
@@ -119,8 +135,7 @@ export function mockAnyResponse(c, operation) {
|
|
|
119
135
|
// Re-inferring it from sibling items would wrap a selected primitive in an array.
|
|
120
136
|
return generated;
|
|
121
137
|
})();
|
|
122
|
-
|
|
123
|
-
const serializedBody = serializeResponseBody(body, acceptedContentType, responseSchema);
|
|
138
|
+
const serializedBody = serializeResponseBody(body, acceptedContentType, responseSchema, provenance);
|
|
124
139
|
// `JSON.stringify` returns `undefined` for an `undefined` body, which is an empty response.
|
|
125
140
|
if (serializedBody === undefined) {
|
|
126
141
|
return c.body(null);
|
|
@@ -4,5 +4,5 @@ import type { Context } from 'hono';
|
|
|
4
4
|
* Mock response using x-handler code.
|
|
5
5
|
* Executes the handler and returns its result as the response.
|
|
6
6
|
*/
|
|
7
|
-
export declare function mockHandlerResponse(c: Context, operation: OpenAPIV3_1.OperationObject, pathItemParameters?: OpenAPIV3_1.PathItemObject['parameters']): Promise<Response>;
|
|
7
|
+
export declare function mockHandlerResponse(c: Context, operation: OpenAPIV3_1.OperationObject, pathItemParameters?: OpenAPIV3_1.PathItemObject['parameters'], openapiVersion?: string): Promise<Response>;
|
|
8
8
|
//# sourceMappingURL=mock-handler-response.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mock-handler-response.d.ts","sourceRoot":"","sources":["../../src/routes/mock-handler-response.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"mock-handler-response.d.ts","sourceRoot":"","sources":["../../src/routes/mock-handler-response.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AAIxD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAA;AAoJnC;;;GAGG;AACH,wBAAsB,mBAAmB,CACvC,CAAC,EAAE,OAAO,EACV,SAAS,EAAE,WAAW,CAAC,eAAe,EACtC,kBAAkB,CAAC,EAAE,WAAW,CAAC,cAAc,CAAC,YAAY,CAAC,EAC7D,cAAc,CAAC,EAAE,MAAM,qBA8ExB"}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { isXmlMediaType } from '@scalar/helpers/http/is-xml-media-type';
|
|
1
2
|
import { getResolvedRef } from '@scalar/workspace-store/helpers/get-resolved-ref';
|
|
3
|
+
import { getXmlBodyExample } from '@scalar/workspace-store/request-example';
|
|
2
4
|
import { buildHandlerContext } from '../utils/build-handler-context.js';
|
|
3
5
|
import { executeHandler } from '../utils/execute-handler.js';
|
|
4
6
|
import { generateResponseExample } from '../utils/generate-response-example.js';
|
|
@@ -7,16 +9,17 @@ import { normalizeResponseBody } from '../utils/normalize-response-body.js';
|
|
|
7
9
|
import { parsePreferHeader } from '../utils/parse-prefer-header.js';
|
|
8
10
|
import { pathParameters } from '../utils/path-parameters.js';
|
|
9
11
|
import { selectResponseExample } from '../utils/select-response-example.js';
|
|
12
|
+
import { serializeResponseBody } from '../utils/serialize-response-body.js';
|
|
10
13
|
import { getStreamingResponse, sendStreamingResponse } from '../utils/streaming-response.js';
|
|
11
14
|
/**
|
|
12
15
|
* Get example response from OpenAPI spec for a given status code.
|
|
13
|
-
* Returns
|
|
16
|
+
* Returns a serialized payload if found, or null if not available.
|
|
14
17
|
*
|
|
15
18
|
* Honors `Prefer: example=<name>` to pick a named example from the
|
|
16
19
|
* `examples` map; otherwise it falls back to the singular `example`, the
|
|
17
20
|
* first entry of the map, or a value generated from the schema.
|
|
18
21
|
*/
|
|
19
|
-
function getExampleFromResponse(c, statusCode, responses, exampleName) {
|
|
22
|
+
function getExampleFromResponse(c, statusCode, responses, exampleName, openapiVersion) {
|
|
20
23
|
if (!responses) {
|
|
21
24
|
return null;
|
|
22
25
|
}
|
|
@@ -39,11 +42,32 @@ function getExampleFromResponse(c, statusCode, responses, exampleName) {
|
|
|
39
42
|
const responseSchema = acceptedResponse.schema ? getResolvedRef(acceptedResponse.schema) : undefined;
|
|
40
43
|
// Extract example (named, singular, or first) or generate from schema
|
|
41
44
|
const selectedExample = selectResponseExample(acceptedResponse, exampleName);
|
|
42
|
-
|
|
45
|
+
if (isXmlMediaType(acceptedContentType)) {
|
|
46
|
+
c.header('Content-Type', acceptedContentType);
|
|
47
|
+
const result = getXmlBodyExample(acceptedResponse.schema, selectedExample, {
|
|
48
|
+
openapiVersion,
|
|
49
|
+
emptyString: 'string',
|
|
50
|
+
variables: pathParameters(c),
|
|
51
|
+
mode: 'read',
|
|
52
|
+
});
|
|
53
|
+
const error = result.diagnostics.find((diagnostic) => diagnostic.severity === 'error');
|
|
54
|
+
if (error) {
|
|
55
|
+
c.header('X-Scalar-XML-Error', error.code);
|
|
56
|
+
}
|
|
57
|
+
return result.xml ?? null;
|
|
58
|
+
}
|
|
59
|
+
const provenance = selectedExample?.provenance;
|
|
60
|
+
if (selectedExample && provenance) {
|
|
61
|
+
c.header('Content-Type', acceptedContentType);
|
|
62
|
+
return serializeResponseBody(selectedExample.value, acceptedContentType, responseSchema, provenance) ?? null;
|
|
63
|
+
}
|
|
64
|
+
const value = selectedExample
|
|
43
65
|
? normalizeResponseBody(selectedExample.value, responseSchema)
|
|
44
66
|
: responseSchema
|
|
45
67
|
? normalizeResponseBody(generateResponseExample(responseSchema, pathParameters(c)), responseSchema)
|
|
46
68
|
: null;
|
|
69
|
+
// Legacy examples retain the handler fallback's JSON encoding policy.
|
|
70
|
+
return JSON.stringify(value) ?? null;
|
|
47
71
|
}
|
|
48
72
|
/**
|
|
49
73
|
* Determine HTTP status code based on store operation tracking.
|
|
@@ -98,7 +122,7 @@ function determineStatusCode(tracking) {
|
|
|
98
122
|
* Mock response using x-handler code.
|
|
99
123
|
* Executes the handler and returns its result as the response.
|
|
100
124
|
*/
|
|
101
|
-
export async function mockHandlerResponse(c, operation, pathItemParameters) {
|
|
125
|
+
export async function mockHandlerResponse(c, operation, pathItemParameters, openapiVersion) {
|
|
102
126
|
// Note: the `onRequest` callback runs as middleware (see `create-mock-server`) so it also fires
|
|
103
127
|
// for requests rejected before reaching this handler.
|
|
104
128
|
// Get x-handler code from operation
|
|
@@ -137,9 +161,12 @@ export async function mockHandlerResponse(c, operation, pathItemParameters) {
|
|
|
137
161
|
if (result === undefined || result === null) {
|
|
138
162
|
// Try to pick up example response from OpenAPI spec if available
|
|
139
163
|
const prefer = parsePreferHeader(c.req.header('Prefer'));
|
|
140
|
-
const exampleResponse = getExampleFromResponse(c, statusCode, operation.responses, prefer.example);
|
|
164
|
+
const exampleResponse = getExampleFromResponse(c, statusCode, operation.responses, prefer.example, openapiVersion);
|
|
141
165
|
if (exampleResponse !== null) {
|
|
142
|
-
return c.
|
|
166
|
+
return c.body(exampleResponse);
|
|
167
|
+
}
|
|
168
|
+
if (isXmlMediaType(c.res.headers.get('Content-Type') ?? undefined)) {
|
|
169
|
+
return c.body(null);
|
|
143
170
|
}
|
|
144
171
|
return c.json(null);
|
|
145
172
|
}
|
|
@@ -11,10 +11,11 @@ import type { AsyncApiDocument } from '@scalar/types/asyncapi/3.1';
|
|
|
11
11
|
* Only AsyncAPI 3.1 is supported; 2.x documents should be upgraded before being passed in.
|
|
12
12
|
*
|
|
13
13
|
* @param document - The AsyncAPI document to process. Can be a string (URL/path) or an object.
|
|
14
|
+
* @param origin - Source file path or URL for resolving references in an already loaded document.
|
|
14
15
|
* @returns A promise that resolves to the AsyncAPI document with lazily resolvable references.
|
|
15
16
|
* @throws Error if the document cannot be processed or is invalid.
|
|
16
17
|
*/
|
|
17
|
-
export declare function processAsyncApiDocument(document: string | Record<string, any> | undefined): Promise<AsyncApiDocument>;
|
|
18
|
+
export declare function processAsyncApiDocument(document: string | Record<string, any> | undefined, origin?: string): Promise<AsyncApiDocument>;
|
|
18
19
|
/**
|
|
19
20
|
* Detects whether a loaded document describes an AsyncAPI API (rather than OpenAPI/Swagger).
|
|
20
21
|
* Used to route an incoming document to the right mock engine.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"process-asyncapi-document.d.ts","sourceRoot":"","sources":["../../src/utils/process-asyncapi-document.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"process-asyncapi-document.d.ts","sourceRoot":"","sources":["../../src/utils/process-asyncapi-document.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAA;AAElE;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,uBAAuB,CAC3C,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,EAClD,MAAM,CAAC,EAAE,MAAM,GACd,OAAO,CAAC,gBAAgB,CAAC,CAsC3B;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,OAAO,GAAG,OAAO,CAE7D"}
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { cwd } from 'node:process';
|
|
1
3
|
import { bundle } from '@scalar/json-magic/bundle';
|
|
2
4
|
import { fetchUrls, parseJson, parseYaml, readFiles } from '@scalar/json-magic/bundle/plugins/node';
|
|
5
|
+
import { isFilePath } from '@scalar/json-magic/helpers/is-file-path';
|
|
3
6
|
import { createMagicProxy } from '@scalar/json-magic/magic-proxy';
|
|
4
7
|
/**
|
|
5
8
|
* Processes an AsyncAPI document by bundling external references and wrapping it so internal
|
|
@@ -13,10 +16,11 @@ import { createMagicProxy } from '@scalar/json-magic/magic-proxy';
|
|
|
13
16
|
* Only AsyncAPI 3.1 is supported; 2.x documents should be upgraded before being passed in.
|
|
14
17
|
*
|
|
15
18
|
* @param document - The AsyncAPI document to process. Can be a string (URL/path) or an object.
|
|
19
|
+
* @param origin - Source file path or URL for resolving references in an already loaded document.
|
|
16
20
|
* @returns A promise that resolves to the AsyncAPI document with lazily resolvable references.
|
|
17
21
|
* @throws Error if the document cannot be processed or is invalid.
|
|
18
22
|
*/
|
|
19
|
-
export async function processAsyncApiDocument(document) {
|
|
23
|
+
export async function processAsyncApiDocument(document, origin) {
|
|
20
24
|
// Handle empty/undefined input gracefully with a minimal valid document.
|
|
21
25
|
if (!document || (typeof document === 'object' && Object.keys(document).length === 0)) {
|
|
22
26
|
return {
|
|
@@ -30,10 +34,14 @@ export async function processAsyncApiDocument(document) {
|
|
|
30
34
|
};
|
|
31
35
|
}
|
|
32
36
|
let bundled;
|
|
37
|
+
// Keep references inside the source directory and prevent access to internal services.
|
|
38
|
+
const source = origin ?? document;
|
|
39
|
+
const basePath = typeof source === 'string' && isFilePath(source) ? path.dirname(path.resolve(source)) : cwd();
|
|
33
40
|
try {
|
|
34
41
|
// Bundle external references; parse string inputs (JSON or YAML) along the way.
|
|
35
42
|
bundled = await bundle(document, {
|
|
36
|
-
|
|
43
|
+
origin,
|
|
44
|
+
plugins: [parseJson(), parseYaml(), readFiles({ basePath }), fetchUrls({ blockPrivateNetworks: true })],
|
|
37
45
|
treeShake: false,
|
|
38
46
|
});
|
|
39
47
|
}
|
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
import type { OpenAPIV3_1 } from '@scalar/openapi-types';
|
|
2
|
+
import type { ExampleObject } from '@scalar/workspace-store/schemas/v3.2/strict/example';
|
|
3
|
+
type SelectedResponseExample = ExampleObject & {
|
|
4
|
+
value: unknown;
|
|
5
|
+
provenance?: 'serialized' | 'data';
|
|
6
|
+
};
|
|
2
7
|
/**
|
|
3
8
|
* Pick the example body for a response media type.
|
|
4
9
|
*
|
|
@@ -18,7 +23,6 @@ import type { OpenAPIV3_1 } from '@scalar/openapi-types';
|
|
|
18
23
|
*/
|
|
19
24
|
export declare const selectResponseExample: <T extends Pick<OpenAPIV3_1.MediaTypeObject, "example" | "examples"> & {
|
|
20
25
|
schema?: unknown;
|
|
21
|
-
}>(mediaType: T | undefined, exampleName?: string) =>
|
|
22
|
-
|
|
23
|
-
} | undefined;
|
|
26
|
+
}>(mediaType: T | undefined, exampleName?: string) => SelectedResponseExample | undefined;
|
|
27
|
+
export {};
|
|
24
28
|
//# sourceMappingURL=select-response-example.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"select-response-example.d.ts","sourceRoot":"","sources":["../../src/utils/select-response-example.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;
|
|
1
|
+
{"version":3,"file":"select-response-example.d.ts","sourceRoot":"","sources":["../../src/utils/select-response-example.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AAExD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qDAAqD,CAAA;AAExF,KAAK,uBAAuB,GAAG,aAAa,GAAG;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,UAAU,CAAC,EAAE,YAAY,GAAG,MAAM,CAAA;CAAE,CAAA;AAarG;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,qBAAqB,GAChC,CAAC,SAAS,IAAI,CAAC,WAAW,CAAC,eAAe,EAAE,SAAS,GAAG,UAAU,CAAC,GAAG;IAAE,MAAM,CAAC,EAAE,OAAO,CAAA;CAAE,EAE1F,WAAW,CAAC,GAAG,SAAS,EACxB,cAAc,MAAM,KACnB,uBAAuB,GAAG,SAkC5B,CAAA"}
|
|
@@ -1,4 +1,14 @@
|
|
|
1
1
|
import { getResolvedRef } from '@scalar/workspace-store/helpers/get-resolved-ref';
|
|
2
|
+
/** Keep serialized/data examples distinguishable until the media serializer runs. */
|
|
3
|
+
const exampleValue = (example) => {
|
|
4
|
+
if (example?.serializedValue !== undefined) {
|
|
5
|
+
return { serializedValue: example.serializedValue, value: example.serializedValue, provenance: 'serialized' };
|
|
6
|
+
}
|
|
7
|
+
if (example?.dataValue !== undefined) {
|
|
8
|
+
return { dataValue: example.dataValue, value: example.dataValue, provenance: 'data' };
|
|
9
|
+
}
|
|
10
|
+
return example?.value !== undefined ? { value: example.value } : undefined;
|
|
11
|
+
};
|
|
2
12
|
/**
|
|
3
13
|
* Pick the example body for a response media type.
|
|
4
14
|
*
|
|
@@ -23,9 +33,9 @@ export const selectResponseExample = (mediaType, exampleName) => {
|
|
|
23
33
|
const { example, examples } = mediaType;
|
|
24
34
|
// 1. A named example requested via `Prefer: example=<name>`
|
|
25
35
|
if (exampleName && examples && exampleName in examples) {
|
|
26
|
-
const
|
|
27
|
-
if (
|
|
28
|
-
return
|
|
36
|
+
const selected = exampleValue(getResolvedRef(examples[exampleName]));
|
|
37
|
+
if (selected) {
|
|
38
|
+
return selected;
|
|
29
39
|
}
|
|
30
40
|
}
|
|
31
41
|
// 2. The singular `example` keyword
|
|
@@ -36,9 +46,9 @@ export const selectResponseExample = (mediaType, exampleName) => {
|
|
|
36
46
|
if (examples) {
|
|
37
47
|
const firstKey = Object.keys(examples)[0];
|
|
38
48
|
if (firstKey !== undefined) {
|
|
39
|
-
const
|
|
40
|
-
if (
|
|
41
|
-
return
|
|
49
|
+
const selected = exampleValue(getResolvedRef(examples[firstKey]));
|
|
50
|
+
if (selected) {
|
|
51
|
+
return selected;
|
|
42
52
|
}
|
|
43
53
|
}
|
|
44
54
|
}
|
|
@@ -6,6 +6,6 @@ type Schema = NonNullable<OpenAPIV3_1.ComponentsObject['schemas']>[string];
|
|
|
6
6
|
* Returns `undefined` for an `undefined` body, mirroring `JSON.stringify`, so the caller can send an
|
|
7
7
|
* empty body rather than the characters `undefined`.
|
|
8
8
|
*/
|
|
9
|
-
export declare const serializeResponseBody: (body: unknown, contentType: string | undefined, schema?: Schema) => string | undefined;
|
|
9
|
+
export declare const serializeResponseBody: (body: unknown, contentType: string | undefined, schema?: Schema, provenance?: "data" | "serialized") => string | undefined;
|
|
10
10
|
export {};
|
|
11
11
|
//# sourceMappingURL=serialize-response-body.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"serialize-response-body.d.ts","sourceRoot":"","sources":["../../src/utils/serialize-response-body.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;
|
|
1
|
+
{"version":3,"file":"serialize-response-body.d.ts","sourceRoot":"","sources":["../../src/utils/serialize-response-body.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AAKxD,KAAK,MAAM,GAAG,WAAW,CAAC,WAAW,CAAC,gBAAgB,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAA;AA2D1E;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,GAChC,MAAM,OAAO,EACb,aAAa,MAAM,GAAG,SAAS,EAC/B,SAAS,MAAM,EACf,aAAa,MAAM,GAAG,YAAY,KACjC,MAAM,GAAG,SAsCX,CAAA"}
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { isXmlMediaType } from '@scalar/helpers/http/is-xml-media-type';
|
|
2
2
|
import { parseMimeType } from '@scalar/helpers/http/mime-type';
|
|
3
|
+
import { serializeXmlExample } from '@scalar/workspace-store/request-example';
|
|
4
|
+
import { coerceValue } from '@scalar/workspace-store/schemas/typebox-coerce';
|
|
5
|
+
import { SchemaObjectSchema } from '@scalar/workspace-store/schemas/v3.2/strict/openapi-document';
|
|
3
6
|
/**
|
|
4
7
|
* Whether a media type carries a single JSON document.
|
|
5
8
|
*
|
|
@@ -13,11 +16,6 @@ const isJsonDocumentContentType = (contentType) => {
|
|
|
13
16
|
const { subtype } = parseMimeType(contentType);
|
|
14
17
|
return subtype === 'json' || subtype.endsWith('+json');
|
|
15
18
|
};
|
|
16
|
-
/** Whether a media type carries XML, including suffixed types such as `application/xhtml+xml`. */
|
|
17
|
-
const isXmlContentType = (contentType) => {
|
|
18
|
-
const { subtype } = parseMimeType(contentType);
|
|
19
|
-
return subtype === 'xml' || subtype.endsWith('+xml');
|
|
20
|
-
};
|
|
21
19
|
/**
|
|
22
20
|
* How the resolved response schema describes the body: as a string, as something else, or not at all.
|
|
23
21
|
*
|
|
@@ -58,11 +56,17 @@ const isSerializedJsonDocument = (value) => {
|
|
|
58
56
|
* Returns `undefined` for an `undefined` body, mirroring `JSON.stringify`, so the caller can send an
|
|
59
57
|
* empty body rather than the characters `undefined`.
|
|
60
58
|
*/
|
|
61
|
-
export const serializeResponseBody = (body, contentType, schema) => {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
59
|
+
export const serializeResponseBody = (body, contentType, schema, provenance) => {
|
|
60
|
+
if (provenance === 'serialized' && typeof body === 'string') {
|
|
61
|
+
return body;
|
|
62
|
+
}
|
|
63
|
+
if (provenance === 'data' && isJsonDocumentContentType(contentType)) {
|
|
64
|
+
return JSON.stringify(body);
|
|
65
|
+
}
|
|
66
|
+
if (isXmlMediaType(contentType)) {
|
|
67
|
+
return typeof body === 'string'
|
|
68
|
+
? body
|
|
69
|
+
: serializeXmlExample(body, coerceValue(SchemaObjectSchema, schema ?? {}), { mode: 'read' }).xml;
|
|
66
70
|
}
|
|
67
71
|
if (typeof body === 'string') {
|
|
68
72
|
// Anywhere but a single JSON document, the characters are the payload: `text/plain`, `text/html`,
|
package/package.json
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"swagger",
|
|
17
17
|
"cli"
|
|
18
18
|
],
|
|
19
|
-
"version": "0.
|
|
19
|
+
"version": "0.17.1",
|
|
20
20
|
"engines": {
|
|
21
21
|
"node": ">=22"
|
|
22
22
|
},
|
|
@@ -53,12 +53,12 @@
|
|
|
53
53
|
"dependencies": {
|
|
54
54
|
"@faker-js/faker": "10.6.0",
|
|
55
55
|
"@hono/node-server": "^2.1.1",
|
|
56
|
-
"@scalar/helpers": "0.
|
|
57
|
-
"@scalar/json-magic": "0.15.
|
|
56
|
+
"@scalar/helpers": "0.16.0",
|
|
57
|
+
"@scalar/json-magic": "0.15.3",
|
|
58
58
|
"@scalar/openapi-types": "0.9.7",
|
|
59
|
-
"@scalar/openapi-upgrader": "0.
|
|
60
|
-
"@scalar/types": "0.22.
|
|
61
|
-
"@scalar/workspace-store": "0.
|
|
59
|
+
"@scalar/openapi-upgrader": "0.4.1",
|
|
60
|
+
"@scalar/types": "0.22.2",
|
|
61
|
+
"@scalar/workspace-store": "0.67.1",
|
|
62
62
|
"ajv": "^8.20.0",
|
|
63
63
|
"ajv-formats": "^3.0.1",
|
|
64
64
|
"hono": "^4.13.8",
|
|
@@ -69,7 +69,7 @@
|
|
|
69
69
|
"devDependencies": {
|
|
70
70
|
"@types/node": "^24.1.0",
|
|
71
71
|
"@types/ws": "8.18.1",
|
|
72
|
-
"undici": "7.
|
|
72
|
+
"undici": "7.29.0",
|
|
73
73
|
"vite": "8.1.5"
|
|
74
74
|
},
|
|
75
75
|
"scripts": {
|