diff --git a/.yarn/install-state.gz b/.yarn/install-state.gz index b4b4f3d8..8dec516f 100644 Binary files a/.yarn/install-state.gz and b/.yarn/install-state.gz differ diff --git a/lib/bundle.ts b/lib/bundle.ts index 3f8d4fde..141b3aa8 100644 --- a/lib/bundle.ts +++ b/lib/bundle.ts @@ -6,6 +6,20 @@ import type $RefParser from "./index"; import type { ParserOptions } from "./index"; import type { JSONSchema } from "./index"; +export interface InventoryEntry { + $ref: any; + parent: any; + key: any; + pathFromRoot: any; + depth: any; + file: any; + hash: any; + value: any; + circular: any; + extended: any; + external: any; + indirections: any; +} /** * Bundles all external JSON references into the main JSON schema, thus resulting in a schema that * only has *internal* references, not any *external* references. @@ -21,7 +35,7 @@ function bundle = Pars // console.log('Bundling $ref pointers in %s', parser.$refs._root$Ref.path); // Build an inventory of all $ref pointers in the JSON Schema - const inventory: any = []; + const inventory: InventoryEntry[] = []; crawl(parser, "schema", parser.$refs._root$Ref.path + "#", "#", 0, inventory, parser.$refs, options); // Remap all $ref pointers @@ -41,16 +55,16 @@ function bundle = Pars * @param options */ function crawl = ParserOptions>( - parent: any, + parent: object | $RefParser, key: string | null, path: string, pathFromRoot: string, indirections: number, - inventory: unknown[], + inventory: InventoryEntry[], $refs: $Refs, options: O, ) { - const obj = key === null ? parent : parent[key]; + const obj = key === null ? parent : parent[key as keyof typeof parent]; if (obj && typeof obj === "object" && !ArrayBuffer.isView(obj)) { if ($Ref.isAllowed$Ref(obj)) { @@ -71,7 +85,7 @@ function crawl = Parse // This produces the shortest possible bundled references return a.length - b.length; } - }); + }) as (keyof typeof obj)[]; // eslint-disable-next-line no-shadow for (const key of keys) { @@ -104,11 +118,11 @@ function crawl = Parse */ function inventory$Ref = ParserOptions>( $refParent: any, - $refKey: any, + $refKey: string | null, path: string, - pathFromRoot: any, - indirections: any, - inventory: any, + pathFromRoot: string, + indirections: number, + inventory: InventoryEntry[], $refs: $Refs, options: O, ) { @@ -118,7 +132,8 @@ function inventory$Ref if (pointer === null) { return; } - const depth = Pointer.parse(pathFromRoot).length; + const parsed = Pointer.parse(pathFromRoot); + const depth = parsed.length; const file = url.stripHash(pointer.path); const hash = url.getHash(pointer.path); const external = file !== $refs._root$Ref.path; @@ -178,9 +193,9 @@ function inventory$Ref * * @param inventory */ -function remap(inventory: any) { +function remap(inventory: InventoryEntry[]) { // Group & sort all the $ref pointers, so they're in the order that we need to dereference/remap them - inventory.sort((a: any, b: any) => { + inventory.sort((a: InventoryEntry, b: InventoryEntry) => { if (a.file !== b.file) { // Group all the $refs that point to the same file return a.file < b.file ? -1 : +1; @@ -243,15 +258,33 @@ function remap(inventory: any) { entry.$ref.$ref = entry.pathFromRoot; } } - - // console.log(' new value: %s', (entry.$ref && entry.$ref.$ref) ? entry.$ref.$ref : '[object Object]'); } + + // we want to ensure that any $refs that point to another $ref are remapped to point to the final value + // let hadChange = true; + // while (hadChange) { + // hadChange = false; + // for (const entry of inventory) { + // if (entry.$ref && typeof entry.$ref === "object" && "$ref" in entry.$ref) { + // const resolved = inventory.find((e: InventoryEntry) => e.pathFromRoot === entry.$ref.$ref); + // if (resolved) { + // const resolvedPointsToAnotherRef = + // resolved.$ref && typeof resolved.$ref === "object" && "$ref" in resolved.$ref; + // if (resolvedPointsToAnotherRef && entry.$ref.$ref !== resolved.$ref.$ref) { + // // console.log('Re-mapping $ref pointer "%s" at %s', entry.$ref.$ref, entry.pathFromRoot); + // entry.$ref.$ref = resolved.$ref.$ref; + // hadChange = true; + // } + // } + // } + // } + // } } /** * TODO */ -function findInInventory(inventory: any, $refParent: any, $refKey: any) { +function findInInventory(inventory: InventoryEntry[], $refParent: any, $refKey: any) { for (const existingEntry of inventory) { if (existingEntry && existingEntry.parent === $refParent && existingEntry.key === $refKey) { return existingEntry; @@ -259,7 +292,7 @@ function findInInventory(inventory: any, $refParent: any, $refKey: any) { } } -function removeFromInventory(inventory: any, entry: any) { +function removeFromInventory(inventory: InventoryEntry[], entry: any) { const index = inventory.indexOf(entry); inventory.splice(index, 1); } diff --git a/test/specs/bundle/bundle.spec.ts b/test/specs/bundle/bundle.spec.ts index ba478f0a..a7de64f8 100644 --- a/test/specs/bundle/bundle.spec.ts +++ b/test/specs/bundle/bundle.spec.ts @@ -3,20 +3,12 @@ import { expect } from "vitest"; import $RefParser from "../../../lib/index.js"; import path from "../../utils/path"; import dereferencedSchema from "./bundled"; -import Ajv from "ajv"; -import addFormats from "ajv-formats"; describe("Bundles", () => { it("should bundle correctly", async () => { const parser = new $RefParser(); const schema = path.rel("test/specs/bundle/schemaA.json"); const bundled = await parser.bundle(schema); - const derefed = await parser.dereference(bundled); - const ajv = new Ajv(); - addFormats(ajv); - - const compiled = ajv.compile(derefed); - const compiledDerefed = ajv.compile(bundled); expect(bundled).to.deep.equal(dereferencedSchema); }); }); diff --git a/test/specs/bundle/bundled.ts b/test/specs/bundle/bundled.ts index 90b6727f..31cceef8 100644 --- a/test/specs/bundle/bundled.ts +++ b/test/specs/bundle/bundled.ts @@ -2,224 +2,31 @@ export default { $id: "schemaA/1.0", $schema: "http://json-schema.org/draft-07/schema#", type: "object", - allOf: [ - { - type: "object", - required: ["eventId", "payload"], - properties: { - eventId: { - type: "string", + properties: { + purchaseRate: { + allOf: [ + { + type: "object", + properties: { + amount: { + type: "number", + format: "float", + }, + }, }, - }, + { + type: "object", + $ref: "#/properties/fee/properties/modificationFee/properties/amount", + }, + ], }, - { + fee: { type: "object", properties: { - payload: { - type: "array", - items: { - allOf: [ - { - type: "object", - }, - { - type: "object", - properties: { - reservationActionMetaData: { - allOf: [ - { - allOf: [ - { - type: "object", - required: ["supplierPriceElements"], - properties: { - supplierPriceElements: { - allOf: [ - { - required: ["type"], - properties: { - type: { - type: "string", - }, - }, - }, - { - type: "object", - required: ["purchaseRate"], - properties: { - purchaseRate: { - allOf: [ - { - type: "object", - required: ["amount", "currency"], - properties: { - amount: { - type: "number", - format: "float", - }, - currency: { - type: "string", - minLength: 1, - }, - }, - }, - { - type: "object", - properties: { - inDetail: { - type: "object", - properties: { - perDate: { - type: "array", - items: { - type: "object", - properties: { - date: { - type: "string", - format: "date", - }, - amount: { - $ref: "#/allOf/1/properties/payload/items/allOf/1/properties/reservationActionMetaData/allOf/0/allOf/0/properties/supplierPriceElements/allOf/1/properties/fee/properties/modificationFee/properties/amount", - }, - detailedPriceInformation: { - type: "array", - items: { - type: "object", - properties: { - amount: { - $ref: "#/allOf/1/properties/payload/items/allOf/1/properties/reservationActionMetaData/allOf/0/allOf/0/properties/supplierPriceElements/allOf/1/properties/fee/properties/modificationFee/properties/amount", - }, - paxId: { - type: "string", - }, - inDetail: { - type: "object", - properties: { - rate: { - $ref: "#/allOf/1/properties/payload/items/allOf/1/properties/reservationActionMetaData/allOf/0/allOf/0/properties/supplierPriceElements/allOf/1/properties/fee/properties/modificationFee/properties/amount", - }, - board: { - $ref: "#/allOf/1/properties/payload/items/allOf/1/properties/reservationActionMetaData/allOf/0/allOf/0/properties/supplierPriceElements/allOf/1/properties/fee/properties/modificationFee/properties/amount", - }, - taxes: { - type: "array", - items: { - $ref: "#/allOf/1/properties/payload/items/allOf/1/properties/reservationActionMetaData/allOf/0/allOf/0/properties/supplierPriceElements/allOf/1/properties/purchaseRate/allOf/1/properties/inDetail/properties/perDate/items/properties/detailedPriceInformation/items/properties/inDetail/properties/fees/items", - }, - }, - fees: { - type: "array", - items: { - type: "object", - properties: { - id: { - type: "string", - }, - amount: { - $ref: "#/allOf/1/properties/payload/items/allOf/1/properties/reservationActionMetaData/allOf/0/allOf/0/properties/supplierPriceElements/allOf/1/properties/fee/properties/modificationFee/properties/amount", - }, - }, - }, - }, - supplements: { - type: "array", - items: { - $ref: "#/allOf/1/properties/payload/items/allOf/1/properties/reservationActionMetaData/allOf/0/allOf/0/properties/supplierPriceElements/allOf/1/properties/purchaseRate/allOf/1/properties/inDetail/properties/perDate/items/properties/detailedPriceInformation/items/properties/inDetail/properties/fees/items", - }, - }, - salesOfferIds: { - type: "array", - items: { - $ref: "#/allOf/1/properties/payload/items/allOf/1/properties/reservationActionMetaData/allOf/0/allOf/0/properties/supplierPriceElements/allOf/1/properties/purchaseRate/allOf/1/properties/inDetail/properties/perDate/items/properties/detailedPriceInformation/items/properties/inDetail/properties/fees/items", - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - perPax: { - type: "array", - items: { - type: "object", - properties: { - id: { - type: "string", - }, - amount: { - $ref: "#/allOf/1/properties/payload/items/allOf/1/properties/reservationActionMetaData/allOf/0/allOf/0/properties/supplierPriceElements/allOf/1/properties/fee/properties/modificationFee/properties/amount", - }, - salesOfferIds: { - type: "array", - items: { - type: "string", - }, - }, - }, - }, - }, - }, - }, - }, - }, - ], - }, - fee: { - type: "object", - properties: { - modificationFee: { - $ref: "#/allOf/1/properties/payload/items/allOf/1/properties/reservationActionMetaData/allOf/0/allOf/0/properties/supplierPriceElements/allOf/1/properties/purchaseRate/allOf/0", - }, - cancellationFee: { - $ref: "#/allOf/1/properties/payload/items/allOf/1/properties/reservationActionMetaData/allOf/0/allOf/0/properties/supplierPriceElements/allOf/1/properties/purchaseRate/allOf/0", - }, - }, - }, - }, - }, - ], - }, - type: { - type: "string", - }, - }, - }, - { - type: "object", - required: ["test"], - properties: { - test: { - type: "string", - }, - }, - }, - ], - properties: { - type: { - type: "string", - }, - }, - }, - { - type: "object", - required: ["test"], - properties: { - test: { - type: "string", - }, - }, - }, - ], - }, - }, - }, - ], - }, + modificationFee: { + $ref: "#/properties/purchaseRate/allOf/0", }, }, }, - ], + }, }; diff --git a/test/specs/bundle/schemaA.json b/test/specs/bundle/schemaA.json index 877c8661..87c6e1d8 100644 --- a/test/specs/bundle/schemaA.json +++ b/test/specs/bundle/schemaA.json @@ -2,5 +2,5 @@ "$id": "schemaA/1.0", "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", - "$ref": "schemaB.json#/definitions/ReservationPurchaseOrderForBookResponse" + "$ref": "schemaB.json#/definitions/SupplierPriceElement" } diff --git a/test/specs/bundle/schemaB.json b/test/specs/bundle/schemaB.json index d26ee9f6..2aaba39c 100644 --- a/test/specs/bundle/schemaB.json +++ b/test/specs/bundle/schemaB.json @@ -1,79 +1,48 @@ { - "$id": "schemaB/1.0", - "$schema": "http://json-schema.org/draft-07/schema#", - "definitions": { - "AbstractPurchaseOrderResponse": { - "type": "object", - "required": [ - "eventId", - "payload" - ], - "properties": { - "eventId": { - "type": "string" - } - } + "$id": "schemaC/1.0", + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "SupplierPriceElement": { + "type": "object", + "properties": { + "fee": { + "$ref": "#/definitions/AllFees" }, - "AbstractReservationDomainAnnotation": { - "required": [ - "type" - ], - "properties": { - "type": { - "type": "string" - } - } - }, - "ReservationPurchaseOrderForBookResponse": { - "allOf": [ - { - "$ref": "#/definitions/AbstractPurchaseOrderResponse" - }, - { - "type": "object", - "properties": { - "payload": { - "type": "array", - "items": { - "$ref": "#/definitions/BookResponseReservationAction" - } - } - } - } - ] - }, - "BookResponseReservationActionMetaData": { - "allOf": [ - { - "$ref": "schemaC.json#/definitions/AbstractResponseReservationActionMetaData" - }, - { - "type": "object", - "required": [ - "test" - ], - "properties": { - "test": { - "type": "string" - } - } - } - ] + "purchaseRate": { + "$ref": "#/definitions/InDetailParent" + } + } + }, + "AllFees": { + "type": "object", + "properties": { + "modificationFee": { + "$ref": "#/definitions/MonetaryAmount" + } + } + }, + "MonetaryAmount": { + "type": "object", + "properties": { + "amount": { + "$ref": "#/definitions/Amount" + } + } + }, + "Amount": { + "type": "number", + "format": "float" + }, + "InDetailParent": { + "allOf": [ + { + "$ref": "#/definitions/MonetaryAmount" }, - "BookResponseReservationAction": { - "allOf": [ - { - "type": "object" - }, - { - "type": "object", - "properties": { - "reservationActionMetaData": { - "$ref": "#/definitions/BookResponseReservationActionMetaData" - } - } - } - ] + { + "type": "object", + "$ref": "#/definitions/Amount" } + ] } + } } diff --git a/test/specs/bundle/schemaC.json b/test/specs/bundle/schemaC.json deleted file mode 100644 index 3fcee17f..00000000 --- a/test/specs/bundle/schemaC.json +++ /dev/null @@ -1,235 +0,0 @@ -{ - "$id": "schemaC/1.0", - "$schema": "http://json-schema.org/draft-07/schema#", - "definitions": { - "AbstractResponseReservationActionMetaData": { - "allOf": [ - { - "$ref": "#/definitions/AbstractReservationActionMetaData" - }, - { - "type": "object", - "required": [ - "test" - ], - "properties": { - "test": { - "type": "string" - } - } - } - ], - "properties": { - "type": { - "type": "string" - } - } - }, - "AbstractReservationActionMetaData": { - "type": "object", - "required": [ - "supplierPriceElements" - ], - "properties": { - "supplierPriceElements": { - "$ref": "#/definitions/SupplierPriceElement" - }, - "type": { - "type": "string" - } - } - }, - "SupplierPriceElement": { - "allOf": [ - { - "$ref": "#/definitions/AbstractSupplierPriceElement" - }, - { - "type": "object", - "required": [ - "purchaseRate" - ], - "properties": { - "purchaseRate": { - "$ref": "#/definitions/InDetailParent" - }, - "fee": { - "$ref": "#/definitions/AllFees" - } - } - } - ] - }, - "AllFees": { - "type": "object", - "properties": { - "modificationFee": { - "$ref": "#/definitions/MonetaryAmount" - }, - "cancellationFee": { - "$ref": "#/definitions/MonetaryAmount" - } - } - }, - "MonetaryAmount": { - "type": "object", - "required": [ - "amount", - "currency" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Amount" - }, - "currency": { - "$ref": "#/definitions/Currency" - } - } - }, - "Amount": { - "type": "number", - "format": "float" - }, - "Currency": { - "type": "string", - "minLength": 1 - }, - "InDetailParent": { - "allOf": [ - { - "$ref": "#/definitions/MonetaryAmount" - }, - { - "type": "object", - "properties": { - "inDetail": { - "$ref": "#/definitions/InDetailChild" - } - } - } - ] - }, - "InDetailChild": { - "type": "object", - "properties": { - "perDate": { - "type": "array", - "items": { - "$ref": "#/definitions/InDetailPerDate" - } - }, - "perPax": { - "type": "array", - "items": { - "$ref": "#/definitions/InDetailPerPax" - } - } - } - }, - "InDetailPerDate": { - "type": "object", - "properties": { - "date": { - "type": "string", - "format": "date" - }, - "amount": { - "$ref": "#/definitions/Amount" - }, - "detailedPriceInformation": { - "type": "array", - "items": { - "$ref": "#/definitions/DetailedPriceInformation" - } - } - } - }, - "DetailedPriceInformation": { - "type": "object", - "properties": { - "amount": { - "$ref": "#/definitions/Amount" - }, - "paxId": { - "type": "string" - }, - "inDetail": { - "$ref": "#/definitions/InDetailPriceInformation" - } - } - }, - "InDetailPriceInformation": { - "type": "object", - "properties": { - "rate": { - "$ref": "#/definitions/Amount" - }, - "board": { - "$ref": "#/definitions/Amount" - }, - "taxes": { - "type": "array", - "items": { - "$ref": "#/definitions/InDetailField" - } - }, - "fees": { - "type": "array", - "items": { - "$ref": "#/definitions/InDetailField" - } - }, - "supplements": { - "type": "array", - "items": { - "$ref": "#/definitions/InDetailField" - } - }, - "salesOfferIds": { - "type": "array", - "items": { - "$ref": "#/definitions/InDetailField" - } - } - } - }, - "InDetailField": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "amount": { - "$ref": "#/definitions/Amount" - } - } - }, - "InDetailPerPax": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "amount": { - "$ref": "#/definitions/Amount" - }, - "salesOfferIds": { - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "AbstractSupplierPriceElement": { - "required": [ - "type" - ], - "properties": { - "type": { - "type": "string" - } - } - } - } -}