Skip to content

Commit

Permalink
fix: add missing template values for additionalItems
Browse files Browse the repository at this point in the history
  • Loading branch information
sagold committed Dec 3, 2024
1 parent d3fa491 commit f846574
Show file tree
Hide file tree
Showing 2 changed files with 95 additions and 121 deletions.
148 changes: 54 additions & 94 deletions lib/getTemplate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,13 @@ function createTemplateSchema(
for (let i = 0; i < schema.allOf.length; i += 1) {
const allNode = draft.createNode(schema.allOf[i], pointer);
allOf.push(resolveSchema(allNode, extendedData).schema);
extendedData = getTemplate(draft, extendedData, { type: schema.type, ...allOf[i] }, `${pointer}/allOf/${i}`, opts);
extendedData = getTemplate(
draft,
extendedData,
{ type: schema.type, ...allOf[i] },
`${pointer}/allOf/${i}`,
opts
);
}

const resolvedSchema = mergeAllOfSchema(draft, { allOf });
Expand All @@ -135,8 +141,7 @@ function createTemplateSchema(
return templateSchema;
}

const isJsonSchema = (template: unknown): template is JsonSchema =>
template && typeof template === "object";
const isJsonSchema = (template: unknown): template is JsonSchema => template && typeof template === "object";

/**
* Create data object matching the given schema
Expand Down Expand Up @@ -175,10 +180,7 @@ function getTemplate(
if (Array.isArray(schema.oneOf)) {
if (isEmpty(data)) {
const type =
schema.oneOf[0].type ||
schema.type ||
(schema.const && typeof schema.const) ||
getTypeOf(data);
schema.oneOf[0].type || schema.type || (schema.const && typeof schema.const) || getTypeOf(data);
schema = { ...schema.oneOf[0], type };
} else {
// find correct schema for data
Expand Down Expand Up @@ -211,19 +213,12 @@ function getTemplate(
return data;
}

const type = Array.isArray(schema.type)
? selectType(schema.type, data, schema.default)
: schema.type;
const type = Array.isArray(schema.type) ? selectType(schema.type, data, schema.default) : schema.type;

// reset invalid type
const javascriptTypeOfData = getTypeOf(data);


if (
data != null &&
javascriptTypeOfData !== type &&
!(javascriptTypeOfData === "number" && type === "integer")
) {
if (data != null && javascriptTypeOfData !== type && !(javascriptTypeOfData === "number" && type === "integer")) {
data = convertValue(type, data);
}

Expand Down Expand Up @@ -259,29 +254,17 @@ function selectType(types: string[], data: unknown, defaultValue: unknown) {

const TYPE: Record<
string,
(
draft: Draft,
schema: JsonSchema,
data: unknown,
pointer: JsonPointer,
opts: TemplateOptions
) => unknown
(draft: Draft, schema: JsonSchema, data: unknown, pointer: JsonPointer, opts: TemplateOptions) => unknown
> = {
null: (draft, schema, data) => getDefault(schema, data, null),
string: (draft, schema, data) => getDefault(schema, data, ""),
number: (draft, schema, data) => getDefault(schema, data, 0),
integer: (draft, schema, data) => getDefault(schema, data, 0),
boolean: (draft, schema, data) => getDefault(schema, data, false),
object: (
draft,
schema,
data: Record<string, unknown> | undefined,
pointer: JsonPointer,
opts: TemplateOptions
) => {
object: (draft, schema, data: Record<string, unknown> | undefined, pointer: JsonPointer, opts: TemplateOptions) => {
const template = schema.default === undefined ? {} : schema.default;
const d: Record<string, unknown> = {}; // do not assign data here, to keep ordering from json-schema
const required = (opts.extendDefaults === false && schema.default !== undefined) ? [] : (schema.required ?? []);
const required = opts.extendDefaults === false && schema.default !== undefined ? [] : (schema.required ?? []);

if (schema.properties) {
Object.keys(schema.properties).forEach((key) => {
Expand All @@ -290,13 +273,7 @@ const TYPE: Record<

// Omit adding a property if it is not required or optional props should be added
if (value != null || isRequired || opts.addOptionalProps) {
d[key] = getTemplate(
draft,
value,
schema.properties[key],
`${pointer}/properties/${key}`,
opts
);
d[key] = getTemplate(draft, value, schema.properties[key], `${pointer}/properties/${key}`, opts);
}
});
}
Expand All @@ -308,21 +285,14 @@ const TYPE: Record<
if (dependenciesSchema) {
dependenciesSchema = mergeSchema(schema, dependenciesSchema);
delete dependenciesSchema.dependencies;
const dependencyData = getTemplate(
draft,
data,
dependenciesSchema,
`${pointer}/dependencies`,
opts
);
const dependencyData = getTemplate(draft, data, dependenciesSchema, `${pointer}/dependencies`, opts);
Object.assign(d, dependencyData);
}

if (data) {
if (
opts.removeInvalidData === true &&
(schema.additionalProperties === false ||
getTypeOf(schema.additionalProperties) === "object")
(schema.additionalProperties === false || getTypeOf(schema.additionalProperties) === "object")
) {
if (getTypeOf(schema.additionalProperties) === "object") {
Object.keys(data).forEach((key) => {
Expand All @@ -344,50 +314,57 @@ const TYPE: Record<
const node = draft.createNode(schema, pointer);
const ifSchema = resolveIfSchema(node, d);
if (isSchemaNode(ifSchema)) {
const additionalData = getTemplate(
draft,
d,
{ type: "object", ...ifSchema.schema },
pointer,
opts
);
const additionalData = getTemplate(draft, d, { type: "object", ...ifSchema.schema }, pointer, opts);
Object.assign(d, additionalData);
}

// returns object, which is ordered by json-schema
return d;
},
// build array type of items, ignores additionalItems
array: (
draft: Draft,
schema: JsonSchema,
data: unknown[],
pointer: JsonPointer,
opts: TemplateOptions
) => {
array: (draft: Draft, schema: JsonSchema, data: unknown[], pointer: JsonPointer, opts: TemplateOptions) => {
const template = schema.default === undefined ? [] : schema.default;
const d: unknown[] = data || template;
const minItems = opts.extendDefaults === false && schema.default !== undefined ? 0 : (schema.minItems ?? 0);

if (schema.items == null) {
if (schema.additionalItems) {
// items-array was processed & this is not an items-schema
// => all items are additionalItems
const itemCount = Math.max(minItems, d.length);
for (let i = 0; i < itemCount; i += 1) {
d[i] = getTemplate(draft, d[i], schema.additionalItems, `${pointer}/additionalItems`, opts);
}
}
return data || []; // items are undefined
}

const template = schema.default === undefined ? [] : schema.default;
const d: unknown[] = data || template;
const minItems = (opts.extendDefaults === false && schema.default !== undefined) ? 0 : (schema.minItems || 0);

// build defined set of items
if (Array.isArray(schema.items)) {
for (let i = 0, l = Math.max(minItems ?? 0, schema.items?.length ?? 0); i < l; i += 1) {
d[i] = getTemplate(
draft,
d[i] == null ? template[i] : d[i],
schema.items[i],
`${pointer}/items/${i}`,
opts
);
const length = Math.max(minItems ?? 0, schema.items?.length ?? 0);
for (let i = 0; i < length; i += 1) {
if (schema.items[i]) {
d[i] = getTemplate(
draft,
d[i] == null ? template[i] : d[i],
schema.items[i],
`${pointer}/items/${i}`,
opts
);
} else if (schema.additionalItems) {
d[i] = getTemplate(
draft,
d[i] == null ? template[i] : d[i],
schema.additionalItems,
`${pointer}/additionalItems`,
opts
);
}
}
return d;
}

// abort if the schema is invalid
// no items-schema - return
if (getTypeOf(schema.items) !== "object") {
return d;
}
Expand All @@ -403,13 +380,7 @@ const TYPE: Record<
if (templateSchema.oneOf && d.length === 0) {
const oneOfSchema = templateSchema.oneOf[0];
for (let i = 0; i < minItems; i += 1) {
d[i] = getTemplate(
draft,
d[i] == null ? template[i] : d[i],
oneOfSchema,
`${pointer}/oneOf/0`,
opts
);
d[i] = getTemplate(draft, d[i] == null ? template[i] : d[i], oneOfSchema, `${pointer}/oneOf/0`, opts);
}
return d;
}
Expand Down Expand Up @@ -444,13 +415,7 @@ const TYPE: Record<
// build data from items-definition
if (templateSchema.type) {
for (let i = 0, l = Math.max(minItems, d.length); i < l; i += 1) {
d[i] = getTemplate(
draft,
d[i] == null ? template[i] : d[i],
templateSchema,
`${pointer}/items`,
opts
);
d[i] = getTemplate(draft, d[i] == null ? template[i] : d[i], templateSchema, `${pointer}/items`, opts);
}
return d;
}
Expand All @@ -472,12 +437,7 @@ function getDefault(schema: JsonSchema, templateValue: any, initValue: any) {
return schema.default;
}

export default (
draft: Draft,
data?: any,
schema: JsonSchema = draft.rootSchema,
opts?: TemplateOptions
) => {
export default (draft: Draft, data?: any, schema: JsonSchema = draft.rootSchema, opts?: TemplateOptions) => {
cache = {};
if (opts) {
return getTemplate(draft, data, schema, "#", { ...defaultOptions, ...opts });
Expand Down
68 changes: 41 additions & 27 deletions test/unit/getTemplate.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -273,12 +273,9 @@ describe("getTemplate", () => {
additionalProperties: false
});

const res = getTemplate(
draft,
{ first: "first", second: 42, thrid: "third" },
draft.getSchema(),
{ removeInvalidData: true }
);
const res = getTemplate(draft, { first: "first", second: 42, thrid: "third" }, draft.getSchema(), {
removeInvalidData: true
});
expect(res).to.deep.equal({ first: "first" });
});

Expand Down Expand Up @@ -307,6 +304,7 @@ describe("getTemplate", () => {
});

describe("$ref", () => {
// eslint-disable-next-line @typescript-eslint/no-var-requires
const settings = require("../../lib/config/settings");
const initialValue = settings.GET_TEMPLATE_RECURSION_LIMIT;
before(() => (settings.GET_TEMPLATE_RECURSION_LIMIT = 1));
Expand Down Expand Up @@ -429,6 +427,7 @@ describe("getTemplate", () => {

// should not follow $ref to infinity
it("should create template of draft04", () => {
// eslint-disable-next-line @typescript-eslint/no-var-requires
draft.setSchema(require("../../remotes/draft04.json"));
const res = draft.getTemplate({});
// console.log("RESULT\n", JSON.stringify(res, null, 2));
Expand Down Expand Up @@ -641,12 +640,7 @@ describe("getTemplate", () => {
}
});

const res = getTemplate(
draft,
{ trigger: "yes" },
draft.getSchema(),
TEMPLATE_OPTIONS
);
const res = getTemplate(draft, { trigger: "yes" }, draft.getSchema(), TEMPLATE_OPTIONS);
expect(res).to.deep.equal({ trigger: "yes", dependency: "default" });
});

Expand Down Expand Up @@ -704,12 +698,7 @@ describe("getTemplate", () => {
}
});

const res = getTemplate(
draft,
{ trigger: "yes" },
draft.getSchema(),
TEMPLATE_OPTIONS
);
const res = getTemplate(draft, { trigger: "yes" }, draft.getSchema(), TEMPLATE_OPTIONS);
expect(res).to.deep.equal({ trigger: "yes", dependency: "default" });
});
});
Expand Down Expand Up @@ -753,12 +742,9 @@ describe("getTemplate", () => {
}
}
});
const res = getTemplate(
draft,
{ additionalValue: "input value" },
draft.getSchema(),
{ addOptionalProps: true }
);
const res = getTemplate(draft, { additionalValue: "input value" }, draft.getSchema(), {
addOptionalProps: true
});
expect(res).to.deep.equal({
test: "tested value",
additionalValue: "input value"
Expand Down Expand Up @@ -1011,6 +997,37 @@ describe("getTemplate", () => {

expect(res).to.deep.equal([false]);
});

describe("additionalItems", () => {
it("should add defaults from additionalItems ", () => {
draft.setSchema({
type: "array",
minItems: 2,
additionalItems: {
type: "number",
default: 2
}
});
const res = getTemplate(draft, ["43"]);

expect(res).to.deep.equal([43, 2]);
});

it("should add defaults from additionalItems for unspecified items ", () => {
draft.setSchema({
type: "array",
minItems: 2,
items: [{ type: "boolean" }],
additionalItems: {
type: "number",
default: 2
}
});
const res = getTemplate(draft, ["43"]);

expect(res).to.deep.equal([false, 2]);
});
});
});

describe("items.oneOf", () => {
Expand Down Expand Up @@ -1307,9 +1324,7 @@ describe("getTemplate", () => {
});
});


describe("extendDefaults", () => {

it("should keep array default-value with 'extendDefaults:false'", () => {
draft.setSchema({
type: "array",
Expand Down Expand Up @@ -1406,6 +1421,5 @@ describe("getTemplate", () => {
expect(res).to.deep.equal({ title: "" });
});
});

});
});

0 comments on commit f846574

Please sign in to comment.