From e4263805c3725c12eb8a8532fc57dbc90c8bc864 Mon Sep 17 00:00:00 2001 From: Nokome Bentley Date: Fri, 31 Jan 2020 11:58:58 +1300 Subject: [PATCH] feat(JSON Schema): Allow for inline $refs --- package-lock.json | 103 ++++++++++++++++++++++++++++++++++++++ package.json | 1 + ts/bindings/typescript.ts | 11 +++- ts/helpers.ts | 11 ++-- ts/schema.ts | 5 +- 5 files changed, 126 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2b77ce0f..95edef1e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5265,6 +5265,73 @@ "integrity": "sha512-ykCapub81y9mpH1zpLRl1UqwkIWFrrAdQUlz0sXiK6AtGgg3gLagB6eIKKeI3rgMX8el1KcQaTPSkRhCa6+07g==", "dev": true }, + "@stoplight/json": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@stoplight/json/-/json-3.4.0.tgz", + "integrity": "sha512-Ljjj11Wa+MusOMeXTehLbuJQJe8CG3sovCGcD/6c8Zyz1n39EsDLZwJIpQ6/DQAdKChiJGWdcULsrGdheFaiLg==", + "dev": true, + "requires": { + "@stoplight/types": "^11.1.1", + "jsonc-parser": "~2.2.0", + "lodash": "^4.17.15", + "safe-stable-stringify": "^1.1" + }, + "dependencies": { + "lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", + "dev": true + } + } + }, + "@stoplight/json-ref-resolver": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@stoplight/json-ref-resolver/-/json-ref-resolver-3.0.8.tgz", + "integrity": "sha512-Ns0jsq/qqDdhpd9iPXLUx5YRddUF5gfg0zkxmskV+srXrWlndg0S50dkX/GF0yrExKjru4veqW8Xx+Wo0noPtg==", + "dev": true, + "requires": { + "@stoplight/json": "^3.1.2", + "@stoplight/path": "^1.3.0", + "@stoplight/types": "^11.1.1", + "@types/urijs": "^1.19", + "dependency-graph": "~0.8.0", + "fast-memoize": "^2.5.1", + "immer": "^4.0.1", + "lodash": "^4.17.15", + "tslib": "^1.10.0", + "urijs": "~1.19.1" + }, + "dependencies": { + "immer": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/immer/-/immer-4.0.2.tgz", + "integrity": "sha512-Q/tm+yKqnKy4RIBmmtISBlhXuSDrB69e9EKTYiIenIKQkXBQir43w+kN/eGiax3wt1J0O1b2fYcNqLSbEcXA7w==", + "dev": true + }, + "lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", + "dev": true + } + } + }, + "@stoplight/path": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@stoplight/path/-/path-1.3.0.tgz", + "integrity": "sha512-t74/MHMgmFVMQhdQ/2Q766GryNTIW8McH8+vB25oeoBhYKTOrJ/wPDt+OCxIWHPUlcSi2fTWa4FKQ8qgmP2jVA==", + "dev": true + }, + "@stoplight/types": { + "version": "11.4.0", + "resolved": "https://registry.npmjs.org/@stoplight/types/-/types-11.4.0.tgz", + "integrity": "sha512-kMh1Sv7bA8BdbUaRXsRyi2K8Y5PzPOUWNSjB4qKOi0P6+dLczjlKggEIw9Xzuu1tCgBFdEvNwjnYDey0iqgeZQ==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.3" + } + }, "@stroncium/procfs": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@stroncium/procfs/-/procfs-1.0.0.tgz", @@ -5588,6 +5655,12 @@ "integrity": "sha512-FvUupuM3rlRsRtCN+fDudtmytGO6iHJuuRKS1Ss0pG5z8oX0diNEw94UEL7hgDbpN94rgaK5R7sWm6RrSkZuAQ==", "dev": true }, + "@types/urijs": { + "version": "1.19.5", + "resolved": "https://registry.npmjs.org/@types/urijs/-/urijs-1.19.5.tgz", + "integrity": "sha512-LAyzQkr9rZDoHrv8xRcHStLo8Z4PbP3ZHMqw8WRr1T7Jn4O1z13Qkv+ed9e12pBXWaaqsBj1l4ADU/FlA/jn3w==", + "dev": true + }, "@types/ws": { "version": "6.0.4", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-6.0.4.tgz", @@ -9278,6 +9351,12 @@ "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", "dev": true }, + "dependency-graph": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/dependency-graph/-/dependency-graph-0.8.1.tgz", + "integrity": "sha512-g213uqF8fyk40W8SBjm079n3CZB4qSpCrA2ye1fLGzH/4HEgB6tzuW2CbLE7leb4t45/6h44Ud59Su1/ROTfqw==", + "dev": true + }, "deprecation": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz", @@ -11158,6 +11237,12 @@ "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", "dev": true }, + "fast-memoize": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/fast-memoize/-/fast-memoize-2.5.1.tgz", + "integrity": "sha512-xdmw296PCL01tMOXx9mdJSmWY29jQgxyuZdq0rEHMu+Tpe1eOEtCycoG6chzlcrWsNgpZP7oL8RiQr7+G6Bl6g==", + "dev": true + }, "fast-redact": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-2.0.0.tgz", @@ -16918,6 +17003,12 @@ "minimist": "^1.2.0" } }, + "jsonc-parser": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-2.2.0.tgz", + "integrity": "sha512-4fLQxW1j/5fWj6p78vAlAafoCKtuBm6ghv+Ij5W2DrDx0qE+ZdEl2c6Ko1mgJNF5ftX1iEWQQ4Ap7+3GlhjkOA==", + "dev": true + }, "jsonfile": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", @@ -25317,6 +25408,12 @@ } } }, + "safe-stable-stringify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-1.1.0.tgz", + "integrity": "sha512-8h+96qSufNQrydRPzbHms38VftQQSRGbqUkaIMWUBWN4/N8sLNALIALa8KmFcQ8P/a9uzMkA+KY04Rj5WQiXPA==", + "dev": true + }, "safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -28408,6 +28505,12 @@ "punycode": "^2.1.0" } }, + "urijs": { + "version": "1.19.2", + "resolved": "https://registry.npmjs.org/urijs/-/urijs-1.19.2.tgz", + "integrity": "sha512-s/UIq9ap4JPZ7H1EB5ULo/aOUbWqfDi7FKzMC2Nz+0Si8GiT1rIEaprt8hy3Vy2Ex2aJPpOQv4P4DuOZ+K1c6w==", + "dev": true + }, "urix": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", diff --git a/package.json b/package.json index 74e4c783..45b6d86f 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,7 @@ "@stencila/dev-config": "1.4.10", "@stencila/encoda": "0.83.2", "@stencila/logga": "2.1.0", + "@stoplight/json-ref-resolver": "3.0.8", "@types/fs-extra": "8.0.1", "@types/jest": "24.9.1", "@types/js-yaml": "3.12.2", diff --git a/ts/bindings/typescript.ts b/ts/bindings/typescript.ts index c51224d3..77db5e8c 100644 --- a/ts/bindings/typescript.ts +++ b/ts/bindings/typescript.ts @@ -176,7 +176,7 @@ const docComment = (description?: string, tags: string[] = []): string => { const schemaToType = (schema: Schema): string => { const { type, anyOf, allOf, $ref } = schema - if ($ref !== undefined) return `${$ref.replace('.schema.json', '')}` + if ($ref !== undefined) return $refToType($ref) if (anyOf !== undefined) return anyOfToType(anyOf) if (allOf !== undefined) return allOfToType(allOf) if (schema.enum !== undefined) return enumToType(schema.enum) @@ -192,6 +192,15 @@ const schemaToType = (schema: Schema): string => { throw new Error(`Unhandled schema: ${JSON.stringify(schema)}`) } +/** + * Convert a schema `$ref` (reference) to a Typescript type + * + * Assume that any `$ref`s refer to a type defined in the file. + */ +const $refToType = ($ref: string): string => { + return $ref.replace('.schema.json', '') +} + /** * Convert a schema with the `anyOf` property to a Typescript `Union` type. */ diff --git a/ts/helpers.ts b/ts/helpers.ts index 1bef8c88..9de9f9cb 100644 --- a/ts/helpers.ts +++ b/ts/helpers.ts @@ -3,6 +3,7 @@ * generating languages bindings and documentation. */ +import { Resolver } from '@stoplight/json-ref-resolver' import fs from 'fs-extra' import globby from 'globby' import path from 'path' @@ -11,7 +12,8 @@ import Schema from './schema-interface' export { default as Schema } from './schema-interface' /** - * Read the schemas from `public/*.schema.json`. + * Read the schemas from `public/*.schema.json` and dereference + * any inline references. */ export async function readSchemas( glob: string | string[] = path.join( @@ -21,10 +23,13 @@ export async function readSchemas( '*.schema.json' ) ): Promise { - // Read in the schemas const files = await globby(glob) return Promise.all( - files.map(async (file: string): Promise => fs.readJSON(file)) + files.map(async (file: string) => { + const resolver = new Resolver() + const resolved = await resolver.resolve(await fs.readJSON(file)) + return resolved.result + }) ) } diff --git a/ts/schema.ts b/ts/schema.ts index 87d0bdd3..beb2f88c 100644 --- a/ts/schema.ts +++ b/ts/schema.ts @@ -233,6 +233,7 @@ const checkSchema = ( if ( key === '$ref' && typeof child === 'string' && + !child.startsWith('#') && !child.endsWith('Types') && !schemas.has(child) ) { @@ -383,13 +384,15 @@ const processSchema = (schemas: Map, schema: Schema): void => { } } - // Replace any `$ref`s to YAML with a ref to the JSON generated in this function + // Replace any `$ref`s that are not internal (i.e. starting with `#`) + // with a ref to the JSON file generated by this function const walk = (node: Schema): void => { if (typeof node !== 'object') return for (const [key, child] of Object.entries(node)) { if ( key === '$ref' && typeof child === 'string' && + !child.startsWith('#') && !child.endsWith('.schema.json') ) node[key] = child + '.schema.json'