Skip to content

Commit

Permalink
Add getSubschemaByPath test for JSONSchema path extraction
Browse files Browse the repository at this point in the history
  • Loading branch information
bastiion committed Sep 16, 2024
1 parent 94dbcbf commit 63eac70
Show file tree
Hide file tree
Showing 2 changed files with 220 additions and 0 deletions.
82 changes: 82 additions & 0 deletions packages/json-schema-utils/src/getSubschemaByPath.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { JSONSchema7 } from "json-schema";
import { bringDefinitionToTop } from "./jsonSchema";
import { getSubschemaByPath } from "./getSubschemaByPath";

const rootSchema = {
$id: "https://example.com/schemas/person",
definitions: {
address: {
type: "object",
properties: {
street: { type: "string" },
houseNumber: [{ type: "number" }, { type: "string" }],
city: { type: "string" },
country: { type: "string" },
},
},
person: {
type: "object",
properties: {
name: { type: "string" },
age: { type: "number" },
address: { $ref: "#/definitions/address" },
parent: { $ref: "#/definitions/person" },
children: {
type: "array",
items: { $ref: "#/definitions/person" },
},
secondaryAddresses: {
type: "array",
items: [{ type: "string" }, { $ref: "#/definitions/address" }],
},
},
},
},
};
const schema: JSONSchema7 = bringDefinitionToTop(
rootSchema as JSONSchema7,
"person",
);

describe("JSONSchema path extraction getSubschemaByPath", () => {
it("should return the correct subschema for a valid path", () => {
const citySubschema = getSubschemaByPath(schema, "children.2.address.city");
expect(citySubschema).toEqual({ type: "string" });
});

it("should handle array indices", () => {
const childNameSubschema = getSubschemaByPath(schema, "children.5.name");
expect(childNameSubschema).toEqual({ type: "string" });
});

it("should handle indexed arrays", () => {
const prefferedAddressStreetSubschema = getSubschemaByPath(
schema,
"secondaryAddresses.1.street",
);
expect(prefferedAddressStreetSubschema).toEqual({ type: "string" });

const preferredAddressHouseNumberSubschema = getSubschemaByPath(
schema,
"secondaryAddresses.1.houseNumber",
);
expect(preferredAddressHouseNumberSubschema).toEqual([
{ type: "number" },
{ type: "string" },
]);

const preferredAddressNameSubschema = getSubschemaByPath(
schema,
"secondaryAddresses.0",
);
expect(preferredAddressNameSubschema).toEqual({ type: "string" });
});

it("should return undefined for invalid or non-existent paths", () => {
const invalidSubschema1 = getSubschemaByPath(schema, "children.10.invalid");
expect(invalidSubschema1).toBeUndefined();

const invalidSubschema2 = getSubschemaByPath(schema, "non.existent.path");
expect(invalidSubschema2).toBeUndefined();
});
});
138 changes: 138 additions & 0 deletions packages/json-schema-utils/src/getSubschemaByPath.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import { JSONSchema7, JSONSchema7Definition } from "json-schema";
import { isJSONSchema, resolveSchema } from ".";

const processSegment = (
rootSchema: JSONSchema7,
schema: JSONSchema7Definition | JSONSchema7Definition[],
segment: string,
):
| JSONSchema7Definition
| JSONSchema7Definition[]
| JSONSchema7
| undefined => {
let currentSubschema:
| JSONSchema7Definition
| JSONSchema7Definition[]
| JSONSchema7
| undefined = schema;

if (currentSubschema) {
if (/^\d+$/.test(segment)) {
if (
!Array.isArray(currentSubschema) &&
isJSONSchema(currentSubschema) &&
currentSubschema.type === "array" &&
currentSubschema.items
) {
const index = parseInt(segment, 10);

currentSubschema = currentSubschema.items;
if (
!Array.isArray(currentSubschema) &&
isJSONSchema(currentSubschema) &&
currentSubschema.$ref
) {
currentSubschema = resolveSchema(currentSubschema, "", rootSchema) as
| JSONSchema7Definition
| JSONSchema7;
}
if (
Array.isArray(currentSubschema) &&
currentSubschema.length > index
) {
// now we have the case that items of an array are an array of schemas
currentSubschema = currentSubschema[index];
if (
currentSubschema &&
!Array.isArray(currentSubschema) &&
isJSONSchema(currentSubschema) &&
currentSubschema.$ref
) {
const resolvedSchema = resolveSchema(
currentSubschema,
"",
rootSchema,
) as JSONSchema7Definition | JSONSchema7;
if (resolvedSchema && isJSONSchema(resolvedSchema)) {
}
}
}
}
} else if (
!Array.isArray(currentSubschema) &&
isJSONSchema(currentSubschema) &&
typeof currentSubschema.properties === "object" &&
currentSubschema.properties.hasOwnProperty(segment)
) {
currentSubschema = currentSubschema.properties[segment];
} else if (
!Array.isArray(currentSubschema) &&
isJSONSchema(currentSubschema) &&
currentSubschema.$ref
) {
const resolvedSchema = resolveSchema(currentSubschema, "", rootSchema) as
| JSONSchema7Definition
| JSONSchema7;
if (
resolvedSchema &&
isJSONSchema(resolvedSchema) &&
resolvedSchema.properties &&
resolvedSchema.properties.hasOwnProperty(segment)
) {
currentSubschema = resolvedSchema.properties[segment];
}
} else {
return undefined;
}
} else {
return undefined;
}
if (
currentSubschema &&
!Array.isArray(currentSubschema) &&
isJSONSchema(currentSubschema) &&
currentSubschema.$ref
) {
const resolvedSchema = resolveSchema(currentSubschema, "", rootSchema) as
| JSONSchema7Definition
| JSONSchema7;
if (resolvedSchema && isJSONSchema(resolvedSchema)) {
currentSubschema = resolvedSchema;
}
}
return currentSubschema;
};

/**
* checks a schema for a given path to exist and outputs its schema
*
* if the path does not exist, it returns undefined
*
* @param schema
* @param path
*/
export const getSubschemaByPath = (
schema: JSONSchema7,
path: string,
):
| JSONSchema7Definition
| JSONSchema7Definition[]
| JSONSchema7
| undefined => {
const pathSegments = path.split(".");
let currentSubschema:
| JSONSchema7Definition[]
| JSONSchema7
| JSONSchema7Definition
| undefined = schema;

for (const segment of pathSegments) {
if (!currentSubschema) {
return undefined;
}

currentSubschema = processSegment(schema, currentSubschema, segment);
}

return currentSubschema;
};

0 comments on commit 63eac70

Please sign in to comment.