diff --git a/README.md b/README.md
index 8782158..885d47c 100644
--- a/README.md
+++ b/README.md
@@ -4,7 +4,7 @@ This is a very basic node based CLI for converting OpenAPI schema to JSON Schema
```
-Usage: openapi-jsonschema [options]
+Usage: kubeopenapi-jsonschema [options]
Options:
-t, --type [type] set type of input, can be either yaml or json (default: "yaml")
@@ -14,6 +14,7 @@ Options:
-o [output-format] output format (default: "json")
--o-filter [output-filter] output filter query
--silent skip output (default: false)
+ --resolve [resolve-filter] root of the OpenAPI spec to resolve the $ref. It is important to note that this jsonpath MUST evaluate to one object (default: "")
-h, --help display help for command
```
@@ -22,9 +23,18 @@ Options:
Download the binaries from the github releases. Only linux-x64, darwin-x64 and windows-x64 binaries are released
```bash
-openapi-jsonschema --location ./istio.yaml -t yaml --filter '$[?(@.kind=="CustomResourceDefinition" && @.spec.names.kind=="EnvoyFilter")]..validation.openAPIV3Schema.properties.spec' -o yaml --o-filter '$[0]'
+kubeopenapi-jsonschema --location ./k8s.json -f '$.definitions' -t json --o-filter '$[0][?(@["x-kubernetes-group-version-kind"][0].kind=="Deployment")].properties.spec' --resolve "$"
```
+The above will consume kubernetes open API schema and will produce schema for Kubernetes `Deployment`
+
+
+```bash
+kubeopenapi-jsonschema --location ./istio.yaml -t yaml --filter '$[?(@.kind=="CustomResourceDefinition")]..schema.openAPIV3Schema.properties.spec' --o-filter '$'
+```
+
+The above will consume istio CRD manifest and will produce schema for all of the CustomResourceDefinition objects
+
## Join the service mesh community!
diff --git a/helper/output.js b/helper/output.js
index b29fb13..fbf7e47 100644
--- a/helper/output.js
+++ b/helper/output.js
@@ -4,15 +4,17 @@ const jp = require("jsonpath");
/**
* Output takes in the data that needs to be printed and
* an output format
- * @param {*} data
+ * @param {Promise} data
* @param {"json" | "yaml"} format output format
*/
function Output(data, format = "json", filter = "$", silent = false) {
if (silent) return;
- data = jp.query(data, filter);
- if (format === "yaml") return console.log(dump(data));
- if (format === "json") return console.log(JSON.stringify(data, null, 2));
+ data.then(data => {
+ data = jp.query(data, filter);
+ if (format === "yaml") return console.log(dump(data));
+ if (format === "json") return console.log(JSON.stringify(data, null, 2));
+ })
}
module.exports = Output;
diff --git a/helper/toJSONSchema.js b/helper/toJSONSchema.js
index 4ba347b..02ffa8b 100644
--- a/helper/toJSONSchema.js
+++ b/helper/toJSONSchema.js
@@ -5,6 +5,7 @@ const { readFileSync, writeFileSync } = require("fs");
const { tmpdir } = require("os");
const path = require("path");
const jp = require("jsonpath");
+const { Resolver } = require("@stoplight/json-ref-resolver");
/**
* convertAllSchemasToJSONSchema takes in the OpenAPIV3 Schemas in an array
@@ -23,17 +24,39 @@ function convertAllSchemasToJSONSchema(schemas) {
* readSchema will read schema file from the given location, it expects
* the schema to be in JSON format
*
- * readSchema will also apply the given jsonpath filter to the read schema
- * and will return only the filtered JSONs
+ * readSchema will also resolve the references if a resolveQuery is passed
* @param {string} location
- * @param {string} query jsonpath based query
- * @returns {any[]}
+ * @param {string} resolveQuery jsonpath based query - must resolve to EXACTLY one match or else is ignored
+ * @returns {Promise}
*/
-function readSchema(location, query) {
+async function readSchema(location, resolveQuery) {
const data = readFileSync(location, "utf-8");
const parsed = JSON.parse(data);
- return jp.query(parsed, query);
+ if (resolveQuery) {
+ const inner = jp.query(parsed, resolveQuery);
+
+ if (inner.length !== 1) return parsed;
+
+ const resolver = new Resolver();
+ const resolved = await resolver.resolve(inner[0], {});
+
+ if (resolved.errors.length) console.error(resolved.errors);
+
+ return resolved.result;
+ }
+
+ return parsed;
+}
+
+/**
+ * filterSchemas takes in an array of schemas and will return an array of filtered schemas
+ * @param {Array} schemas - OpenAPI schema in JSON format
+ * @param {string} query jsonpath based query to filter out the data
+ * @returns {Array}
+ */
+function filterSchemas(schemas, query) {
+ return jp.query(schemas, query);
}
/**
@@ -72,16 +95,19 @@ function setupFiles(location, type) {
* @param {string} location location of the schemas in open api v3 format
* @param {"yaml" | "json"} type encoding in which the openapi schema is present
* @param {string} query jsonpath query to filter the read schemas
+ * @param {string} resolve jsonpath query to reach to the root of the openAPI spec
*/
-function ToJSONSchema(location, type = "yaml", query = "") {
+async function ToJSONSchema(location, type = "yaml", query = "", resolve = "") {
if (type !== "yaml" && type !== "json")
throw Error('invalid type received: can be either "yaml" or "json"');
const source = setupFiles(location, type);
- const schemas = readSchema(source, query);
+ const schemas = await readSchema(source, resolve);
+
+ const filtered = filterSchemas(schemas, query);
- return convertAllSchemasToJSONSchema(schemas);
+ return convertAllSchemasToJSONSchema(filtered);
}
module.exports = ToJSONSchema;
diff --git a/index.js b/index.js
index 3006fcd..41966c7 100644
--- a/index.js
+++ b/index.js
@@ -15,7 +15,8 @@ program
.option("--kubernetes", "enable kubernetes specific filters", false)
.option("-o [output-format]", "output format", "json")
.option("--o-filter [output-filter]", "output filter query")
- .option("--silent", "skip output", false);
+ .option("--silent", "skip output", false)
+ .option("--resolve [resolve-filter]", "root of the OpenAPI spec to resolve the $ref", "")
program.parse(process.argv);
@@ -25,7 +26,8 @@ Output(
ToJSONSchema(
options.location,
options.type,
- CreateQuery(options.filter, options.kubernetes)
+ CreateQuery(options.filter, options.kubernetes),
+ options.resolve,
),
options.o,
options.oFilter,
diff --git a/package-lock.json b/package-lock.json
index 4afb38b..1e511cb 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -12,6 +12,65 @@
"fast-deep-equal": "^3.1.3"
}
},
+ "@stoplight/json": {
+ "version": "3.17.0",
+ "resolved": "https://registry.npmjs.org/@stoplight/json/-/json-3.17.0.tgz",
+ "integrity": "sha512-WW0z2bb0D4t8FTl+zNTCu46J8lEOsrUhBPgwEYQ3Ri2Y0MiRE4U1/9ZV8Ki+pIJznZgY9i42bbFwOBxyZn5/6w==",
+ "requires": {
+ "@stoplight/ordered-object-literal": "^1.0.2",
+ "@stoplight/types": "^12.3.0",
+ "jsonc-parser": "~2.2.1",
+ "lodash": "^4.17.21",
+ "safe-stable-stringify": "^1.1"
+ }
+ },
+ "@stoplight/json-ref-resolver": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/@stoplight/json-ref-resolver/-/json-ref-resolver-3.1.3.tgz",
+ "integrity": "sha512-SgoKXwVnlpIZUyAFX4W79eeuTWvXmNlMfICZixL16GZXnkjcW+uZnfmAU0ZIjcnaTgaI4mjfxn8LAP2KR6Cr0A==",
+ "requires": {
+ "@stoplight/json": "^3.17.0",
+ "@stoplight/path": "^1.3.2",
+ "@stoplight/types": "^12.3.0",
+ "@types/urijs": "^1.19.16",
+ "dependency-graph": "~0.11.0",
+ "fast-memoize": "^2.5.2",
+ "immer": "^9.0.6",
+ "lodash.get": "^4.4.2",
+ "lodash.set": "^4.3.2",
+ "tslib": "^2.3.1",
+ "urijs": "^1.19.6"
+ }
+ },
+ "@stoplight/ordered-object-literal": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/@stoplight/ordered-object-literal/-/ordered-object-literal-1.0.2.tgz",
+ "integrity": "sha512-0ZMS/9sNU3kVo/6RF3eAv7MK9DY8WLjiVJB/tVyfF2lhr2R4kqh534jZ0PlrFB9CRXrdndzn1DbX6ihKZXft2w=="
+ },
+ "@stoplight/path": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/@stoplight/path/-/path-1.3.2.tgz",
+ "integrity": "sha512-lyIc6JUlUA8Ve5ELywPC8I2Sdnh1zc1zmbYgVarhXIp9YeAB0ReeqmGEOWNtlHkbP2DAA1AL65Wfn2ncjK/jtQ=="
+ },
+ "@stoplight/types": {
+ "version": "12.3.0",
+ "resolved": "https://registry.npmjs.org/@stoplight/types/-/types-12.3.0.tgz",
+ "integrity": "sha512-hgzUR1z5BlYvIzUeFK5pjs5JXSvEutA9Pww31+dVicBlunsG1iXopDx/cvfBY7rHOrgtZDuvyeK4seqkwAZ6Cg==",
+ "requires": {
+ "@types/json-schema": "^7.0.4",
+ "utility-types": "^3.10.0"
+ }
+ },
+ "@types/json-schema": {
+ "version": "7.0.9",
+ "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz",
+ "integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ=="
+ },
+ "@types/urijs": {
+ "version": "1.19.17",
+ "resolved": "https://registry.npmjs.org/@types/urijs/-/urijs-1.19.17.tgz",
+ "integrity": "sha512-ShIlp+8iNGo/yVVfYFoNRqUiaE9wMCzsSl85qTg2/C5l56BTJokU7QeMgVBQ9xhcyhWQP0zGXPBZPPvEG/sRmQ=="
+ },
"argparse": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
@@ -27,6 +86,11 @@
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz",
"integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ="
},
+ "dependency-graph": {
+ "version": "0.11.0",
+ "resolved": "https://registry.npmjs.org/dependency-graph/-/dependency-graph-0.11.0.tgz",
+ "integrity": "sha512-JeMq7fEshyepOWDfcfHK06N3MhyPhz++vtqWhMT5O9A3K42rdsEDpfdVqjaqaAhsw6a+ZqeDvQVtD0hFHQWrzg=="
+ },
"escodegen": {
"version": "1.14.3",
"resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz",
@@ -71,6 +135,16 @@
"resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
"integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc="
},
+ "fast-memoize": {
+ "version": "2.5.2",
+ "resolved": "https://registry.npmjs.org/fast-memoize/-/fast-memoize-2.5.2.tgz",
+ "integrity": "sha512-Ue0LwpDYErFbmNnZSF0UH6eImUwDmogUO1jyE+JbN2gsQz/jICm1Ve7t9QT0rNSsfJt+Hs4/S3GnsDVjL4HVrw=="
+ },
+ "immer": {
+ "version": "9.0.6",
+ "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.6.tgz",
+ "integrity": "sha512-G95ivKpy+EvVAnAab4fVa4YGYn24J1SpEktnJX7JJ45Bd7xqME/SCplFzYFmTbrkwZbQ4xJK1xMTUYBkN6pWsQ=="
+ },
"js-yaml": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
@@ -79,6 +153,11 @@
"argparse": "^2.0.1"
}
},
+ "jsonc-parser": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-2.2.1.tgz",
+ "integrity": "sha512-o6/yDBYccGvTz1+QFevz6l6OBZ2+fMVu2JZ9CIhzsYRX4mjaK5IyX9eldUdCmga16zlgQxyrj5pt9kzuj2C02w=="
+ },
"jsonpath": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/jsonpath/-/jsonpath-1.1.1.tgz",
@@ -98,6 +177,21 @@
"type-check": "~0.3.2"
}
},
+ "lodash": {
+ "version": "4.17.21",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
+ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
+ },
+ "lodash.get": {
+ "version": "4.4.2",
+ "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz",
+ "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk="
+ },
+ "lodash.set": {
+ "version": "4.3.2",
+ "resolved": "https://registry.npmjs.org/lodash.set/-/lodash.set-4.3.2.tgz",
+ "integrity": "sha1-2HV7HagH3eJIFrDWqEvqGnYjCyM="
+ },
"optionator": {
"version": "0.8.3",
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz",
@@ -116,6 +210,11 @@
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz",
"integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ="
},
+ "safe-stable-stringify": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-1.1.1.tgz",
+ "integrity": "sha512-ERq4hUjKDbJfE4+XtZLFPCDi8Vb1JqaxAPTxWFLBx8XcAlf9Bda/ZJdVezs/NAfsMQScyIlUMx+Yeu7P7rx5jw=="
+ },
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
@@ -130,6 +229,11 @@
"escodegen": "^1.8.1"
}
},
+ "tslib": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
+ "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw=="
+ },
"type-check": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz",
@@ -143,6 +247,16 @@
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.12.1.tgz",
"integrity": "sha512-hEQt0+ZLDVUMhebKxL4x1BTtDY7bavVofhZ9KZ4aI26X9SRaE+Y3m83XUL1UP2jn8ynjndwCCpEHdUG+9pP1Tw=="
},
+ "urijs": {
+ "version": "1.19.7",
+ "resolved": "https://registry.npmjs.org/urijs/-/urijs-1.19.7.tgz",
+ "integrity": "sha512-Id+IKjdU0Hx+7Zx717jwLPsPeUqz7rAtuVBRLLs+qn+J2nf9NGITWVCxcijgYxBqe83C7sqsQPs6H1pyz3x9gA=="
+ },
+ "utility-types": {
+ "version": "3.10.0",
+ "resolved": "https://registry.npmjs.org/utility-types/-/utility-types-3.10.0.tgz",
+ "integrity": "sha512-O11mqxmi7wMKCo6HKFt5AhO4BwY3VV68YU07tgxfz8zJTIxr4BpsezN49Ffwy9j3ZpwwJp4fkRwjRzq3uWE6Rg=="
+ },
"word-wrap": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
diff --git a/package.json b/package.json
index fcbc375..e2830cc 100644
--- a/package.json
+++ b/package.json
@@ -10,6 +10,7 @@
"license": "MIT",
"dependencies": {
"@openapi-contrib/openapi-schema-to-json-schema": "^3.1.1",
+ "@stoplight/json-ref-resolver": "^3.1.3",
"commander": "^7.2.0",
"js-yaml": "^4.1.0",
"jsonpath": "^1.1.1"