Skip to content

Commit

Permalink
feat(plugins): add JSON validation schema plugin (#62)
Browse files Browse the repository at this point in the history
* chore: add root node_modules to gitignore

* feat: setup json folder

* feat(json): add ajv

* feat(json): fixed plugin and tests

* chore: package rename

* Revert "chore: package rename"

This reverts commit efe1965.

* chore: rename module

* fix: removed commands

---------

Co-authored-by: Puria Nafisi Azizi <[email protected]>
  • Loading branch information
bbtgnn and puria authored Dec 13, 2023
1 parent 7901bd0 commit a72432f
Show file tree
Hide file tree
Showing 9 changed files with 264 additions and 3 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,4 @@
!/docs/*.mjs
!/docs/*.json

node_modules
1 change: 1 addition & 0 deletions pkg/json-schema/.npmignore
31 changes: 31 additions & 0 deletions pkg/json-schema/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"name": "@slangroom/json-schema",
"version": "1.9.6",
"dependencies": {
"@slangroom/core": "workspace:*",
"@slangroom/shared": "workspace:*",
"ajv": "^8.12.0"
},
"repository": "https://github.com/dyne/slangroom",
"license": "AGPL-3.0-only",
"type": "module",
"main": "./build/esm/src/index.js",
"types": "./build/esm/src/index.d.ts",
"exports": {
".": {
"import": {
"types": "./build/esm/src/index.d.ts",
"default": "./build/esm/src/index.js"
}
},
"./*": {
"import": {
"types": "./build/esm/src/*.d.ts",
"default": "./build/esm/src/*.js"
}
}
},
"publishConfig": {
"access": "public"
}
}
1 change: 1 addition & 0 deletions pkg/json-schema/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from '@slangroom/json-schema/plugin';
46 changes: 46 additions & 0 deletions pkg/json-schema/src/plugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { Plugin } from '@slangroom/core';
import Ajv, { type ValidationError } from 'ajv';

export { ValidationError };

//

const p = new Plugin();

export const ARG_JSON_DATA = 'json_data';
export const ARG_JSON_SCHEMA = 'json_schema';
export const PHRASE_VALIDATE_JSON = 'validate json';

export const SENTENCE_VALIDATE_JSON = (dataKey: string, schemaKey: string, outputKey: string) =>
`Given I send ${ARG_JSON_DATA} '${dataKey}' and send ${ARG_JSON_SCHEMA} '${schemaKey}' and ${PHRASE_VALIDATE_JSON} and output into '${outputKey}'
Given I have a 'string dictionary' named '${outputKey}'`;

/**
* @internal
*/
export const validateJSON = p.new(
[ARG_JSON_DATA, ARG_JSON_SCHEMA],
PHRASE_VALIDATE_JSON,
async (ctx) => {
const data = ctx.fetch(ARG_JSON_DATA);
const schema = ctx.fetch(ARG_JSON_SCHEMA);

try {
const ajv = new Ajv.default({ allErrors: true });

// @ts-ignore
const validate = ajv.compile(schema);
validate(data);

// @ts-ignore
return ctx.pass({
errors: validate.errors ?? [],
});
} catch (e) {
console.log(e.message);
return ctx.fail('JSON Schema not valid' + e);
}
},
);

export const JSONSchema = p;
81 changes: 81 additions & 0 deletions pkg/json-schema/test/e2e.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import test from 'ava';
import { Slangroom } from '@slangroom/core';
import { JSONSchema, SENTENCE_VALIDATE_JSON, ValidationError } from '@slangroom/json-schema';

const KEY_DATA = 'data';
const KEY_SCHEMA = 'schema';
const KEY_OUTPUT = 'out';
const SENTENCE = SENTENCE_VALIDATE_JSON(KEY_DATA, KEY_SCHEMA, KEY_OUTPUT);

test('validate valid data with valid schema', async (t) => {
const script = `
Rule unknown ignore
${SENTENCE}
Then print data
`;

const slangroom = new Slangroom(JSONSchema);

const res = await slangroom.execute(script, {
data: {
[KEY_DATA]: 'nice',
[KEY_SCHEMA]: {
type: 'string',
},
},
});

const output = res.result[KEY_OUTPUT] as unknown as { errors: ValidationError[] };
t.deepEqual(output.errors, []);
});

test('validate invalid data with valid schema', async (t) => {
const script = `
Rule unknown ignore
${SENTENCE}
Then print data
`;

const slangroom = new Slangroom(JSONSchema);

const res = await slangroom.execute(script, {
data: {
[KEY_DATA]: 12,
[KEY_SCHEMA]: {
type: 'string',
},
},
});

const output = res.result[KEY_OUTPUT] as unknown as { errors: ValidationError[] };
t.true(Array.isArray(output.errors) && output.errors.length > 0);
});

test('load invalid schema', async (t) => {
const script = `
Rule unknown ignore
${SENTENCE}
Then print data
`;

const slangroom = new Slangroom(JSONSchema);

let error;
try {
await slangroom.execute(script, {
data: {
[KEY_DATA]: 12,
[KEY_SCHEMA]: {
invalid_key: 'string',
},
},
});
} catch (e) {
error = 'Invalid JSON schema';
}

t.assert(error);
});
1 change: 1 addition & 0 deletions pkg/json-schema/tsconfig.json
72 changes: 72 additions & 0 deletions pkg/wallet/src/test/e2e.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import test from 'ava';
import { Slangroom } from '@slangroom/core';
import { wallet } from '@slangroom/wallet';
import { JsonableArray } from '@slangroom/shared';

test('Create VC SD JWT', async (t) => {
const scriptCreate = `
Rule unknown ignore
Given I create p-256 key and output into 'issuer_jwk'
Given I create p-256 key and output into 'holder_jwk'
Given I send sk 'holder_jwk' and create p-256 public key and output into 'holder_public_jwk'
Given I send sk 'issuer_jwk' and create p-256 public key and output into 'issuer_public_jwk'
Given I send jwk 'issuer_jwk' and send holder 'holder_public_jwk' and send object 'object' and send fields 'fields' and create vc sd jwt and output into 'vcsdjwt'
Given I send token 'vcsdjwt' and pretty print sd jwt and output into 'pretty_jwt'
Given I have a 'string dictionary' named 'issuer_jwk'
Given I have a 'string dictionary' named 'holder_jwk'
Given I have a 'string dictionary' named 'holder_public_jwk'
Given I have a 'string dictionary' named 'issuer_public_jwk'
Given I have a 'string' named 'vcsdjwt'
Given I have a 'string dictionary' named 'pretty_jwt'
Then print data
`;
const nonce = 'nIdBbNgRqCXBl8YOkfVdg==';
const slangroom = new Slangroom(wallet);
const res = await slangroom.execute(scriptCreate, {
keys: {
object: {
name: 'test person',
age: 25,
},
fields: ['name', 'age'],
},
});
t.truthy(res.result['vcsdjwt']);
t.is((res.result['pretty_jwt'] as JsonableArray).length, 3);

const scriptPrepare = `
Rule unknown ignore
Given I send verifier_url 'verifier_url' and send issued_vc 'issued_vc' and send disclosed 'disclosed' and send nonce 'nonce' and send holder 'holder_jwk' and present vc sd jwt and output into 'presentation'
Given I have a 'string' named 'presentation'
Then print data
`;
const res2 = await slangroom.execute(scriptPrepare, {
keys: {
disclosed: ['name'],
nonce,
verifier_url: 'https://valid.verifier.url',
holder_jwk: res.result['holder_jwk'] || {},
issued_vc: res.result['vcsdjwt'] || '',
},
});
t.truthy(res2.result['presentation']);

const scriptVerify = `
Rule unknown ignore
Given I send verifier_url 'verifier_url' and send issued_vc 'presentation' and send nonce 'nonce' and send issuer 'issuer_jwk' and verify vc sd jwt and output into 'verification'
Given I have a 'string dictionary' named 'verification'
Then print data
`;
const res3 = await slangroom.execute(scriptVerify, {
keys: {
nonce,
verifier_url: 'https://valid.verifier.url',
issuer_jwk: res.result['issuer_public_jwk'] || {},
presentation: res2.result['presentation'] || '',
},
});
t.truthy(res3.result['verification']);
});
33 changes: 30 additions & 3 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit a72432f

Please sign in to comment.