Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

json: $ref + object overhaul (https & recursive $refs, mix properties & allOf) #8199

Open
wants to merge 15 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
json: restrict external refs to https, remove allowFetch options
  • Loading branch information
ochafik committed Jun 28, 2024
commit b6abfdb5fe0f8fac4780840e4de93c194903a1d1
5 changes: 4 additions & 1 deletion common/json-schema-to-grammar.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -838,10 +838,13 @@ class SchemaConverter {
auto it = _external_refs.find(url);
if (it != _external_refs.end()) {
target = it->second;
} else {
} else if (url.rfind("https://", 0) == 0) {
// Fetch the referenced schema and resolve its refs
target = _fetch_json(url);
_external_refs[url] = target;
} else {
_errors.push_back("Error resolving ref " + ref + ": unsupported url scheme");
return {json(), "", false};
}
}
if (parts.size() == 1) {
Expand Down
26 changes: 12 additions & 14 deletions examples/json_schema_to_grammar.py
Original file line number Diff line number Diff line change
Expand Up @@ -235,9 +235,8 @@ def __init__(self, content: str, deps: list = None):


class SchemaConverter:
def __init__(self, *, prop_order, allow_fetch, dotall, raw_pattern):
def __init__(self, *, prop_order, dotall, raw_pattern):
self._prop_order = prop_order
self._allow_fetch = allow_fetch
self._dotall = dotall
self._raw_pattern = raw_pattern
self._rules = {
Expand Down Expand Up @@ -507,7 +506,7 @@ def __init__(self, target: Any, name: str, is_local: bool):

def _resolve_ref(self, ref: str):
parts = ref.split('#')
assert len(parts) == 2, f'Unsupported ref: {ref}'
assert len(parts) <= 2, f'Unsupported ref: {ref}'
url = parts[0]
target = None
is_local = not url
Expand All @@ -518,13 +517,18 @@ def _resolve_ref(self, ref: str):
target = self._external_refs.get(url)
if target is None:
# Fetch the referenced schema and resolve its refs
target = self._fetch_json(url)
assert url.startswith("https://"), f"Error resolving ref {ref}: unsupported url scheme"
import requests
target = requests.get(url).json()
self._external_refs[url] = target

tokens = parts[1].split('/')
for sel in tokens[1:]:
assert target is not None and sel in target, f'Error resolving ref {ref}: {sel} not in {target}'
target = target[sel]
if len(parts) == 1:
return self.ResolvedRef(target, '', is_local)
else:
tokens = parts[1].split('/')
for sel in tokens[1:]:
assert target is not None and sel in target, f'Error resolving ref {ref}: {sel} not in {target}'
target = target[sel]

return self.ResolvedRef(target, tokens[-1] if tokens else '', is_local)

Expand Down Expand Up @@ -760,11 +764,6 @@ def main(args_in = None):
given precedence over optional properties.
'''
)
parser.add_argument(
'--allow-fetch',
action='store_true',
default=False,
help='Whether to allow fetching referenced schemas over HTTPS')
parser.add_argument(
'--dotall',
action='store_true',
Expand Down Expand Up @@ -792,7 +791,6 @@ def main(args_in = None):
schema = json.load(f)
converter = SchemaConverter(
prop_order={name: idx for idx, name in enumerate(args.prop_order)},
allow_fetch=args.allow_fetch,
dotall=args.dotall,
raw_pattern=args.raw_pattern)
converter.visit(schema, '')
Expand Down
4 changes: 3 additions & 1 deletion examples/server/public/json-schema-to-grammar.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,6 @@ const ESCAPED_IN_REGEXPS_BUT_NOT_IN_LITERALS = new Set('^$.[]()|{}*+?');
export class SchemaConverter {
constructor(options) {
this._propOrder = options.prop_order || {};
this._allowFetch = options.allow_fetch || false;
this._dotall = options.dotall || false;
this._rules = {'space': SPACE_RULE};
this._refs = {};
Expand Down Expand Up @@ -558,6 +557,9 @@ export class SchemaConverter {
target = this._externalRefs.get(url);
if (target === undefined) {
// Fetch the referenced schema and resolve its refs
if (!url.startsWith('https://')) {
throw new Error(`Error resolving ref ${ref}: unsupported url scheme`);
}
target = this._fetchJson(url);
this._externalRefs.set(url, target);
}
Expand Down
8 changes: 6 additions & 2 deletions tests/run-json-schema-to-grammar.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@ import { readFileSync } from "fs"
import { SchemaConverter } from "../examples/server/public/json-schema-to-grammar.mjs"

const [, , file] = process.argv
const url = `file://${file}`
const schema = JSON.parse(readFileSync(file, "utf8"));
let schema;
if (file.startsWith('https://')) {
schema = await (await fetch(file)).json()
} else {
schema = JSON.parse(readFileSync(file, "utf8"));
}
const converter = new SchemaConverter({})
converter.visit(schema, '')
console.log(converter.formatGrammar())