Skip to content

Commit

Permalink
fix: use standalone ajv validation for model introspection schema (#807)
Browse files Browse the repository at this point in the history
* fix: use standalone ajv validation for model introspection schema

* test: add unit tests for validator
  • Loading branch information
AaronZyLee authored Apr 12, 2024
1 parent a9e6cbd commit 9f55941
Show file tree
Hide file tree
Showing 8 changed files with 74 additions and 17 deletions.
5 changes: 4 additions & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,9 @@ module.exports = {
'/packages/*/CHANGELOG.md',

// Ignore lint in e2e test apps
'test-apps'
'test-apps',

// Ignore lint for standalone JSON validation function
'/packages/appsync-modelgen-plugin/src/validate-cjs.js'
]
};
7 changes: 4 additions & 3 deletions packages/appsync-modelgen-plugin/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,18 @@
"codegen"
],
"scripts": {
"build": "tsc",
"build": "yarn generate-schemas && yarn generate-standalone-validation-function && tsc",
"watch": "tsc -w",
"test-watch": "jest --watch",
"test": "jest",
"generate-schemas": "ts-node ./scripts/generateSchemas.ts",
"generate-schemas": "ts-node ./scripts/generateSchemas.ts --overwrite",
"generate-standalone-validation-function": "ts-node ./scripts/generateStandaloneValidationFunction.ts",
"extract-api": "ts-node ../../scripts/extract-api.ts"
},
"dependencies": {
"@graphql-codegen/plugin-helpers": "^1.18.8",
"@graphql-codegen/visitor-plugin-common": "^1.22.0",
"@graphql-tools/utils": "^6.0.18",
"ajv": "^6.10.0",
"chalk": "^3.0.0",
"change-case": "^4.1.1",
"graphql-transformer-common": "^4.25.1",
Expand All @@ -45,6 +45,7 @@
"@types/fs-extra": "^8.1.2",
"@types/node": "^12.12.6",
"@types/pluralize": "0.0.29",
"ajv": "^8.12.0",
"graphql": "^15.5.0",
"java-ast": "^0.3.0",
"ts-json-schema-generator": "1.0.0"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import Ajv from 'ajv';
import modelIntrospectionSchemaDefinition from '../schemas/introspection/1/ModelIntrospectionSchema.json'
import { join } from 'path';
import { writeFileSync } from 'fs';

const standaloneCode = require("ajv/dist/standalone").default

const ajv = new Ajv({ code: { source: true } });
const validate = ajv.compile(modelIntrospectionSchemaDefinition);

let moduleCode = standaloneCode(ajv, validate)

// Now you can write the module code to file
writeFileSync(join(__dirname, "../src/validate-cjs.js"), moduleCode)
41 changes: 41 additions & 0 deletions packages/appsync-modelgen-plugin/src/__tests__/validate.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
const validateModelIntrospectionSchema = require('../validate-cjs');
import { ModelIntrospectionSchema } from '../interfaces/introspection'

describe('Standalone validation function', () => {
const validSchema: ModelIntrospectionSchema = {
version: 1,
models: {},
nonModels: {},
enums: {},
}
it('should pass on the valid schema', () => {
const result = validateModelIntrospectionSchema(validSchema);
expect(result).toBe(true);
});
describe('should fail on the invalid schema', () => {
it('invalid version', () => {
const schema = {
...validSchema,
version: 100,
};
const result = validateModelIntrospectionSchema(schema);
expect(result).toBe(false);
});
it('invalid fields', () => {
const schema = {
...validSchema,
invalidField: {}
};
const result = validateModelIntrospectionSchema(schema);
expect(result).toBe(false);
});
it('missing required fields', () => {
const schema = {
...validSchema,
};
delete (schema as any).models;
const result = validateModelIntrospectionSchema(schema);
expect(result).toBe(false);
});
});
});
1 change: 1 addition & 0 deletions packages/appsync-modelgen-plugin/src/validate-cjs.js

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,9 @@ import { GraphQLSchema } from "graphql";
import { Argument, AssociationType, Field, Fields, FieldType, ModelAttribute, ModelIntrospectionSchema, PrimaryKeyInfo, SchemaEnum, SchemaModel, SchemaMutation, SchemaNonModel, SchemaQuery, SchemaSubscription, Input, InputFieldType } from "../interfaces/introspection";
import { METADATA_SCALAR_MAP } from "../scalars";
import { CodeGenConnectionType } from "../utils/process-connections";
import { RawAppSyncModelConfig, ParsedAppSyncModelConfig, AppSyncModelVisitor, CodeGenEnum, CodeGenField, CodeGenModel, CodeGenPrimaryKeyType, CodeGenQuery, CodeGenSubscription, CodeGenMutation, CodeGenInputObject, CodeGenUnion, CodeGenInterface } from "./appsync-visitor";
import fs from 'fs';
import path from 'path';
import Ajv from 'ajv';
import { RawAppSyncModelConfig, ParsedAppSyncModelConfig, AppSyncModelVisitor, CodeGenEnum, CodeGenField, CodeGenModel, CodeGenPrimaryKeyType, CodeGenQuery, CodeGenSubscription, CodeGenMutation, CodeGenInputObject } from "./appsync-visitor";

const validateModelIntrospectionSchema = require('../validate-cjs');

type UnionFieldType = { union: string };
type InterfaceFieldType = { interface: string };
Expand All @@ -18,17 +17,13 @@ export class AppSyncModelIntrospectionVisitor<
TPluginConfig extends ParsedAppSyncModelIntrospectionConfig = ParsedAppSyncModelIntrospectionConfig
> extends AppSyncModelVisitor<TRawConfig, TPluginConfig> {
private readonly introspectionVersion = 1;
private schemaValidator: Ajv.ValidateFunction;
constructor(
schema: GraphQLSchema,
rawConfig: TRawConfig,
additionalConfig: Partial<TPluginConfig>,
defaultScalars: NormalizedScalarsMap = DEFAULT_SCALARS,
) {
super(schema, rawConfig, additionalConfig, defaultScalars);
const modelIntrospectionSchemaText = fs.readFileSync(path.join(__dirname, '..', '..', 'schemas', 'introspection', this.introspectionVersion.toString(), 'ModelIntrospectionSchema.json'), 'utf8');
const modelIntrospectionSchema = JSON.parse(modelIntrospectionSchemaText);
this.schemaValidator = new Ajv().compile(modelIntrospectionSchema);
}

generate(): string {
Expand All @@ -43,8 +38,8 @@ export class AppSyncModelIntrospectionVisitor<
);

const modelIntrosepctionSchema = this.generateModelIntrospectionSchema();
if (!this.schemaValidator(modelIntrosepctionSchema)) {
throw new Error(`Data did not validate against the supplied schema. Underlying errors were ${JSON.stringify(this.schemaValidator.errors)}`);
if (!validateModelIntrospectionSchema(modelIntrosepctionSchema)) {
throw new Error(`Data did not validate against the supplied schema. Underlying errors were ${JSON.stringify(validateModelIntrospectionSchema.errors)}`);
}
return JSON.stringify(modelIntrosepctionSchema, null, 4);
}
Expand Down
6 changes: 4 additions & 2 deletions packages/appsync-modelgen-plugin/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"rootDir": "src",
"outDir": "lib"
"outDir": "lib",
"allowJs": true,
},
"exclude": [
"scripts",
"schemas",
"lib",
"src/__tests__"
"src/__tests__",
"coverage",
]
}
2 changes: 1 addition & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -6125,7 +6125,7 @@ ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.6, ajv@~6.12.6:
json-schema-traverse "^0.4.1"
uri-js "^4.2.2"

ajv@^8.0.1, ajv@^8.11.0:
ajv@^8.0.1, ajv@^8.11.0, ajv@^8.12.0:
version "8.12.0"
resolved "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz#d1a0527323e22f53562c567c00991577dfbe19d1"
integrity sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==
Expand Down

0 comments on commit 9f55941

Please sign in to comment.