Skip to content

Commit

Permalink
update: add transformDate keyword
Browse files Browse the repository at this point in the history
  • Loading branch information
k0stik committed Mar 18, 2024
1 parent 3ae39fc commit e72be26
Show file tree
Hide file tree
Showing 2 changed files with 94 additions and 3 deletions.
63 changes: 60 additions & 3 deletions src/js/utils/ajv.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type {} from "ajv"; // @see https://github.com/microsoft/TypeScript/issues/47663
import type { FuncKeywordDefinition } from "ajv";
import Ajv, { SchemaObject } from "ajv";
import { AnyValidateFunction } from "ajv/dist/core";

Expand All @@ -25,6 +25,59 @@ function addAdditionalPropertiesToSchema(schema: JSONSchema, additionalPropertie
});
}

function addTransformDateKeywordToSchema(schema: JSONSchema) {
return mapObjectDeep(schema, (object) => {
const localSchema = object as JSONSchema;

if (
typeof object === "object" &&
localSchema?.type === "string" &&
localSchema?.format === "date-time"
) {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { type, format, ...restSchema } = localSchema;
return {
...restSchema,
transformDate: true,
};
}
});
}

/**
* This function defines custom transformDate AJV keyword
*
* Problem:
* There's no way to define dates except as a string with "date-time" or "date" format in JsonSchema:
* {
* type: "string",
* format: "date-time"
* }
* But we need the Date object to be stored in databases or to perform correct date comparison
*
* Solution:
* The keyword will convert string with "date-time" format to Date object
*/
function transformDateKeyword(): FuncKeywordDefinition {
return {
keyword: "transformDate",
schemaType: "boolean",
modifying: true,
validate(transformDate, date, _metadata, dataCxt) {
if (transformDate && dataCxt && typeof date === "string") {
const dateObject = new Date(date);
if (dateObject.toString() === "Invalid Date") {
return false;
}

dataCxt.parentData[dataCxt.parentDataProperty] = dateObject;
}

return true;
},
};
}

const ajvConfig = {
strict: false, // TODO: adjust schemas and enable strict mode
useDefaults: true,
Expand All @@ -33,6 +86,7 @@ const ajvConfig = {
* @see https://ajv.js.org/guide/modifying-data.html#assigning-defaults
*/
discriminator: true,
keywords: [transformDateKeyword()],
};

const ajvValidator = new Ajv({ ...ajvConfig });
Expand Down Expand Up @@ -69,8 +123,11 @@ export function getValidator(
let validate = ajv.getSchema(schemaKey);

if (!validate) {
// properties that were not defined in schema will be ignored when clean = false
const patchedSchema = clean ? addAdditionalPropertiesToSchema(jsonSchema) : jsonSchema;
// replace "date-time" format with "transformDate" keyword
const patchedSchema = addTransformDateKeywordToSchema(
// properties that were not defined in schema will be ignored when clean = false
clean ? addAdditionalPropertiesToSchema(jsonSchema) : jsonSchema,
);
ajv.addSchema(patchedSchema, schemaKey);
validate = ajv.getSchema(schemaKey);
}
Expand Down
34 changes: 34 additions & 0 deletions tests/js/validate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import groupBy from "lodash/groupBy";
import path from "path";

import JSONSchemasInterface from "../../src/js/esse/JSONSchemasInterfaceServer";
import { AnyObject } from "../../src/js/esse/types";
import * as ajv from "../../src/js/utils/ajv";
import { walkDirSync } from "../../src/js/utils/filesystem";

Expand Down Expand Up @@ -35,6 +36,39 @@ describe("validate all examples", () => {
});
});

interface Example extends AnyObject {
property: string | Date;
}

describe("validate Date object", () => {
const schema = {
$id: "validate-date-object",
type: "object",
properties: {
property: {
type: "string",
format: "date-time",
},
},
};

const example1: Example = { property: new Date() };
const example2: Example = { property: "December 17, 1995 03:24:00" };
const example3: Example = { property: "Invalid Date" };

const result1 = ajv.validate(example1, schema);
const result2 = ajv.validate(example2, schema);
const result3 = ajv.validate(example3, schema);

expect(result1.isValid).to.be.equal(true);
expect(result2.isValid).to.be.equal(true);
expect(result3.isValid).to.be.equal(false);

expect(example1.property instanceof Date).to.be.equal(true);
expect(example2.property instanceof Date).to.be.equal(true);
expect(typeof example3.property === "string").to.be.equal(true);
});

describe("schema titles must be unique or empty", () => {
JSONSchemasInterface.setSchemaFolder(schemasPath);
const schemas = JSONSchemasInterface.schemasCache.values();
Expand Down

0 comments on commit e72be26

Please sign in to comment.