@zuplo/cli 6.65.8 → 6.66.2
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/dist/__tests__/import-openapi.test.js +90 -1
- package/dist/__tests__/import-openapi.test.js.map +1 -1
- package/dist/open-api/convert/handler.js +2 -2
- package/dist/open-api/convert/handler.js.map +1 -1
- package/dist/open-api/merge/handler.d.ts +1 -1
- package/dist/open-api/merge/handler.d.ts.map +1 -1
- package/dist/open-api/merge/handler.js +4 -5
- package/dist/open-api/merge/handler.js.map +1 -1
- package/dist/open-api/overlay/handler.js +1 -2
- package/dist/open-api/overlay/handler.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +4 -4
- package/dist/__tests__/import-openapi-utils.test.d.ts +0 -2
- package/dist/__tests__/import-openapi-utils.test.d.ts.map +0 -1
- package/dist/__tests__/import-openapi-utils.test.js +0 -125
- package/dist/__tests__/import-openapi-utils.test.js.map +0 -1
- package/dist/__tests__/oas-test-data.d.ts +0 -98
- package/dist/__tests__/oas-test-data.d.ts.map +0 -1
- package/dist/__tests__/oas-test-data.js +0 -114
- package/dist/__tests__/oas-test-data.js.map +0 -1
- package/dist/common/file-format.d.ts +0 -25
- package/dist/common/file-format.d.ts.map +0 -1
- package/dist/common/file-format.js +0 -72
- package/dist/common/file-format.js.map +0 -1
- package/dist/common/open-api/constants.d.ts +0 -13
- package/dist/common/open-api/constants.d.ts.map +0 -1
- package/dist/common/open-api/constants.js +0 -16
- package/dist/common/open-api/constants.js.map +0 -1
- package/dist/common/open-api/index.d.ts +0 -3
- package/dist/common/open-api/index.d.ts.map +0 -1
- package/dist/common/open-api/index.js +0 -3
- package/dist/common/open-api/index.js.map +0 -1
- package/dist/common/open-api/validation.d.ts +0 -297
- package/dist/common/open-api/validation.d.ts.map +0 -1
- package/dist/common/open-api/validation.js +0 -88
- package/dist/common/open-api/validation.js.map +0 -1
- package/dist/open-api/convert/convert-engine.d.ts +0 -26
- package/dist/open-api/convert/convert-engine.d.ts.map +0 -1
- package/dist/open-api/convert/convert-engine.js +0 -20
- package/dist/open-api/convert/convert-engine.js.map +0 -1
- package/dist/open-api/convert/convert-engine.spec.d.ts +0 -2
- package/dist/open-api/convert/convert-engine.spec.d.ts.map +0 -1
- package/dist/open-api/convert/convert-engine.spec.js +0 -268
- package/dist/open-api/convert/convert-engine.spec.js.map +0 -1
- package/dist/open-api/merge/ajv.d.ts +0 -34
- package/dist/open-api/merge/ajv.d.ts.map +0 -1
- package/dist/open-api/merge/ajv.js +0 -2
- package/dist/open-api/merge/ajv.js.map +0 -1
- package/dist/open-api/merge/interfaces.d.ts +0 -72
- package/dist/open-api/merge/interfaces.d.ts.map +0 -1
- package/dist/open-api/merge/interfaces.js +0 -5
- package/dist/open-api/merge/interfaces.js.map +0 -1
- package/dist/open-api/merge/merge-engine.d.ts +0 -20
- package/dist/open-api/merge/merge-engine.d.ts.map +0 -1
- package/dist/open-api/merge/merge-engine.js +0 -34
- package/dist/open-api/merge/merge-engine.js.map +0 -1
- package/dist/open-api/merge/merge-engine.spec.d.ts +0 -2
- package/dist/open-api/merge/merge-engine.spec.d.ts.map +0 -1
- package/dist/open-api/merge/merge-engine.spec.js +0 -117
- package/dist/open-api/merge/merge-engine.spec.js.map +0 -1
- package/dist/open-api/merge/utils.d.ts +0 -11
- package/dist/open-api/merge/utils.d.ts.map +0 -1
- package/dist/open-api/merge/utils.js +0 -33
- package/dist/open-api/merge/utils.js.map +0 -1
- package/dist/open-api/overlay/overlay-engine.d.ts +0 -45
- package/dist/open-api/overlay/overlay-engine.d.ts.map +0 -1
- package/dist/open-api/overlay/overlay-engine.js +0 -309
- package/dist/open-api/overlay/overlay-engine.js.map +0 -1
- package/dist/open-api/overlay/overlay-engine.spec.d.ts +0 -2
- package/dist/open-api/overlay/overlay-engine.spec.d.ts.map +0 -1
- package/dist/open-api/overlay/overlay-engine.spec.js +0 -687
- package/dist/open-api/overlay/overlay-engine.spec.js.map +0 -1
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert";
|
|
2
|
-
import { describe, it } from "node:test";
|
|
3
|
-
import { detectFormat, detectFormatFromContent, guessFileNameFromUrl, isUrl, } from "./merge-engine.js";
|
|
4
|
-
describe("Merge Engine", () => {
|
|
5
|
-
describe("isUrl", () => {
|
|
6
|
-
it("should return true for valid HTTP URLs", () => {
|
|
7
|
-
assert.strictEqual(isUrl("http://example.com"), true);
|
|
8
|
-
assert.strictEqual(isUrl("http://example.com/api.json"), true);
|
|
9
|
-
assert.strictEqual(isUrl("http://api.example.com/v1/openapi.yaml"), true);
|
|
10
|
-
});
|
|
11
|
-
it("should return true for valid HTTPS URLs", () => {
|
|
12
|
-
assert.strictEqual(isUrl("https://example.com"), true);
|
|
13
|
-
assert.strictEqual(isUrl("https://example.com/api.json"), true);
|
|
14
|
-
assert.strictEqual(isUrl("https://api.example.com/v1/openapi.yaml"), true);
|
|
15
|
-
});
|
|
16
|
-
it("should return true for URLs with query parameters", () => {
|
|
17
|
-
assert.strictEqual(isUrl("https://example.com/api?version=1.0"), true);
|
|
18
|
-
assert.strictEqual(isUrl("https://example.com/api?format=json&version=2"), true);
|
|
19
|
-
});
|
|
20
|
-
it("should return true for URLs with fragments", () => {
|
|
21
|
-
assert.strictEqual(isUrl("https://example.com/api#section"), true);
|
|
22
|
-
assert.strictEqual(isUrl("https://example.com/api.json#definitions"), true);
|
|
23
|
-
});
|
|
24
|
-
it("should return true for URLs with ports", () => {
|
|
25
|
-
assert.strictEqual(isUrl("https://example.com:8080/api"), true);
|
|
26
|
-
assert.strictEqual(isUrl("http://localhost:3000/openapi.json"), true);
|
|
27
|
-
});
|
|
28
|
-
it("should return false for relative file paths", () => {
|
|
29
|
-
assert.strictEqual(isUrl("./api.json"), false);
|
|
30
|
-
assert.strictEqual(isUrl("../specs/openapi.yaml"), false);
|
|
31
|
-
assert.strictEqual(isUrl("docs/api.json"), false);
|
|
32
|
-
});
|
|
33
|
-
it("should return false for absolute file paths", () => {
|
|
34
|
-
assert.strictEqual(isUrl("/path/to/api.json"), false);
|
|
35
|
-
assert.strictEqual(isUrl("/Users/name/docs/openapi.yaml"), false);
|
|
36
|
-
});
|
|
37
|
-
it("should return false for filenames without paths", () => {
|
|
38
|
-
assert.strictEqual(isUrl("api.json"), false);
|
|
39
|
-
assert.strictEqual(isUrl("openapi.yaml"), false);
|
|
40
|
-
});
|
|
41
|
-
it("should return false for empty strings", () => {
|
|
42
|
-
assert.strictEqual(isUrl(""), false);
|
|
43
|
-
});
|
|
44
|
-
});
|
|
45
|
-
describe("guessFileNameFromUrl", () => {
|
|
46
|
-
it("should extract filename from URL path", () => {
|
|
47
|
-
assert.strictEqual(guessFileNameFromUrl("https://example.com/api.json"), "api");
|
|
48
|
-
assert.strictEqual(guessFileNameFromUrl("https://example.com/openapi.yaml"), "openapi");
|
|
49
|
-
});
|
|
50
|
-
it("should extract filename from URL with multiple path segments", () => {
|
|
51
|
-
assert.strictEqual(guessFileNameFromUrl("https://example.com/v1/specs/api.json"), "api");
|
|
52
|
-
assert.strictEqual(guessFileNameFromUrl("https://api.example.com/docs/v2/openapi.yaml"), "openapi");
|
|
53
|
-
});
|
|
54
|
-
it("should extract filename from URL with query parameters", () => {
|
|
55
|
-
assert.strictEqual(guessFileNameFromUrl("https://example.com/api.json?version=1"), "api");
|
|
56
|
-
assert.strictEqual(guessFileNameFromUrl("https://example.com/openapi.yaml?format=json&v=2"), "openapi");
|
|
57
|
-
});
|
|
58
|
-
it("should extract filename from URL with fragments", () => {
|
|
59
|
-
assert.strictEqual(guessFileNameFromUrl("https://example.com/api.json#section"), "api");
|
|
60
|
-
assert.strictEqual(guessFileNameFromUrl("https://example.com/spec.yaml#definitions"), "spec");
|
|
61
|
-
});
|
|
62
|
-
it("should handle URLs with no file extension", () => {
|
|
63
|
-
assert.strictEqual(guessFileNameFromUrl("https://example.com/api"), "api");
|
|
64
|
-
assert.strictEqual(guessFileNameFromUrl("https://example.com/v1/openapi"), "openapi");
|
|
65
|
-
});
|
|
66
|
-
it("should handle URLs ending with slash", () => {
|
|
67
|
-
const result = guessFileNameFromUrl("https://example.com/api/");
|
|
68
|
-
assert.strictEqual(result.startsWith("imported-"), true);
|
|
69
|
-
});
|
|
70
|
-
it("should generate base64 name for root URLs", () => {
|
|
71
|
-
const result = guessFileNameFromUrl("https://example.com/");
|
|
72
|
-
assert.strictEqual(result.startsWith("imported-"), true);
|
|
73
|
-
assert.strictEqual(result.length > 9, true);
|
|
74
|
-
});
|
|
75
|
-
it("should handle filenames with multiple dots", () => {
|
|
76
|
-
assert.strictEqual(guessFileNameFromUrl("https://example.com/api.v1.0.json"), "api.v1.0");
|
|
77
|
-
assert.strictEqual(guessFileNameFromUrl("https://example.com/my.api.spec.yaml"), "my.api.spec");
|
|
78
|
-
});
|
|
79
|
-
it("should handle complex URLs", () => {
|
|
80
|
-
assert.strictEqual(guessFileNameFromUrl("https://api.example.com:8080/v2/specs/openapi.json?auth=token#info"), "openapi");
|
|
81
|
-
});
|
|
82
|
-
it("should generate consistent base64 names for same URL", () => {
|
|
83
|
-
const url = "https://example.com/";
|
|
84
|
-
const name1 = guessFileNameFromUrl(url);
|
|
85
|
-
const name2 = guessFileNameFromUrl(url);
|
|
86
|
-
assert.strictEqual(name1, name2);
|
|
87
|
-
});
|
|
88
|
-
});
|
|
89
|
-
describe("detectFormat (re-exported from common)", () => {
|
|
90
|
-
it("should detect JSON format", () => {
|
|
91
|
-
assert.strictEqual(detectFormat("api.json"), "json");
|
|
92
|
-
assert.strictEqual(detectFormat("/path/to/api.json"), "json");
|
|
93
|
-
});
|
|
94
|
-
it("should detect YAML format", () => {
|
|
95
|
-
assert.strictEqual(detectFormat("api.yaml"), "yaml");
|
|
96
|
-
assert.strictEqual(detectFormat("api.yml"), "yaml");
|
|
97
|
-
});
|
|
98
|
-
it("should return null for unknown extensions", () => {
|
|
99
|
-
assert.strictEqual(detectFormat("api.txt"), null);
|
|
100
|
-
});
|
|
101
|
-
});
|
|
102
|
-
describe("detectFormatFromContent (re-exported from common)", () => {
|
|
103
|
-
it("should detect JSON content", () => {
|
|
104
|
-
const content = '{"openapi": "3.1.0"}';
|
|
105
|
-
assert.strictEqual(detectFormatFromContent(content), "json");
|
|
106
|
-
});
|
|
107
|
-
it("should detect YAML content", () => {
|
|
108
|
-
const content = "openapi: 3.1.0\ninfo:\n title: Test";
|
|
109
|
-
assert.strictEqual(detectFormatFromContent(content), "yaml");
|
|
110
|
-
});
|
|
111
|
-
it("should throw error for invalid content", () => {
|
|
112
|
-
const content = "not valid json or yaml: : :";
|
|
113
|
-
assert.throws(() => detectFormatFromContent(content), /Failed to parse content/);
|
|
114
|
-
});
|
|
115
|
-
});
|
|
116
|
-
});
|
|
117
|
-
//# sourceMappingURL=merge-engine.spec.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"merge-engine.spec.js","sourceRoot":"","sources":["../../../src/open-api/merge/merge-engine.spec.ts"],"names":[],"mappings":"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","sourcesContent":["import assert from \"node:assert\";\nimport { describe, it } from \"node:test\";\nimport {\n detectFormat,\n detectFormatFromContent,\n guessFileNameFromUrl,\n isUrl,\n} from \"./merge-engine.js\";\n\ndescribe(\"Merge Engine\", () => {\n describe(\"isUrl\", () => {\n it(\"should return true for valid HTTP URLs\", () => {\n assert.strictEqual(isUrl(\"http://example.com\"), true);\n assert.strictEqual(isUrl(\"http://example.com/api.json\"), true);\n assert.strictEqual(isUrl(\"http://api.example.com/v1/openapi.yaml\"), true);\n });\n\n it(\"should return true for valid HTTPS URLs\", () => {\n assert.strictEqual(isUrl(\"https://example.com\"), true);\n assert.strictEqual(isUrl(\"https://example.com/api.json\"), true);\n assert.strictEqual(\n isUrl(\"https://api.example.com/v1/openapi.yaml\"),\n true\n );\n });\n\n it(\"should return true for URLs with query parameters\", () => {\n assert.strictEqual(isUrl(\"https://example.com/api?version=1.0\"), true);\n assert.strictEqual(\n isUrl(\"https://example.com/api?format=json&version=2\"),\n true\n );\n });\n\n it(\"should return true for URLs with fragments\", () => {\n assert.strictEqual(isUrl(\"https://example.com/api#section\"), true);\n assert.strictEqual(\n isUrl(\"https://example.com/api.json#definitions\"),\n true\n );\n });\n\n it(\"should return true for URLs with ports\", () => {\n assert.strictEqual(isUrl(\"https://example.com:8080/api\"), true);\n assert.strictEqual(isUrl(\"http://localhost:3000/openapi.json\"), true);\n });\n\n it(\"should return false for relative file paths\", () => {\n assert.strictEqual(isUrl(\"./api.json\"), false);\n assert.strictEqual(isUrl(\"../specs/openapi.yaml\"), false);\n assert.strictEqual(isUrl(\"docs/api.json\"), false);\n });\n\n it(\"should return false for absolute file paths\", () => {\n assert.strictEqual(isUrl(\"/path/to/api.json\"), false);\n assert.strictEqual(isUrl(\"/Users/name/docs/openapi.yaml\"), false);\n // Windows absolute paths with drive letters may be interpreted as URLs with protocol\n // This is expected behavior from the URL constructor\n });\n\n it(\"should return false for filenames without paths\", () => {\n assert.strictEqual(isUrl(\"api.json\"), false);\n assert.strictEqual(isUrl(\"openapi.yaml\"), false);\n });\n\n it(\"should return false for empty strings\", () => {\n assert.strictEqual(isUrl(\"\"), false);\n });\n });\n\n describe(\"guessFileNameFromUrl\", () => {\n it(\"should extract filename from URL path\", () => {\n assert.strictEqual(\n guessFileNameFromUrl(\"https://example.com/api.json\"),\n \"api\"\n );\n assert.strictEqual(\n guessFileNameFromUrl(\"https://example.com/openapi.yaml\"),\n \"openapi\"\n );\n });\n\n it(\"should extract filename from URL with multiple path segments\", () => {\n assert.strictEqual(\n guessFileNameFromUrl(\"https://example.com/v1/specs/api.json\"),\n \"api\"\n );\n assert.strictEqual(\n guessFileNameFromUrl(\"https://api.example.com/docs/v2/openapi.yaml\"),\n \"openapi\"\n );\n });\n\n it(\"should extract filename from URL with query parameters\", () => {\n assert.strictEqual(\n guessFileNameFromUrl(\"https://example.com/api.json?version=1\"),\n \"api\"\n );\n assert.strictEqual(\n guessFileNameFromUrl(\n \"https://example.com/openapi.yaml?format=json&v=2\"\n ),\n \"openapi\"\n );\n });\n\n it(\"should extract filename from URL with fragments\", () => {\n assert.strictEqual(\n guessFileNameFromUrl(\"https://example.com/api.json#section\"),\n \"api\"\n );\n assert.strictEqual(\n guessFileNameFromUrl(\"https://example.com/spec.yaml#definitions\"),\n \"spec\"\n );\n });\n\n it(\"should handle URLs with no file extension\", () => {\n assert.strictEqual(\n guessFileNameFromUrl(\"https://example.com/api\"),\n \"api\"\n );\n assert.strictEqual(\n guessFileNameFromUrl(\"https://example.com/v1/openapi\"),\n \"openapi\"\n );\n });\n\n it(\"should handle URLs ending with slash\", () => {\n // When URL ends with slash, pop() returns empty string, should use base64\n const result = guessFileNameFromUrl(\"https://example.com/api/\");\n assert.strictEqual(result.startsWith(\"imported-\"), true);\n });\n\n it(\"should generate base64 name for root URLs\", () => {\n const result = guessFileNameFromUrl(\"https://example.com/\");\n assert.strictEqual(result.startsWith(\"imported-\"), true);\n assert.strictEqual(result.length > 9, true); // \"imported-\" is 9 chars\n });\n\n it(\"should handle filenames with multiple dots\", () => {\n assert.strictEqual(\n guessFileNameFromUrl(\"https://example.com/api.v1.0.json\"),\n \"api.v1.0\"\n );\n assert.strictEqual(\n guessFileNameFromUrl(\"https://example.com/my.api.spec.yaml\"),\n \"my.api.spec\"\n );\n });\n\n it(\"should handle complex URLs\", () => {\n assert.strictEqual(\n guessFileNameFromUrl(\n \"https://api.example.com:8080/v2/specs/openapi.json?auth=token#info\"\n ),\n \"openapi\"\n );\n });\n\n it(\"should generate consistent base64 names for same URL\", () => {\n const url = \"https://example.com/\";\n const name1 = guessFileNameFromUrl(url);\n const name2 = guessFileNameFromUrl(url);\n assert.strictEqual(name1, name2);\n });\n });\n\n describe(\"detectFormat (re-exported from common)\", () => {\n it(\"should detect JSON format\", () => {\n assert.strictEqual(detectFormat(\"api.json\"), \"json\");\n assert.strictEqual(detectFormat(\"/path/to/api.json\"), \"json\");\n });\n\n it(\"should detect YAML format\", () => {\n assert.strictEqual(detectFormat(\"api.yaml\"), \"yaml\");\n assert.strictEqual(detectFormat(\"api.yml\"), \"yaml\");\n });\n\n it(\"should return null for unknown extensions\", () => {\n assert.strictEqual(detectFormat(\"api.txt\"), null);\n });\n });\n\n describe(\"detectFormatFromContent (re-exported from common)\", () => {\n it(\"should detect JSON content\", () => {\n const content = '{\"openapi\": \"3.1.0\"}';\n assert.strictEqual(detectFormatFromContent(content), \"json\");\n });\n\n it(\"should detect YAML content\", () => {\n const content = \"openapi: 3.1.0\\ninfo:\\n title: Test\";\n assert.strictEqual(detectFormatFromContent(content), \"yaml\");\n });\n\n it(\"should throw error for invalid content\", () => {\n const content = \"not valid json or yaml: : :\";\n assert.throws(\n () => detectFormatFromContent(content),\n /Failed to parse content/\n );\n });\n });\n});\n"]}
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { OpenAPIV3_1 } from "openapi-types";
|
|
2
|
-
import { ZuploOpenApiDocument } from "./interfaces.js";
|
|
3
|
-
export declare const OPERATION_PATH_MERGE_DELIMITER = ">";
|
|
4
|
-
export declare const parseOpenApiFile: (
|
|
5
|
-
extName: string,
|
|
6
|
-
fileText: string
|
|
7
|
-
) => Promise<ZuploOpenApiDocument | OpenAPIV3_1.Document<{}>>;
|
|
8
|
-
export declare const addOperationIdsAsNecessary: (
|
|
9
|
-
openApi: OpenAPIV3_1.Document | ZuploOpenApiDocument
|
|
10
|
-
) => void;
|
|
11
|
-
//# sourceMappingURL=utils.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../src/open-api/merge/utils.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAM5C,OAAO,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAEvD,eAAO,MAAM,8BAA8B,MAAM,CAAC;AAElD,eAAO,MAAM,gBAAgB,GAAU,SAAS,MAAM,EAAE,UAAU,MAAM,6DAYvE,CAAC;AAMF,eAAO,MAAM,0BAA0B,GACrC,SAAS,WAAW,CAAC,QAAQ,GAAG,oBAAoB,SAmBrD,CAAC"}
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import yaml from "js-yaml";
|
|
2
|
-
import { v4 } from "uuid";
|
|
3
|
-
import { HTTP_METHODS, OPEN_API_FILE_TYPES, } from "../../common/open-api/index.js";
|
|
4
|
-
export const OPERATION_PATH_MERGE_DELIMITER = ">";
|
|
5
|
-
export const parseOpenApiFile = async (extName, fileText) => {
|
|
6
|
-
if (!OPEN_API_FILE_TYPES.includes(extName)) {
|
|
7
|
-
throw new Error(`Invalid file type. Supported file types are: ${OPEN_API_FILE_TYPES.join(", ")}`);
|
|
8
|
-
}
|
|
9
|
-
if (extName.includes("json")) {
|
|
10
|
-
return JSON.parse(fileText);
|
|
11
|
-
}
|
|
12
|
-
return yaml.load(fileText);
|
|
13
|
-
};
|
|
14
|
-
export const addOperationIdsAsNecessary = (openApi) => {
|
|
15
|
-
const paths = openApi.paths;
|
|
16
|
-
if (!paths) {
|
|
17
|
-
return;
|
|
18
|
-
}
|
|
19
|
-
for (const path of Object.keys(paths)) {
|
|
20
|
-
const pathItem = paths[path];
|
|
21
|
-
if (!pathItem) {
|
|
22
|
-
continue;
|
|
23
|
-
}
|
|
24
|
-
const methods = HTTP_METHODS;
|
|
25
|
-
for (const method of methods) {
|
|
26
|
-
const operation = pathItem[method];
|
|
27
|
-
if (operation && !operation.operationId) {
|
|
28
|
-
operation.operationId = v4();
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
};
|
|
33
|
-
//# sourceMappingURL=utils.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../../../src/open-api/merge/utils.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,SAAS,CAAC;AAE3B,OAAO,EAAE,EAAE,EAAE,MAAM,MAAM,CAAC;AAC1B,OAAO,EACL,YAAY,EACZ,mBAAmB,GACpB,MAAM,gCAAgC,CAAC;AAGxC,MAAM,CAAC,MAAM,8BAA8B,GAAG,GAAG,CAAC;AAElD,MAAM,CAAC,MAAM,gBAAgB,GAAG,KAAK,EAAE,OAAe,EAAE,QAAgB,EAAE,EAAE;IAC1E,IAAI,CAAC,mBAAmB,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;QAC3C,MAAM,IAAI,KAAK,CACb,gDAAgD,mBAAmB,CAAC,IAAI,CACtE,IAAI,CACL,EAAE,CACJ,CAAC;IACJ,CAAC;IACD,IAAI,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;QAC7B,OAAO,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAgD,CAAC;IAC7E,CAAC;IACD,OAAO,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAgD,CAAC;AAC5E,CAAC,CAAC;AAMF,MAAM,CAAC,MAAM,0BAA0B,GAAG,CACxC,OAAoD,EACpD,EAAE;IACF,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC;IAC5B,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAO;IACT,CAAC;IACD,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QACtC,MAAM,QAAQ,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC;QAC7B,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,SAAS;QACX,CAAC;QACD,MAAM,OAAO,GAAG,YAAoD,CAAC;QACrE,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;YAC7B,MAAM,SAAS,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC;YACnC,IAAI,SAAS,IAAI,CAAC,SAAS,CAAC,WAAW,EAAE,CAAC;gBACxC,SAAS,CAAC,WAAW,GAAG,EAAE,EAAE,CAAC;YAC/B,CAAC;QACH,CAAC;IACH,CAAC;AACH,CAAC,CAAC","sourcesContent":["import yaml from \"js-yaml\";\nimport { OpenAPIV3_1 } from \"openapi-types\";\nimport { v4 } from \"uuid\";\nimport {\n HTTP_METHODS,\n OPEN_API_FILE_TYPES,\n} from \"../../common/open-api/index.js\";\nimport { ZuploOpenApiDocument } from \"./interfaces.js\";\n\nexport const OPERATION_PATH_MERGE_DELIMITER = \">\";\n\nexport const parseOpenApiFile = async (extName: string, fileText: string) => {\n if (!OPEN_API_FILE_TYPES.includes(extName)) {\n throw new Error(\n `Invalid file type. Supported file types are: ${OPEN_API_FILE_TYPES.join(\n \", \"\n )}`\n );\n }\n if (extName.includes(\"json\")) {\n return JSON.parse(fileText) as OpenAPIV3_1.Document | ZuploOpenApiDocument;\n }\n return yaml.load(fileText) as OpenAPIV3_1.Document | ZuploOpenApiDocument;\n};\n\n/**\n * Add operation ID to any operations that don't have one, this makes\n * onboarding smoother for new users\n */\nexport const addOperationIdsAsNecessary = (\n openApi: OpenAPIV3_1.Document | ZuploOpenApiDocument\n) => {\n const paths = openApi.paths;\n if (!paths) {\n return;\n }\n for (const path of Object.keys(paths)) {\n const pathItem = paths[path];\n if (!pathItem) {\n continue;\n }\n const methods = HTTP_METHODS as unknown as OpenAPIV3_1.HttpMethods[];\n for (const method of methods) {\n const operation = pathItem[method];\n if (operation && !operation.operationId) {\n operation.operationId = v4();\n }\n }\n }\n};\n"]}
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
type OverlayAction,
|
|
3
|
-
type OverlayDocument,
|
|
4
|
-
type RemoveCondition,
|
|
5
|
-
} from "../../common/open-api/index.js";
|
|
6
|
-
export interface ApplyResult {
|
|
7
|
-
applied: boolean;
|
|
8
|
-
count: number;
|
|
9
|
-
}
|
|
10
|
-
export interface OverlayStats {
|
|
11
|
-
applied: number;
|
|
12
|
-
skipped: number;
|
|
13
|
-
totalNodes: number;
|
|
14
|
-
}
|
|
15
|
-
export declare function deepClone<T>(obj: T): T;
|
|
16
|
-
export declare function deepMerge(target: unknown, source: unknown): unknown;
|
|
17
|
-
export declare function checkCondition(
|
|
18
|
-
value: unknown,
|
|
19
|
-
condition: RemoveCondition
|
|
20
|
-
): boolean;
|
|
21
|
-
export declare function setValueAtPath(
|
|
22
|
-
doc: unknown,
|
|
23
|
-
target: string,
|
|
24
|
-
value: unknown
|
|
25
|
-
): void;
|
|
26
|
-
export declare function applyAction(
|
|
27
|
-
doc: unknown,
|
|
28
|
-
action: OverlayAction
|
|
29
|
-
): ApplyResult;
|
|
30
|
-
export declare function extractPathOrder(overlay: OverlayDocument): string[];
|
|
31
|
-
export declare function reorderOperationProperties(
|
|
32
|
-
operation: unknown
|
|
33
|
-
): Record<string, unknown>;
|
|
34
|
-
export declare function reorderPaths<T>(doc: T, pathOrder: string[]): T;
|
|
35
|
-
export declare function reorderDocumentProperties(
|
|
36
|
-
doc: unknown
|
|
37
|
-
): Record<string, unknown>;
|
|
38
|
-
export declare function applyOverlay(
|
|
39
|
-
openapi: unknown,
|
|
40
|
-
overlay: unknown
|
|
41
|
-
): {
|
|
42
|
-
result: Record<string, unknown>;
|
|
43
|
-
stats: OverlayStats;
|
|
44
|
-
};
|
|
45
|
-
//# sourceMappingURL=overlay-engine.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"overlay-engine.d.ts","sourceRoot":"","sources":["../../../src/open-api/overlay/overlay-engine.ts"],"names":[],"mappings":"AACA,OAAO,EAEL,KAAK,aAAa,EAClB,KAAK,eAAe,EACpB,KAAK,eAAe,EAGrB,MAAM,gCAAgC,CAAC;AAExC,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;CACpB;AAmBD,wBAAgB,SAAS,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,CAKtC;AAQD,wBAAgB,SAAS,CAAC,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,GAAG,OAAO,CA+DnE;AAKD,wBAAgB,cAAc,CAC5B,KAAK,EAAE,OAAO,EACd,SAAS,EAAE,eAAe,GACzB,OAAO,CAgCT;AAyCD,wBAAgB,cAAc,CAC5B,GAAG,EAAE,OAAO,EACZ,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,OAAO,GACb,IAAI,CAyCN;AAKD,wBAAgB,WAAW,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,aAAa,GAAG,WAAW,CA2F5E;AAMD,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,eAAe,GAAG,MAAM,EAAE,CAiBnE;AAMD,wBAAgB,0BAA0B,CACxC,SAAS,EAAE,OAAO,GACjB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAoBzB;AAMD,wBAAgB,YAAY,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,CAAC,CAkD9D;AAOD,wBAAgB,yBAAyB,CACvC,GAAG,EAAE,OAAO,GACX,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAoBzB;AAmBD,wBAAgB,YAAY,CAC1B,OAAO,EAAE,OAAO,EAChB,OAAO,EAAE,OAAO,GACf;IAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAAC,KAAK,EAAE,YAAY,CAAA;CAAE,CA6B1D"}
|
|
@@ -1,309 +0,0 @@
|
|
|
1
|
-
import { JSONPath } from "jsonpath-plus";
|
|
2
|
-
import { HTTP_METHODS, validateOpenApiDocument, validateOverlayDocument, } from "../../common/open-api/index.js";
|
|
3
|
-
function toRecord(value, context) {
|
|
4
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
5
|
-
throw new Error(`Expected object at ${context}, but got ${Array.isArray(value) ? "array" : typeof value}`);
|
|
6
|
-
}
|
|
7
|
-
return value;
|
|
8
|
-
}
|
|
9
|
-
export function deepClone(obj) {
|
|
10
|
-
if (typeof structuredClone === "function") {
|
|
11
|
-
return structuredClone(obj);
|
|
12
|
-
}
|
|
13
|
-
return JSON.parse(JSON.stringify(obj));
|
|
14
|
-
}
|
|
15
|
-
export function deepMerge(target, source) {
|
|
16
|
-
if (!source || typeof source !== "object") {
|
|
17
|
-
return source;
|
|
18
|
-
}
|
|
19
|
-
if (!target || typeof target !== "object") {
|
|
20
|
-
return deepClone(source);
|
|
21
|
-
}
|
|
22
|
-
if (Array.isArray(source)) {
|
|
23
|
-
if (!Array.isArray(target)) {
|
|
24
|
-
return deepClone(source);
|
|
25
|
-
}
|
|
26
|
-
if (source.length > 0 &&
|
|
27
|
-
typeof source[0] === "object" &&
|
|
28
|
-
source[0] !== null &&
|
|
29
|
-
"name" in source[0] &&
|
|
30
|
-
"in" in source[0]) {
|
|
31
|
-
const merged = [...target];
|
|
32
|
-
for (const sourceParam of source) {
|
|
33
|
-
if (typeof sourceParam !== "object" || sourceParam === null)
|
|
34
|
-
continue;
|
|
35
|
-
const existingIndex = merged.findIndex((p) => typeof p === "object" &&
|
|
36
|
-
p !== null &&
|
|
37
|
-
"name" in p &&
|
|
38
|
-
"in" in p &&
|
|
39
|
-
"name" in sourceParam &&
|
|
40
|
-
"in" in sourceParam &&
|
|
41
|
-
p.name === sourceParam.name &&
|
|
42
|
-
p.in === sourceParam.in);
|
|
43
|
-
if (existingIndex >= 0) {
|
|
44
|
-
merged[existingIndex] = deepMerge(merged[existingIndex], sourceParam);
|
|
45
|
-
}
|
|
46
|
-
else {
|
|
47
|
-
merged.push(deepClone(sourceParam));
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
return merged;
|
|
51
|
-
}
|
|
52
|
-
return [...target, ...deepClone(source)];
|
|
53
|
-
}
|
|
54
|
-
const targetObj = target;
|
|
55
|
-
const sourceObj = source;
|
|
56
|
-
const result = { ...targetObj };
|
|
57
|
-
for (const [key, value] of Object.entries(sourceObj)) {
|
|
58
|
-
if (key in result) {
|
|
59
|
-
result[key] = deepMerge(result[key], value);
|
|
60
|
-
}
|
|
61
|
-
else {
|
|
62
|
-
result[key] = deepClone(value);
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
return result;
|
|
66
|
-
}
|
|
67
|
-
export function checkCondition(value, condition) {
|
|
68
|
-
if (condition.empty) {
|
|
69
|
-
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
70
|
-
return Object.keys(value).length === 0;
|
|
71
|
-
}
|
|
72
|
-
if (Array.isArray(value)) {
|
|
73
|
-
return value.length === 0;
|
|
74
|
-
}
|
|
75
|
-
return false;
|
|
76
|
-
}
|
|
77
|
-
if (condition.missing) {
|
|
78
|
-
const parts = condition.missing.split(".");
|
|
79
|
-
let current = value;
|
|
80
|
-
for (const part of parts) {
|
|
81
|
-
if (current &&
|
|
82
|
-
typeof current === "object" &&
|
|
83
|
-
!Array.isArray(current) &&
|
|
84
|
-
part in current) {
|
|
85
|
-
current = current[part];
|
|
86
|
-
}
|
|
87
|
-
else {
|
|
88
|
-
return true;
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
return false;
|
|
92
|
-
}
|
|
93
|
-
return false;
|
|
94
|
-
}
|
|
95
|
-
const JSONPATH_SEGMENT_PATTERN = /[^.[]+|\[[^\]]+\]/g;
|
|
96
|
-
function extractBracketKey(bracketNotation) {
|
|
97
|
-
const inner = bracketNotation.slice(1, -1);
|
|
98
|
-
if (inner.startsWith("'") && inner.endsWith("'")) {
|
|
99
|
-
return inner.slice(1, -1);
|
|
100
|
-
}
|
|
101
|
-
if (inner.startsWith('"') && inner.endsWith('"')) {
|
|
102
|
-
return inner.slice(1, -1);
|
|
103
|
-
}
|
|
104
|
-
return inner;
|
|
105
|
-
}
|
|
106
|
-
export function setValueAtPath(doc, target, value) {
|
|
107
|
-
const targetPath = target.replace(/^\$\./, "");
|
|
108
|
-
const parts = targetPath.match(JSONPATH_SEGMENT_PATTERN) || [];
|
|
109
|
-
let current = toRecord(doc, "setValueAtPath document");
|
|
110
|
-
for (let i = 0; i < parts.length - 1; i++) {
|
|
111
|
-
const part = parts[i];
|
|
112
|
-
if (part.startsWith("[")) {
|
|
113
|
-
const key = extractBracketKey(part);
|
|
114
|
-
if (!(key in current)) {
|
|
115
|
-
current[key] = {};
|
|
116
|
-
}
|
|
117
|
-
const next = current[key];
|
|
118
|
-
current = toRecord(next, `setValueAtPath at ${target}[${i}]`);
|
|
119
|
-
}
|
|
120
|
-
else {
|
|
121
|
-
if (!(part in current)) {
|
|
122
|
-
current[part] = {};
|
|
123
|
-
}
|
|
124
|
-
const next = current[part];
|
|
125
|
-
current = toRecord(next, `setValueAtPath at ${target}.${part}`);
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
const lastPart = parts[parts.length - 1];
|
|
129
|
-
if (lastPart.startsWith("[")) {
|
|
130
|
-
const key = extractBracketKey(lastPart);
|
|
131
|
-
current[key] = value;
|
|
132
|
-
}
|
|
133
|
-
else {
|
|
134
|
-
current[lastPart] = value;
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
export function applyAction(doc, action) {
|
|
138
|
-
const docRecord = toRecord(doc, "applyAction document");
|
|
139
|
-
if (action.update) {
|
|
140
|
-
if (action.target === "$") {
|
|
141
|
-
Object.assign(docRecord, deepMerge(doc, action.update));
|
|
142
|
-
return { applied: true, count: 1 };
|
|
143
|
-
}
|
|
144
|
-
try {
|
|
145
|
-
const results = JSONPath({
|
|
146
|
-
path: action.target,
|
|
147
|
-
json: doc,
|
|
148
|
-
resultType: "all",
|
|
149
|
-
});
|
|
150
|
-
if (results.length > 0) {
|
|
151
|
-
let count = 0;
|
|
152
|
-
for (const result of results) {
|
|
153
|
-
const { parent, parentProperty } = result;
|
|
154
|
-
if (parent &&
|
|
155
|
-
typeof parent === "object" &&
|
|
156
|
-
!Array.isArray(parent) &&
|
|
157
|
-
parentProperty !== undefined) {
|
|
158
|
-
const parentRecord = parent;
|
|
159
|
-
parentRecord[parentProperty] = deepMerge(parentRecord[parentProperty], action.update);
|
|
160
|
-
count++;
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
return { applied: true, count };
|
|
164
|
-
}
|
|
165
|
-
setValueAtPath(doc, action.target, action.update);
|
|
166
|
-
return { applied: true, count: 1 };
|
|
167
|
-
}
|
|
168
|
-
catch (cause) {
|
|
169
|
-
throw new Error("Failed to apply update", { cause });
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
else if (action.remove) {
|
|
173
|
-
try {
|
|
174
|
-
const results = JSONPath({
|
|
175
|
-
path: action.target,
|
|
176
|
-
json: doc,
|
|
177
|
-
resultType: "all",
|
|
178
|
-
});
|
|
179
|
-
if (results.length === 0) {
|
|
180
|
-
return { applied: false, count: 0 };
|
|
181
|
-
}
|
|
182
|
-
let count = 0;
|
|
183
|
-
for (let i = results.length - 1; i >= 0; i--) {
|
|
184
|
-
const result = results[i];
|
|
185
|
-
const { parent, parentProperty, value } = result;
|
|
186
|
-
const shouldRemove = typeof action.remove === "object"
|
|
187
|
-
? checkCondition(value, action.remove)
|
|
188
|
-
: true;
|
|
189
|
-
if (shouldRemove && parent && parentProperty !== undefined) {
|
|
190
|
-
if (Array.isArray(parent)) {
|
|
191
|
-
parent.splice(parentProperty, 1);
|
|
192
|
-
}
|
|
193
|
-
else if (typeof parent === "object" && parent !== null) {
|
|
194
|
-
delete parent[parentProperty];
|
|
195
|
-
}
|
|
196
|
-
count++;
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
return { applied: true, count };
|
|
200
|
-
}
|
|
201
|
-
catch (cause) {
|
|
202
|
-
throw new Error("Failed to remove", { cause });
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
return { applied: false, count: 0 };
|
|
206
|
-
}
|
|
207
|
-
export function extractPathOrder(overlay) {
|
|
208
|
-
const pathOrder = [];
|
|
209
|
-
const seenPaths = new Set();
|
|
210
|
-
for (const action of overlay.actions) {
|
|
211
|
-
const match = action.target.match(/^\$\.paths\['([^']+)'\]/);
|
|
212
|
-
if (match) {
|
|
213
|
-
const targetPath = match[1];
|
|
214
|
-
if (!seenPaths.has(targetPath)) {
|
|
215
|
-
pathOrder.push(targetPath);
|
|
216
|
-
seenPaths.add(targetPath);
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
return pathOrder;
|
|
221
|
-
}
|
|
222
|
-
export function reorderOperationProperties(operation) {
|
|
223
|
-
const operationObj = toRecord(operation, "reorderOperationProperties");
|
|
224
|
-
const priorityKeys = ["summary", "description", "operationId"];
|
|
225
|
-
const reordered = {};
|
|
226
|
-
for (const key of priorityKeys) {
|
|
227
|
-
if (key in operationObj) {
|
|
228
|
-
reordered[key] = operationObj[key];
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
for (const [key, value] of Object.entries(operationObj)) {
|
|
232
|
-
if (!priorityKeys.includes(key)) {
|
|
233
|
-
reordered[key] = value;
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
return reordered;
|
|
237
|
-
}
|
|
238
|
-
export function reorderPaths(doc, pathOrder) {
|
|
239
|
-
if (typeof doc !== "object" ||
|
|
240
|
-
doc === null ||
|
|
241
|
-
!("paths" in doc) ||
|
|
242
|
-
pathOrder.length === 0) {
|
|
243
|
-
return doc;
|
|
244
|
-
}
|
|
245
|
-
const docRecord = doc;
|
|
246
|
-
const originalPaths = toRecord(docRecord.paths, "paths");
|
|
247
|
-
const reorderedPaths = {};
|
|
248
|
-
for (const targetPath of pathOrder) {
|
|
249
|
-
if (targetPath in originalPaths) {
|
|
250
|
-
reorderedPaths[targetPath] = originalPaths[targetPath];
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
for (const [targetPath, value] of Object.entries(originalPaths)) {
|
|
254
|
-
if (!(targetPath in reorderedPaths)) {
|
|
255
|
-
reorderedPaths[targetPath] = value;
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
for (const pathItem of Object.values(reorderedPaths)) {
|
|
259
|
-
if (typeof pathItem !== "object" || pathItem === null)
|
|
260
|
-
continue;
|
|
261
|
-
const pathItemRecord = pathItem;
|
|
262
|
-
for (const method of HTTP_METHODS) {
|
|
263
|
-
if (method in pathItemRecord &&
|
|
264
|
-
typeof pathItemRecord[method] === "object" &&
|
|
265
|
-
pathItemRecord[method] !== null) {
|
|
266
|
-
pathItemRecord[method] = reorderOperationProperties(pathItemRecord[method]);
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
docRecord.paths = reorderedPaths;
|
|
271
|
-
return doc;
|
|
272
|
-
}
|
|
273
|
-
export function reorderDocumentProperties(doc) {
|
|
274
|
-
const docObj = toRecord(doc, "reorderDocumentProperties");
|
|
275
|
-
const priorityKeys = ["openapi", "info", "tags"];
|
|
276
|
-
const reordered = {};
|
|
277
|
-
for (const key of priorityKeys) {
|
|
278
|
-
if (key in docObj) {
|
|
279
|
-
reordered[key] = docObj[key];
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
for (const [key, value] of Object.entries(docObj)) {
|
|
283
|
-
if (!priorityKeys.includes(key)) {
|
|
284
|
-
reordered[key] = value;
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
return reordered;
|
|
288
|
-
}
|
|
289
|
-
export function applyOverlay(openapi, overlay) {
|
|
290
|
-
const validatedOverlay = validateOverlayDocument(overlay);
|
|
291
|
-
const validatedOpenApi = validateOpenApiDocument(openapi);
|
|
292
|
-
const result = deepClone(validatedOpenApi);
|
|
293
|
-
const stats = { applied: 0, skipped: 0, totalNodes: 0 };
|
|
294
|
-
for (const action of validatedOverlay.actions) {
|
|
295
|
-
const { applied, count } = applyAction(result, action);
|
|
296
|
-
if (applied) {
|
|
297
|
-
stats.applied++;
|
|
298
|
-
stats.totalNodes += count;
|
|
299
|
-
}
|
|
300
|
-
else {
|
|
301
|
-
stats.skipped++;
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
const pathOrder = extractPathOrder(validatedOverlay);
|
|
305
|
-
reorderPaths(result, pathOrder);
|
|
306
|
-
const reordered = reorderDocumentProperties(result);
|
|
307
|
-
return { result: reordered, stats };
|
|
308
|
-
}
|
|
309
|
-
//# sourceMappingURL=overlay-engine.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"overlay-engine.js","sourceRoot":"","sources":["../../../src/open-api/overlay/overlay-engine.ts"],"names":[],"mappings":"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","sourcesContent":["import { JSONPath } from \"jsonpath-plus\";\nimport {\n HTTP_METHODS,\n type OverlayAction,\n type OverlayDocument,\n type RemoveCondition,\n validateOpenApiDocument,\n validateOverlayDocument,\n} from \"../../common/open-api/index.js\";\n\nexport interface ApplyResult {\n applied: boolean;\n count: number;\n}\n\nexport interface OverlayStats {\n applied: number;\n skipped: number;\n totalNodes: number;\n}\n\n/**\n * Safely validate and return a generic object\n * Used internally for dynamic JSON manipulation\n */\nfunction toRecord(value: unknown, context: string): Record<string, unknown> {\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n throw new Error(\n `Expected object at ${context}, but got ${Array.isArray(value) ? \"array\" : typeof value}`\n );\n }\n return value as Record<string, unknown>;\n}\n\n/**\n * Deep clone an object using structuredClone for better performance and correctness\n * Falls back to JSON parse/stringify for environments without structuredClone\n */\nexport function deepClone<T>(obj: T): T {\n if (typeof structuredClone === \"function\") {\n return structuredClone(obj);\n }\n return JSON.parse(JSON.stringify(obj));\n}\n\n/**\n * Deep merge two objects\n * Special handling for OpenAPI parameters (merge by name+in)\n * Returns the merged result, which will have the shape of source if target is not an object,\n * or a merged object/array if both are objects/arrays\n */\nexport function deepMerge(target: unknown, source: unknown): unknown {\n if (!source || typeof source !== \"object\") {\n return source;\n }\n\n if (!target || typeof target !== \"object\") {\n return deepClone(source);\n }\n\n if (Array.isArray(source)) {\n if (!Array.isArray(target)) {\n return deepClone(source);\n }\n\n // Special case for OpenAPI parameters - merge by name+in\n if (\n source.length > 0 &&\n typeof source[0] === \"object\" &&\n source[0] !== null &&\n \"name\" in source[0] &&\n \"in\" in source[0]\n ) {\n const merged = [...target];\n for (const sourceParam of source) {\n if (typeof sourceParam !== \"object\" || sourceParam === null) continue;\n const existingIndex = merged.findIndex(\n (p) =>\n typeof p === \"object\" &&\n p !== null &&\n \"name\" in p &&\n \"in\" in p &&\n \"name\" in sourceParam &&\n \"in\" in sourceParam &&\n p.name === sourceParam.name &&\n p.in === sourceParam.in\n );\n if (existingIndex >= 0) {\n merged[existingIndex] = deepMerge(merged[existingIndex], sourceParam);\n } else {\n merged.push(deepClone(sourceParam));\n }\n }\n return merged;\n }\n\n // For other arrays, concatenate\n return [...target, ...deepClone(source)];\n }\n\n // Both are objects at this point (checked above)\n const targetObj = target as Record<string, unknown>;\n const sourceObj = source as Record<string, unknown>;\n const result = { ...targetObj };\n\n for (const [key, value] of Object.entries(sourceObj)) {\n if (key in result) {\n result[key] = deepMerge(result[key], value);\n } else {\n result[key] = deepClone(value);\n }\n }\n\n return result;\n}\n\n/**\n * Check if a condition is met for conditional removal\n */\nexport function checkCondition(\n value: unknown,\n condition: RemoveCondition\n): boolean {\n // Support \"empty\" condition to check if object has no keys\n if (condition.empty) {\n if (typeof value === \"object\" && value !== null && !Array.isArray(value)) {\n return Object.keys(value).length === 0;\n }\n if (Array.isArray(value)) {\n return value.length === 0;\n }\n return false;\n }\n\n // Support \"missing\" condition to check if a property doesn't exist\n if (condition.missing) {\n const parts = condition.missing.split(\".\");\n let current: unknown = value;\n for (const part of parts) {\n if (\n current &&\n typeof current === \"object\" &&\n !Array.isArray(current) &&\n part in current\n ) {\n current = (current as Record<string, unknown>)[part];\n } else {\n return true; // Property is missing\n }\n }\n return false; // Property exists\n }\n\n return false;\n}\n\n/**\n * Regular expression for parsing JSONPath segments\n * Matches: word, or [quoted], or ['quoted'], or [\"quoted\"], or numeric indices [0]\n */\nconst JSONPATH_SEGMENT_PATTERN = /[^.[]+|\\[[^\\]]+\\]/g;\n\n/**\n * Extract key from bracket notation, handling various formats:\n * - ['key'] -> key\n * - [\"key\"] -> key\n * - [key] -> key\n * - [0] -> 0 (as string for consistency)\n */\nfunction extractBracketKey(bracketNotation: string): string {\n const inner = bracketNotation.slice(1, -1); // Remove [ and ]\n\n // Handle single-quoted: ['key'] -> key\n if (inner.startsWith(\"'\") && inner.endsWith(\"'\")) {\n return inner.slice(1, -1);\n }\n\n // Handle double-quoted: [\"key\"] -> key\n if (inner.startsWith('\"') && inner.endsWith('\"')) {\n return inner.slice(1, -1);\n }\n\n // Handle unquoted: [key] or [0] -> key or 0\n return inner;\n}\n\n/**\n * Set a value at a JSONPath, creating parent paths as needed\n * Handles patterns like:\n * $.paths['/route'].method\n * $.components.parameters['name']\n * $.paths[\"/users\"].get\n * $.paths[users].get\n * $.items[0].value\n */\nexport function setValueAtPath(\n doc: unknown,\n target: string,\n value: unknown\n): void {\n // Parse the path manually to handle both bracket and dot notation\n // Example: $.paths['/route'].post -> [\"paths\", \"['/route']\", \"post\"]\n\n // Remove leading $.\n const targetPath = target.replace(/^\\$\\./, \"\");\n\n // Split by dots, but keep bracket notation together\n const parts = targetPath.match(JSONPATH_SEGMENT_PATTERN) || [];\n\n let current = toRecord(doc, \"setValueAtPath document\");\n\n for (let i = 0; i < parts.length - 1; i++) {\n const part = parts[i];\n\n // Handle bracket notation\n if (part.startsWith(\"[\")) {\n const key = extractBracketKey(part);\n if (!(key in current)) {\n current[key] = {};\n }\n const next = current[key];\n current = toRecord(next, `setValueAtPath at ${target}[${i}]`);\n } else {\n // Handle regular property\n if (!(part in current)) {\n current[part] = {};\n }\n const next = current[part];\n current = toRecord(next, `setValueAtPath at ${target}.${part}`);\n }\n }\n\n // Set the final value\n const lastPart = parts[parts.length - 1];\n if (lastPart.startsWith(\"[\")) {\n const key = extractBracketKey(lastPart);\n current[key] = value;\n } else {\n current[lastPart] = value;\n }\n}\n\n/**\n * Apply a single overlay action to the document\n */\nexport function applyAction(doc: unknown, action: OverlayAction): ApplyResult {\n const docRecord = toRecord(doc, \"applyAction document\");\n\n if (action.update) {\n // Handle root level updates specially\n if (action.target === \"$\") {\n Object.assign(docRecord, deepMerge(doc, action.update));\n return { applied: true, count: 1 };\n }\n\n try {\n // Try to find existing nodes\n const results = JSONPath({\n path: action.target,\n // biome-ignore lint/suspicious/noExplicitAny: JSONPath library requires loose typing\n json: doc as any,\n resultType: \"all\",\n }) as Array<{ parent: unknown; parentProperty: unknown; value: unknown }>;\n\n if (results.length > 0) {\n // Path exists, merge the update\n let count = 0;\n for (const result of results) {\n const { parent, parentProperty } = result;\n if (\n parent &&\n typeof parent === \"object\" &&\n !Array.isArray(parent) &&\n parentProperty !== undefined\n ) {\n const parentRecord = parent as Record<string, unknown>;\n parentRecord[parentProperty as string] = deepMerge(\n parentRecord[parentProperty as string],\n action.update\n );\n count++;\n }\n }\n return { applied: true, count };\n }\n\n // Path doesn't exist, create it manually\n setValueAtPath(doc, action.target, action.update);\n return { applied: true, count: 1 };\n } catch (cause) {\n throw new Error(\"Failed to apply update\", { cause });\n }\n } else if (action.remove) {\n try {\n const results = JSONPath({\n path: action.target,\n // biome-ignore lint/suspicious/noExplicitAny: JSONPath library requires loose typing\n json: doc as any,\n resultType: \"all\",\n }) as Array<{ parent: unknown; parentProperty: unknown; value: unknown }>;\n\n if (results.length === 0) {\n return { applied: false, count: 0 };\n }\n\n let count = 0;\n // Process in reverse to avoid index issues when removing\n for (let i = results.length - 1; i >= 0; i--) {\n const result = results[i];\n const { parent, parentProperty, value } = result;\n\n // Check condition if specified\n const shouldRemove =\n typeof action.remove === \"object\"\n ? checkCondition(value, action.remove)\n : true;\n\n if (shouldRemove && parent && parentProperty !== undefined) {\n if (Array.isArray(parent)) {\n parent.splice(parentProperty as number, 1);\n } else if (typeof parent === \"object\" && parent !== null) {\n delete (parent as Record<string, unknown>)[\n parentProperty as string\n ];\n }\n count++;\n }\n }\n\n return { applied: true, count };\n } catch (cause) {\n throw new Error(\"Failed to remove\", { cause });\n }\n }\n\n return { applied: false, count: 0 };\n}\n\n/**\n * Extract path order from overlay actions\n * Returns array of paths in the order they appear in overlay\n */\nexport function extractPathOrder(overlay: OverlayDocument): string[] {\n const pathOrder: string[] = [];\n const seenPaths = new Set<string>();\n\n for (const action of overlay.actions) {\n // Match patterns like $.paths['/route'].method\n const match = action.target.match(/^\\$\\.paths\\['([^']+)'\\]/);\n if (match) {\n const targetPath = match[1];\n if (!seenPaths.has(targetPath)) {\n pathOrder.push(targetPath);\n seenPaths.add(targetPath);\n }\n }\n }\n\n return pathOrder;\n}\n\n/**\n * Reorder operation properties so summary, description, operationId come first\n * Returns a new object with reordered keys\n */\nexport function reorderOperationProperties(\n operation: unknown\n): Record<string, unknown> {\n const operationObj = toRecord(operation, \"reorderOperationProperties\");\n const priorityKeys = [\"summary\", \"description\", \"operationId\"];\n const reordered: Record<string, unknown> = {};\n\n // Add priority keys first (in order)\n for (const key of priorityKeys) {\n if (key in operationObj) {\n reordered[key] = operationObj[key];\n }\n }\n\n // Add remaining keys\n for (const [key, value] of Object.entries(operationObj)) {\n if (!priorityKeys.includes(key)) {\n reordered[key] = value;\n }\n }\n\n return reordered;\n}\n\n/**\n * Reorder paths object to match the order from overlay\n * Modifies the document in place and returns it\n */\nexport function reorderPaths<T>(doc: T, pathOrder: string[]): T {\n // Early return if no paths to reorder\n if (\n typeof doc !== \"object\" ||\n doc === null ||\n !(\"paths\" in doc) ||\n pathOrder.length === 0\n ) {\n return doc;\n }\n\n const docRecord = doc as Record<string, unknown>;\n const originalPaths = toRecord(docRecord.paths, \"paths\");\n const reorderedPaths: Record<string, unknown> = {};\n\n // First, add paths in the order specified by overlay\n for (const targetPath of pathOrder) {\n if (targetPath in originalPaths) {\n reorderedPaths[targetPath] = originalPaths[targetPath];\n }\n }\n\n // Then add any remaining paths that weren't in the overlay\n for (const [targetPath, value] of Object.entries(originalPaths)) {\n if (!(targetPath in reorderedPaths)) {\n reorderedPaths[targetPath] = value;\n }\n }\n\n // Reorder operation properties within each path\n for (const pathItem of Object.values(reorderedPaths)) {\n if (typeof pathItem !== \"object\" || pathItem === null) continue;\n\n const pathItemRecord = pathItem as Record<string, unknown>;\n\n for (const method of HTTP_METHODS) {\n if (\n method in pathItemRecord &&\n typeof pathItemRecord[method] === \"object\" &&\n pathItemRecord[method] !== null\n ) {\n pathItemRecord[method] = reorderOperationProperties(\n pathItemRecord[method]\n );\n }\n }\n }\n\n docRecord.paths = reorderedPaths;\n return doc;\n}\n\n/**\n * Reorder top-level document properties to ensure proper ordering\n * OpenAPI spec should have: openapi, info, tags, servers, paths, components, etc.\n * Returns a new object with reordered keys\n */\nexport function reorderDocumentProperties(\n doc: unknown\n): Record<string, unknown> {\n const docObj = toRecord(doc, \"reorderDocumentProperties\");\n const priorityKeys = [\"openapi\", \"info\", \"tags\"];\n const reordered: Record<string, unknown> = {};\n\n // Add priority keys first\n for (const key of priorityKeys) {\n if (key in docObj) {\n reordered[key] = docObj[key];\n }\n }\n\n // Add remaining keys\n for (const [key, value] of Object.entries(docObj)) {\n if (!priorityKeys.includes(key)) {\n reordered[key] = value;\n }\n }\n\n return reordered;\n}\n\n/**\n * Apply overlay to OpenAPI document\n *\n * Validates both the OpenAPI document and overlay upfront, then applies\n * all actions defined in the overlay to produce a modified OpenAPI document.\n *\n * @param openapi The OpenAPI document to modify (will be validated)\n * @param overlay The overlay document to apply (will be validated)\n * @returns The modified OpenAPI document and statistics about what was applied\n * @throws {Error} if either document is invalid, with actionable error messages\n *\n * @example\n * ```typescript\n * const result = applyOverlay(openapiDoc, overlayDoc);\n * console.log(`Applied ${result.stats.applied} actions`);\n * ```\n */\nexport function applyOverlay(\n openapi: unknown,\n overlay: unknown\n): { result: Record<string, unknown>; stats: OverlayStats } {\n // Validate inputs upfront with actionable error messages\n const validatedOverlay = validateOverlayDocument(overlay);\n const validatedOpenApi = validateOpenApiDocument(openapi);\n\n // Clone the document for modification\n const result = deepClone(validatedOpenApi);\n const stats: OverlayStats = { applied: 0, skipped: 0, totalNodes: 0 };\n\n // Apply each action\n for (const action of validatedOverlay.actions) {\n const { applied, count } = applyAction(result, action);\n\n if (applied) {\n stats.applied++;\n stats.totalNodes += count;\n } else {\n stats.skipped++;\n }\n }\n\n // Reorder paths to match overlay order\n const pathOrder = extractPathOrder(validatedOverlay);\n reorderPaths(result, pathOrder);\n\n // Reorder top-level document properties\n const reordered = reorderDocumentProperties(result);\n\n return { result: reordered, stats };\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"overlay-engine.spec.d.ts","sourceRoot":"","sources":["../../../src/open-api/overlay/overlay-engine.spec.ts"],"names":[],"mappings":""}
|