Adding a Helper to idenfitify valid json-schemas.

This commit is contained in:
Martin Karkowski 2022-03-21 15:56:06 +01:00
parent 39c50061dc
commit d81eddd7a6
2 changed files with 353 additions and 0 deletions

View File

@ -0,0 +1,264 @@
/**
* @author Martin Karkowski
* @email m.karkowski@zema.de
*/
import { assert } from "chai";
import { describe, it } from "mocha";
import { INopeDescriptor } from "../index.browser";
import { isJsonSchema } from "./jsonSchemaMethods";
describe("jsonSchemaMethods", function () {
// Describe the required Test:
const tests: [string, INopeDescriptor, boolean][] = [
[
"function",
{
type: "function",
inputs: [
{
name: "parameter",
schema: {
type: "string",
},
},
],
},
false,
],
[
"nested-function",
{
type: "object",
properties: {
function: {
type: "function",
inputs: [],
},
},
},
false,
],
[
"array => items",
{
$id: "https://example.com/arrays.schema.json",
$schema: "https://json-schema.org/draft/2020-12/schema",
description:
"A representation of a person, company, organization, or place",
type: "object",
properties: {
fruits: {
type: "array",
items: {
type: "function",
},
},
vegetables: {
type: "array",
items: { $ref: "#/$defs/veggie" },
},
},
},
false,
],
[
"array => additionalItems",
{
$id: "https://example.com/arrays.schema.json",
$schema: "https://json-schema.org/draft/2020-12/schema",
description:
"A representation of a person, company, organization, or place",
type: "object",
properties: {
fruits: {
type: "array",
items: {
type: "string",
},
additionalItems: {
type: "function",
},
},
vegetables: {
type: "array",
items: { $ref: "#/$defs/veggie" },
},
},
},
false,
],
[
"anyOf",
{
$id: "https://example.com/arrays.schema.json",
$schema: "https://json-schema.org/draft/2020-12/schema",
description:
"A representation of a person, company, organization, or place",
type: "object",
properties: {
fruits: {
type: "array",
items: {
type: "string",
},
},
},
anyOf: [
{
type: "string",
},
{
type: "function",
},
],
},
false,
],
[
"allOf",
{
$id: "https://example.com/arrays.schema.json",
$schema: "https://json-schema.org/draft/2020-12/schema",
description:
"A representation of a person, company, organization, or place",
type: "object",
properties: {
fruits: {
type: "array",
items: {
type: "string",
},
},
},
allOf: [
{
type: "function",
},
],
},
false,
],
[
"oneOf",
{
$id: "https://example.com/arrays.schema.json",
$schema: "https://json-schema.org/draft/2020-12/schema",
description:
"A representation of a person, company, organization, or place",
type: "object",
properties: {
fruits: {
type: "array",
items: {
type: "string",
},
},
},
oneOf: [
{
type: "function",
},
],
},
false,
],
];
describe("isJsonSchema", function () {
for (const [name, test, expectedResult] of tests) {
it(name, function () {
const result = isJsonSchema(test);
assert.isTrue(result == expectedResult, "Test Failed");
});
}
it("detect-nope-schema -> true", function () {
const shouldFalse: INopeDescriptor[] = [
{
$id: "https://example.com/person.schema.json",
$schema: "https://json-schema.org/draft/2020-12/schema",
title: "Person",
type: "object",
properties: {
firstName: {
type: "string",
description: "The person's first name.",
},
lastName: {
type: "string",
description: "The person's last name.",
},
age: {
description:
"Age in years which must be equal to or greater than zero.",
type: "integer",
minimum: 0,
},
},
},
{
$id: "https://example.com/geographical-location.schema.json",
$schema: "https://json-schema.org/draft/2020-12/schema",
title: "Longitude and Latitude Values",
description: "A geographical coordinate.",
required: ["latitude", "longitude"],
type: "object",
properties: {
latitude: {
type: "number",
minimum: -90,
maximum: 90,
},
longitude: {
type: "number",
minimum: -180,
maximum: 180,
},
},
},
{
$id: "https://example.com/arrays.schema.json",
$schema: "https://json-schema.org/draft/2020-12/schema",
description:
"A representation of a person, company, organization, or place",
type: "object",
properties: {
fruits: {
type: "array",
items: {
type: "string",
},
},
vegetables: {
type: "array",
items: { $ref: "#/$defs/veggie" },
},
},
definitions: {
veggie: {
type: "object",
required: ["veggieName", "veggieLike"],
properties: {
veggieName: {
type: "string",
description: "The name of the vegetable.",
},
veggieLike: {
type: "boolean",
description: "Do I like this vegetable?",
},
},
},
},
},
];
for (const test of shouldFalse) {
const result = isJsonSchema(test);
assert.isTrue(result, "Test should be true");
}
});
});
});

View File

@ -7,6 +7,7 @@
*/ */
import { IJsonSchema } from "../types/IJSONSchema"; import { IJsonSchema } from "../types/IJSONSchema";
import { INopeDescriptor } from "../types/nope";
import { flattenObject, rgetattr, rsetattr, SPLITCHAR } from "./objectMethods"; import { flattenObject, rgetattr, rsetattr, SPLITCHAR } from "./objectMethods";
/** /**
@ -59,3 +60,91 @@ export function flattenSchema(schema: IJsonSchema) {
export function schemaGetDefinition(schema: IJsonSchema, reference: string) { export function schemaGetDefinition(schema: IJsonSchema, reference: string) {
return rgetattr(schema, reference.replace("#/", ""), null, "/"); return rgetattr(schema, reference.replace("#/", ""), null, "/");
} }
const _isNopeDescriptor: Array<
[keyof INopeDescriptor, (value: any) => boolean]
> = [
["type", (value) => value === "function"],
["inputs", (value) => typeof value === "object"],
["outputs", (value) => typeof value === "object"],
];
/**
* A Helper Function, to test, if the given schema is a JSON Schema or whether it contains a method description
*
* @param { INopeDescriptor | IJsonSchema } schema The Schema to Test
* @returns {boolean}
*/
export function isJsonSchema(schema: INopeDescriptor | IJsonSchema): boolean {
for (const [attr, test] of _isNopeDescriptor) {
if (test(schema[attr])) {
return false;
}
}
// Object-Related Test-Cases.
if (
schema.type == "object" ||
(Array.isArray(schema.type) && schema.type.includes("object"))
) {
if (schema.properties) {
for (const key in schema.properties) {
if (!isJsonSchema(schema.properties[key])) {
return false;
}
}
}
if (schema.patternProperties) {
for (const key in schema.patternProperties) {
if (!isJsonSchema(schema.patternProperties[key])) {
return false;
}
}
}
if (schema.dependencies) {
for (const key in schema.dependencies) {
if (
typeof schema.dependencies[key] !== "string" &&
!isJsonSchema(schema.dependencies[key] as INopeDescriptor)
) {
return false;
}
}
}
}
//
for (const key of ["allOf", "anyOf", "oneOf"]) {
if (schema[key]) {
for (const subSchema of schema[key]) {
if (!isJsonSchema(subSchema)) {
return false;
}
}
}
}
if (
schema.type == "array" ||
(Array.isArray(schema.type) && schema.type.includes("array"))
) {
if (Array.isArray(schema.items)) {
for (const key in schema.items) {
if (!isJsonSchema(schema.items[key])) {
return false;
}
}
} else if (!isJsonSchema(schema.items)) {
return false;
}
// Test the Additional Items.
if (typeof schema.additionalItems === "object") {
if (!isJsonSchema(schema.additionalItems)) {
return false;
}
}
}
return true;
}