Skip to content

Commit

Permalink
Revert "feat: use ajv standalone validation code (#258)"
Browse files Browse the repository at this point in the history
This reverts commit 2a69683.
  • Loading branch information
zixiang2018 committed Oct 27, 2023
1 parent 2a69683 commit 15bf048
Show file tree
Hide file tree
Showing 7 changed files with 851 additions and 1,180 deletions.
1,883 changes: 784 additions & 1,099 deletions package-lock.json

Large diffs are not rendered by default.

10 changes: 5 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,8 @@
"@types/qrcode": "^1.4.1",
"@types/uuid": "^8.3.1",
"@types/validator": "^13.6.3",
"@typescript-eslint/eslint-plugin": "^6.9.0",
"@typescript-eslint/parser": "^6.9.0",
"ajv": "^8.12.0",
"ajv-formats": "^2.1.1",
"@typescript-eslint/eslint-plugin": "^4.28.5",
"@typescript-eslint/parser": "^4.28.5",
"cbor": "^7.0.6",
"commitizen": "^4.2.5",
"eslint": "^7.32.0",
Expand All @@ -65,7 +63,7 @@
"prettier": "^2.3.2",
"qrcode": "^1.5.1",
"quicktype": "^15.0.260",
"rollup": "^2.79.1",
"rollup": "^2.56.2",
"rollup-plugin-commonjs": "^10.1.0",
"semantic-release": "^17.4.4",
"ts-jest": "^29.1.1",
Expand All @@ -74,6 +72,8 @@
},
"dependencies": {
"@govtechsg/jsonld": "^0.1.0",
"ajv": "^8.6.2",
"ajv-formats": "^2.1.0",
"cross-fetch": "^3.1.5",
"debug": "^4.3.2",
"ethers": "^5.7.2",
Expand Down
61 changes: 0 additions & 61 deletions scripts/postInstall.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,40 +2,9 @@
const fs = require("fs");
const path = require("path");
const { execSync } = require("child_process");
const standaloneCode = require("ajv/dist/standalone").default;
const Ajv = require("ajv");
const { clone, cloneDeepWith } = require("lodash");

const openAttestationSchemav2 = require("../src/2.0/schema/schema.json");
const openAttestationSchemav3 = require("../src/3.0/schema/schema.json");
const quicktype = path.join(process.cwd(), "node_modules", ".bin", "quicktype");

// remove enum and pattern from the schema
function transformSchema(schema) {
const excludeKeys = ["enum", "pattern"];
function omit(value) {
if (value && typeof value === "object") {
const key = excludeKeys.find((key) => value[key]);
if (key) {
const node = clone(value);
excludeKeys.forEach((key) => {
delete node[key];
});
return node;
}
}
}

const newSchema = cloneDeepWith(schema, omit);
// because we remove check on enum (DNS-DID, DNS-TXT, etc.) the identity proof can match multiple sub schema in v2.
// so here we change oneOf to anyOf, so that if more than one identityProof matches, it still passes
if (newSchema?.definitions?.identityProof?.oneOf) {
newSchema.definitions.identityProof.anyOf = newSchema?.definitions?.identityProof?.oneOf;
delete newSchema?.definitions?.identityProof?.oneOf;
}
return newSchema;
}

if (fs.existsSync(quicktype) && process.env.npm_config_production !== "true") {
console.log('"Creating types from src/2.0/schema/schema.json"');
execSync(
Expand All @@ -47,36 +16,6 @@ if (fs.existsSync(quicktype) && process.env.npm_config_production !== "true") {
quicktype +
" -s schema -o src/__generated__/schema.3.0.ts -t OpenAttestationDocument --just-types src/3.0/schema/schema.json --no-date-times"
);

console.log('"Creating strict compiled schema for v2 and v3"');
const addFormats = require("ajv-formats").default;

let strictAjv = new Ajv({
allErrors: true,
allowUnionTypes: true,
schemas: [openAttestationSchemav2, openAttestationSchemav3],
code: { source: true },
}).addKeyword("deprecationMessage");
addFormats(strictAjv);

fs.writeFileSync(path.join(__dirname, "../src/__generated__/compiled_schema_strict.js"), standaloneCode(strictAjv));

console.log('"Creating non-strict compiled schema for v2 and v3"');
// custom ajv for loose schema validation
// it will allow invalid format, invalid pattern and invalid enum
let nonStrictAjv = new Ajv({
allErrors: true,
allowUnionTypes: true,
schemas: [transformSchema(openAttestationSchemav2), transformSchema(openAttestationSchemav3)],
code: { source: true },
validateFormats: false,
}).addKeyword("deprecationMessage");
addFormats(nonStrictAjv);

fs.writeFileSync(
path.join(__dirname, "../src/__generated__/compiled_schema_non_strict.js"),
standaloneCode(nonStrictAjv)
);
} else {
console.log("Not running quicktype");
}
33 changes: 24 additions & 9 deletions src/shared/ajv.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,27 @@
import { ValidateFunction } from "ajv/dist/core.js";
import strictCompiledSchema from "../__generated__/compiled_schema_strict.js";
import nonStrictCompiledSchema from "../__generated__/compiled_schema_non_strict.js";
import Ajv from "ajv";
import addFormats from "ajv-formats";
import openAttestationSchemav2 from "../2.0/schema/schema.json";
import openAttestationSchemav3 from "../3.0/schema/schema.json";
import { CurrentOptions } from "ajv/dist/core";

export const getSchema = (key: string, mode = "strict"): any => {
const schema =
mode === "strict"
? strictCompiledSchema[key as keyof typeof strictCompiledSchema]
: nonStrictCompiledSchema[key as keyof typeof nonStrictCompiledSchema];
const defaultTransform = (schema: Record<string, any>) => schema;
export const buildAjv = (
options: CurrentOptions & { transform: (schema: Record<string, any>) => Record<string, any> } = {
transform: defaultTransform,
}
): Ajv => {
const { transform, ...ajvOptions } = options;
const ajv = new Ajv({ allErrors: true, allowUnionTypes: true, ...ajvOptions });
addFormats(ajv);
ajv.addKeyword("deprecationMessage");
ajv.compile(transform(openAttestationSchemav2));
ajv.compile(transform(openAttestationSchemav3));
return ajv;
};

const localAjv = buildAjv();
export const getSchema = (key: string, ajv = localAjv) => {
const schema = ajv.getSchema(key);
if (!schema) throw new Error(`Could not find ${key} schema`);
return schema as ValidateFunction;
return schema;
};
38 changes: 36 additions & 2 deletions src/shared/utils/diagnose.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import {
VerifiableCredentialWrappedProofStrict,
} from "../../3.0/types";
import { ArrayProof, Signature, SignatureStrict } from "../../2.0/types";
import { getSchema } from "../ajv";
import { clone, cloneDeepWith } from "lodash";
import { buildAjv, getSchema } from "../ajv";
import { Kind, Mode } from "./@types/diagnose";

type Version = "2.0" | "3.0";
Expand All @@ -25,6 +26,35 @@ const handleError = (debug: boolean, ...messages: string[]) => {
return messages.map((message) => ({ message }));
};

// remove enum and pattern from the schema
function transformSchema(schema: Record<string, any>): Record<string, any> {
const excludeKeys = ["enum", "pattern"];
function omit(value: any) {
if (value && typeof value === "object") {
const key = excludeKeys.find((key) => value[key]);
if (key) {
const node = clone(value);
excludeKeys.forEach((key) => {
delete node[key];
});
return node;
}
}
}

const newSchema = cloneDeepWith(schema, omit);
// because we remove check on enum (DNS-DID, DNS-TXT, etc.) the identity proof can match multiple sub schema in v2.
// so here we change oneOf to anyOf, so that if more than one identityProof matches, it still passes
if (newSchema?.definitions?.identityProof?.oneOf) {
newSchema.definitions.identityProof.anyOf = newSchema?.definitions?.identityProof?.oneOf;
delete newSchema?.definitions?.identityProof?.oneOf;
}
return newSchema;
}
// custom ajv for loose schema validation
// it will allow invalid format, invalid pattern and invalid enum
const ajv = buildAjv({ transform: transformSchema, validateFormats: false });

/**
* Tools to give information about the validity of a document. It will return and eventually output the errors found.
* @param version 2.0 or 3.0
Expand Down Expand Up @@ -54,7 +84,11 @@ export const diagnose = ({
return handleError(debug, "The document must be an object");
}

const errors = validate(document, getSchema(version === "3.0" ? SchemaId.v3 : SchemaId.v2, mode), kind);
const errors = validate(
document,
getSchema(version === "3.0" ? SchemaId.v3 : SchemaId.v2, mode === "non-strict" ? ajv : undefined),
kind
);

if (errors.length > 0) {
// TODO this can be improved later
Expand Down
3 changes: 1 addition & 2 deletions src/shared/validate/validate.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { ErrorObject, ValidateFunction } from "ajv";
import { getLogger } from "../logger";
import { SchemaId } from "../@types/document";
// don't change this otherwise there is a cycle
import { getData } from "../utils/utils";
import { Kind } from "../utils/@types/diagnose";
import { ValidateFunction } from "ajv/dist/core";
import { ErrorObject } from "ajv";
const logger = getLogger("validate");

export const validateSchema = (document: any, validator: ValidateFunction, kind?: Kind): ErrorObject[] => {
Expand Down
3 changes: 1 addition & 2 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,7 @@
"typeRoots": ["./src/@types", "./node_modules/@types"],
"skipLibCheck": true,
"resolveJsonModule": true,
"esModuleInterop": true,
"allowJs": true
"esModuleInterop": true
},
"include": ["./src/**/*"]
}

0 comments on commit 15bf048

Please sign in to comment.