diff --git a/docs/reference/JSON_schema.md b/docs/reference/JSON_schema.md new file mode 100644 index 000000000..1022144bc --- /dev/null +++ b/docs/reference/JSON_schema.md @@ -0,0 +1,769 @@ +# Contract Spec and JSON Schema + +Currently the Contract Spec class can generate methods for encoding a JSON object into XDR. JSON schema describes a valid JSON value. There are then validators that can check if the value is valid. + +## Example Schema + +The following is an example used in the tests: + +```json +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$ref": "#/definitions/tuple_strukt", + "definitions": { + "U32": { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + "I32": { + "type": "integer", + "minimum": -2147483648, + "maximum": 2147483647 + }, + "U64": { + "type": "string", + "pattern": "^([1-9][0-9]*|0)$", + "minLength": 1, + "maxLength": 20 + }, + "I64": { + "type": "string", + "pattern": "^(-?[1-9][0-9]*|0)$", + "minLength": 1, + "maxLength": 21 + }, + "U128": { + "type": "string", + "pattern": "^([1-9][0-9]*|0)$", + "minLength": 1, + "maxLength": 39 + }, + "I128": { + "type": "string", + "pattern": "^(-?[1-9][0-9]*|0)$", + "minLength": 1, + "maxLength": 40 + }, + "U256": { + "type": "string", + "pattern": "^([1-9][0-9]*|0)$", + "minLength": 1, + "maxLength": 78 + }, + "I256": { + "type": "string", + "pattern": "^(-?[1-9][0-9]*|0)$", + "minLength": 1, + "maxLength": 79 + }, + "Address": { + "type": "string", + "format": "address", + "description": "Address can be a public key or contract id" + }, + "ScString": { + "type": "string", + "description": "ScString is a string" + }, + "ScSymbol": { + "type": "string", + "description": "ScString is a string" + }, + "DataUrl": { + "type": "string", + "pattern": "^(?:[A-Za-z0-9+\\/]{4})*(?:[A-Za-z0-9+\\/]{2}==|[A-Za-z0-9+\\/]{3}=)?$" + }, + "Test": { + "description": "This is from the rust doc above the struct Test", + "properties": { + "a": { + "$ref": "#/definitions/U32" + }, + "b": { + "type": "boolean" + }, + "c": { + "$ref": "#/definitions/ScSymbol" + }, + "additionalProperties": false + }, + "required": [ + "a", + "b", + "c" + ], + "type": "object" + }, + "SimpleEnum": { + "oneOf": [ + { + "type": "object", + "title": "First", + "properties": { + "tag": "First" + }, + "additionalProperties": false, + "required": [ + "tag" + ] + }, + { + "type": "object", + "title": "Second", + "properties": { + "tag": "Second" + }, + "additionalProperties": false, + "required": [ + "tag" + ] + }, + { + "type": "object", + "title": "Third", + "properties": { + "tag": "Third" + }, + "additionalProperties": false, + "required": [ + "tag" + ] + } + ] + }, + "RoyalCard": { + "oneOf": [ + { + "description": "", + "title": "Jack", + "enum": [ + 11 + ], + "type": "number" + }, + { + "description": "", + "title": "Queen", + "enum": [ + 12 + ], + "type": "number" + }, + { + "description": "", + "title": "King", + "enum": [ + 13 + ], + "type": "number" + } + ] + }, + "TupleStruct": { + "type": "array", + "items": [ + { + "$ref": "#/definitions/Test" + }, + { + "$ref": "#/definitions/SimpleEnum" + } + ], + "minItems": 2, + "maxItems": 2 + }, + "ComplexEnum": { + "oneOf": [ + { + "type": "object", + "title": "Struct", + "properties": { + "tag": "Struct", + "values": { + "type": "array", + "items": [ + { + "$ref": "#/definitions/Test" + } + ] + } + }, + "required": [ + "tag", + "values" + ], + "additionalProperties": false + }, + { + "type": "object", + "title": "Tuple", + "properties": { + "tag": "Tuple", + "values": { + "type": "array", + "items": [ + { + "$ref": "#/definitions/TupleStruct" + } + ] + } + }, + "required": [ + "tag", + "values" + ], + "additionalProperties": false + }, + { + "type": "object", + "title": "Enum", + "properties": { + "tag": "Enum", + "values": { + "type": "array", + "items": [ + { + "$ref": "#/definitions/SimpleEnum" + } + ] + } + }, + "required": [ + "tag", + "values" + ], + "additionalProperties": false + }, + { + "type": "object", + "title": "Asset", + "properties": { + "tag": "Asset", + "values": { + "type": "array", + "items": [ + { + "$ref": "#/definitions/Address" + }, + { + "$ref": "#/definitions/I128" + } + ] + } + }, + "required": [ + "tag", + "values" + ], + "additionalProperties": false + }, + { + "type": "object", + "title": "Void", + "properties": { + "tag": "Void" + }, + "additionalProperties": false, + "required": [ + "tag" + ] + } + ] + }, + "hello": { + "properties": { + "args": { + "additionalProperties": false, + "properties": { + "hello": { + "$ref": "#/definitions/ScSymbol" + } + }, + "type": "object", + "required": [ + "hello" + ] + } + }, + "additionalProperties": false + }, + "woid": { + "properties": { + "args": { + "additionalProperties": false, + "properties": {}, + "type": "object" + } + }, + "additionalProperties": false + }, + "val": { + "properties": { + "args": { + "additionalProperties": false, + "properties": {}, + "type": "object" + } + }, + "additionalProperties": false + }, + "u32_fail_on_even": { + "properties": { + "args": { + "additionalProperties": false, + "properties": { + "u32_": { + "$ref": "#/definitions/U32" + } + }, + "type": "object", + "required": [ + "u32_" + ] + } + }, + "additionalProperties": false + }, + "u32_": { + "properties": { + "args": { + "additionalProperties": false, + "properties": { + "u32_": { + "$ref": "#/definitions/U32" + } + }, + "type": "object", + "required": [ + "u32_" + ] + } + }, + "additionalProperties": false + }, + "i32_": { + "properties": { + "args": { + "additionalProperties": false, + "properties": { + "i32_": { + "$ref": "#/definitions/I32" + } + }, + "type": "object", + "required": [ + "i32_" + ] + } + }, + "additionalProperties": false + }, + "i64_": { + "properties": { + "args": { + "additionalProperties": false, + "properties": { + "i64_": { + "$ref": "#/definitions/I64" + } + }, + "type": "object", + "required": [ + "i64_" + ] + } + }, + "additionalProperties": false + }, + "strukt_hel": { + "properties": { + "args": { + "additionalProperties": false, + "properties": { + "strukt": { + "$ref": "#/definitions/Test" + } + }, + "type": "object", + "required": [ + "strukt" + ] + } + }, + "description": "Example contract method which takes a struct", + "additionalProperties": false + }, + "strukt": { + "properties": { + "args": { + "additionalProperties": false, + "properties": { + "strukt": { + "$ref": "#/definitions/Test" + } + }, + "type": "object", + "required": [ + "strukt" + ] + } + }, + "additionalProperties": false + }, + "simple": { + "properties": { + "args": { + "additionalProperties": false, + "properties": { + "simple": { + "$ref": "#/definitions/SimpleEnum" + } + }, + "type": "object", + "required": [ + "simple" + ] + } + }, + "additionalProperties": false + }, + "complex": { + "properties": { + "args": { + "additionalProperties": false, + "properties": { + "complex": { + "$ref": "#/definitions/ComplexEnum" + } + }, + "type": "object", + "required": [ + "complex" + ] + } + }, + "additionalProperties": false + }, + "addresse": { + "properties": { + "args": { + "additionalProperties": false, + "properties": { + "addresse": { + "$ref": "#/definitions/Address" + } + }, + "type": "object", + "required": [ + "addresse" + ] + } + }, + "additionalProperties": false + }, + "bytes": { + "properties": { + "args": { + "additionalProperties": false, + "properties": { + "bytes": { + "$ref": "#/definitions/DataUrl" + } + }, + "type": "object", + "required": [ + "bytes" + ] + } + }, + "additionalProperties": false + }, + "bytes_n": { + "properties": { + "args": { + "additionalProperties": false, + "properties": { + "bytes_n": { + "$ref": "#/definitions/DataUrl", + "maxLength": 9 + } + }, + "type": "object", + "required": [ + "bytes_n" + ] + } + }, + "additionalProperties": false + }, + "card": { + "properties": { + "args": { + "additionalProperties": false, + "properties": { + "card": { + "$ref": "#/definitions/RoyalCard" + } + }, + "type": "object", + "required": [ + "card" + ] + } + }, + "additionalProperties": false + }, + "boolean": { + "properties": { + "args": { + "additionalProperties": false, + "properties": { + "boolean": { + "type": "boolean" + } + }, + "type": "object", + "required": [ + "boolean" + ] + } + }, + "additionalProperties": false + }, + "not": { + "properties": { + "args": { + "additionalProperties": false, + "properties": { + "boolean": { + "type": "boolean" + } + }, + "type": "object", + "required": [ + "boolean" + ] + } + }, + "description": "Negates a boolean value", + "additionalProperties": false + }, + "i128": { + "properties": { + "args": { + "additionalProperties": false, + "properties": { + "i128": { + "$ref": "#/definitions/I128" + } + }, + "type": "object", + "required": [ + "i128" + ] + } + }, + "additionalProperties": false + }, + "u128": { + "properties": { + "args": { + "additionalProperties": false, + "properties": { + "u128": { + "$ref": "#/definitions/U128" + } + }, + "type": "object", + "required": [ + "u128" + ] + } + }, + "additionalProperties": false + }, + "multi_args": { + "properties": { + "args": { + "additionalProperties": false, + "properties": { + "a": { + "$ref": "#/definitions/U32" + }, + "b": { + "type": "boolean" + } + }, + "type": "object", + "required": [ + "a", + "b" + ] + } + }, + "additionalProperties": false + }, + "map": { + "properties": { + "args": { + "additionalProperties": false, + "properties": { + "map": { + "type": "array", + "items": { + "type": "array", + "items": [ + { + "$ref": "#/definitions/U32" + }, + { + "type": "boolean" + } + ], + "minItems": 2, + "maxItems": 2 + } + } + }, + "type": "object", + "required": [ + "map" + ] + } + }, + "additionalProperties": false + }, + "vec": { + "properties": { + "args": { + "additionalProperties": false, + "properties": { + "vec": { + "type": "array", + "items": { + "$ref": "#/definitions/U32" + } + } + }, + "type": "object", + "required": [ + "vec" + ] + } + }, + "additionalProperties": false + }, + "tuple": { + "properties": { + "args": { + "additionalProperties": false, + "properties": { + "tuple": { + "type": "array", + "items": [ + { + "$ref": "#/definitions/ScSymbol" + }, + { + "$ref": "#/definitions/U32" + } + ], + "minItems": 2, + "maxItems": 2 + } + }, + "type": "object", + "required": [ + "tuple" + ] + } + }, + "additionalProperties": false + }, + "option": { + "properties": { + "args": { + "additionalProperties": false, + "properties": { + "option": { + "$ref": "#/definitions/U32" + } + }, + "type": "object" + } + }, + "description": "Example of an optional argument", + "additionalProperties": false + }, + "u256": { + "properties": { + "args": { + "additionalProperties": false, + "properties": { + "u256": { + "$ref": "#/definitions/U256" + } + }, + "type": "object", + "required": [ + "u256" + ] + } + }, + "additionalProperties": false + }, + "i256": { + "properties": { + "args": { + "additionalProperties": false, + "properties": { + "i256": { + "$ref": "#/definitions/I256" + } + }, + "type": "object", + "required": [ + "i256" + ] + } + }, + "additionalProperties": false + }, + "string": { + "properties": { + "args": { + "additionalProperties": false, + "properties": { + "string": { + "$ref": "#/definitions/ScString" + } + }, + "type": "object", + "required": [ + "string" + ] + } + }, + "additionalProperties": false + }, + "tuple_strukt": { + "properties": { + "args": { + "additionalProperties": false, + "properties": { + "tuple_strukt": { + "$ref": "#/definitions/TupleStruct" + } + }, + "type": "object", + "required": [ + "tuple_strukt" + ] + } + }, + "additionalProperties": false + } + } +} +``` \ No newline at end of file diff --git a/package.json b/package.json index ee61466df..b1185a862 100644 --- a/package.json +++ b/package.json @@ -85,6 +85,7 @@ "@babel/register": "^7.23.7", "@definitelytyped/dtslint": "^0.1.2", "@istanbuljs/nyc-config-babel": "3.0.0", + "@rjsf/validator-ajv8": "^5.17.1", "@stellar/tsconfig": "^1.0.2", "@types/chai": "^4.3.6", "@types/detect-node": "^2.0.0", diff --git a/test/unit/spec/contract_spec.ts b/test/unit/spec/contract_spec.ts index 25f611b07..c39b03d76 100644 --- a/test/unit/spec/contract_spec.ts +++ b/test/unit/spec/contract_spec.ts @@ -1,6 +1,5 @@ import { xdr, Address, ContractSpec, Keypair } from "../../../lib"; import { JSONSchemaFaker } from "json-schema-faker"; - import spec from "../spec.json"; import { expect } from "chai"; @@ -41,6 +40,7 @@ describe("Can round trip custom types", function () { num: number = 100 ) { let funcSpec = spec.jsonSchema(funcName); + console.log(JSON.stringify(funcSpec, null, 2)); for (let i = 0; i < num; i++) { let arg = await JSONSchemaFaker.resolve(funcSpec)!; diff --git a/yarn.lock b/yarn.lock index 6a9dd30b2..75738af5b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1307,6 +1307,16 @@ optionalDependencies: npmlog "2 || ^3.1.0 || ^4.0.0" +"@rjsf/validator-ajv8@^5.17.1": + version "5.17.1" + resolved "https://registry.yarnpkg.com/@rjsf/validator-ajv8/-/validator-ajv8-5.17.1.tgz#9b5e4b22f3ab47316c7a19da22639812e4a91193" + integrity sha512-KdvHsjDQ60b04fqnoqhfkiCv7E4n4NIHli8QU8dtpuUAVS/TOqDuOtDJVz6bv/rd/QNROGpxlO/OCccE0rmxLQ== + dependencies: + ajv "^8.12.0" + ajv-formats "^2.1.1" + lodash "^4.17.21" + lodash-es "^4.17.21" + "@sinclair/typebox@^0.27.8": version "0.27.8" resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e" @@ -1877,7 +1887,7 @@ ajv@^6.12.3, ajv@^6.12.4, ajv@^6.12.5: json-schema-traverse "^0.4.1" uri-js "^4.2.2" -ajv@^8.0.0, ajv@^8.9.0: +ajv@^8.0.0, ajv@^8.12.0, ajv@^8.9.0: version "8.12.0" resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.12.0.tgz#d1a0527323e22f53562c567c00991577dfbe19d1" integrity sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA== @@ -4978,6 +4988,11 @@ locate-path@^7.1.0: dependencies: p-locate "^6.0.0" +lodash-es@^4.17.21: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.21.tgz#43e626c46e6591b7750beb2b50117390c609e3ee" + integrity sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw== + lodash._baseclone@~4.5.0: version "4.5.7" resolved "https://registry.yarnpkg.com/lodash._baseclone/-/lodash._baseclone-4.5.7.tgz#ce42ade08384ef5d62fa77c30f61a46e686f8434"