referencing-0.37.3/.cargo_vcs_info.json0000644000000001730000000000100134160ustar { "git": { "sha1": "4000ae89776e8608aef21b85dce981aac266eed3" }, "path_in_vcs": "crates/jsonschema-referencing" }referencing-0.37.3/Cargo.lock0000644000000675120000000000100114030ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 4 [[package]] name = "ahash" version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" dependencies = [ "cfg-if", "getrandom 0.3.4", "once_cell", "serde", "version_check", "zerocopy", ] [[package]] name = "aho-corasick" version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" dependencies = [ "memchr", ] [[package]] name = "allocator-api2" version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "anes" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] name = "anstyle" version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" [[package]] name = "anyhow" version = "1.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" [[package]] name = "approx" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" dependencies = [ "num-traits", ] [[package]] name = "async-trait" version = "0.1.89" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "autocfg" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "bitflags" version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" [[package]] name = "borrow-or-share" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc0b364ead1874514c8c2855ab558056ebfeb775653e7ae45ff72f28f8f3166c" [[package]] name = "bumpalo" version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" [[package]] name = "cast" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" version = "1.2.48" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c481bdbf0ed3b892f6f806287d72acd515b352a4ec27a208489b8c1bc839633a" dependencies = [ "find-msvc-tools", "shlex", ] [[package]] name = "cfg-if" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "cfg_aliases" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "ciborium" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" dependencies = [ "ciborium-io", "ciborium-ll", "serde", ] [[package]] name = "ciborium-io" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" [[package]] name = "ciborium-ll" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" dependencies = [ "ciborium-io", "half", ] [[package]] name = "clap" version = "4.5.53" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" version = "4.5.53" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00" dependencies = [ "anstyle", "clap_lex", ] [[package]] name = "clap_lex" version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" [[package]] name = "codspeed" version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3b847e05a34be5c38f3f2a5052178a3bd32e6b5702f3ea775efde95c483a539" dependencies = [ "anyhow", "cc", "colored", "getrandom 0.2.16", "glob", "libc", "nix", "serde", "serde_json", "statrs", ] [[package]] name = "codspeed-criterion-compat" version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30a0e2a53beb18dec493ec133f226e0d35e8bb2fdc638ccf7351696eabee416c" dependencies = [ "clap", "codspeed", "codspeed-criterion-compat-walltime", "colored", "regex", ] [[package]] name = "codspeed-criterion-compat-walltime" version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f652d6e6d40bba0f5c244744db94d92a26b7f083df18692df88fb0772f1c793" dependencies = [ "anes", "cast", "ciborium", "clap", "codspeed", "criterion-plot 0.5.0", "is-terminal", "itertools 0.10.5", "num-traits", "once_cell", "oorandom", "regex", "serde", "serde_derive", "serde_json", "tinytemplate", "walkdir", ] [[package]] name = "colored" version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" dependencies = [ "lazy_static", "windows-sys 0.59.0", ] [[package]] name = "criterion" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1c047a62b0cc3e145fa84415a3191f628e980b194c2755aa12300a4e6cbd928" dependencies = [ "anes", "cast", "ciborium", "clap", "criterion-plot 0.6.0", "itertools 0.13.0", "num-traits", "oorandom", "regex", "serde", "serde_json", "tinytemplate", "walkdir", ] [[package]] name = "criterion-plot" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" dependencies = [ "cast", "itertools 0.10.5", ] [[package]] name = "criterion-plot" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b1bcc0dc7dfae599d84ad0b1a55f80cde8af3725da8313b528da95ef783e338" dependencies = [ "cast", "itertools 0.13.0", ] [[package]] name = "crunchy" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" [[package]] name = "either" version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "equivalent" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "find-msvc-tools" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" [[package]] name = "fluent-uri" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc74ac4d8359ae70623506d512209619e5cf8f347124910440dbc221714b328e" dependencies = [ "borrow-or-share", "ref-cast", "serde", ] [[package]] name = "foldhash" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" [[package]] name = "futures" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" dependencies = [ "futures-channel", "futures-core", "futures-executor", "futures-io", "futures-sink", "futures-task", "futures-util", ] [[package]] name = "futures-channel" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", "futures-sink", ] [[package]] name = "futures-core" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] name = "futures-executor" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" dependencies = [ "futures-core", "futures-task", "futures-util", ] [[package]] name = "futures-io" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] name = "futures-macro" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "futures-sink" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" [[package]] name = "futures-task" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" [[package]] name = "futures-util" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-channel", "futures-core", "futures-io", "futures-macro", "futures-sink", "futures-task", "memchr", "pin-project-lite", "pin-utils", "slab", ] [[package]] name = "getrandom" version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", "js-sys", "libc", "wasi", "wasm-bindgen", ] [[package]] name = "getrandom" version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", "js-sys", "libc", "r-efi", "wasip2", "wasm-bindgen", ] [[package]] name = "glob" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" [[package]] name = "half" version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" dependencies = [ "cfg-if", "crunchy", "zerocopy", ] [[package]] name = "hashbrown" version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" dependencies = [ "allocator-api2", "equivalent", "foldhash", ] [[package]] name = "hermit-abi" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" [[package]] name = "is-terminal" version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" dependencies = [ "hermit-abi", "libc", "windows-sys 0.61.2", ] [[package]] name = "itertools" version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" dependencies = [ "either", ] [[package]] name = "itertools" version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" dependencies = [ "either", ] [[package]] name = "itoa" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "js-sys" version = "0.3.83" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8" dependencies = [ "once_cell", "wasm-bindgen", ] [[package]] name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" version = "0.2.177" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" [[package]] name = "lock_api" version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" dependencies = [ "scopeguard", ] [[package]] name = "memchr" version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" [[package]] name = "nix" version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" dependencies = [ "bitflags", "cfg-if", "cfg_aliases", "libc", ] [[package]] name = "num-traits" version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", ] [[package]] name = "once_cell" version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "oorandom" version = "11.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" [[package]] name = "parking_lot" version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" dependencies = [ "lock_api", "parking_lot_core", ] [[package]] name = "parking_lot_core" version = "0.9.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", "windows-link", ] [[package]] name = "percent-encoding" version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pin-project-lite" version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" [[package]] name = "pin-utils" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "proc-macro2" version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" dependencies = [ "proc-macro2", ] [[package]] name = "r-efi" version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] name = "redox_syscall" version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ "bitflags", ] [[package]] name = "ref-cast" version = "1.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" dependencies = [ "ref-cast-impl", ] [[package]] name = "ref-cast-impl" version = "1.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "referencing" version = "0.37.3" dependencies = [ "ahash", "async-trait", "codspeed-criterion-compat", "criterion", "fluent-uri", "futures", "getrandom 0.2.16", "getrandom 0.3.4", "hashbrown", "parking_lot", "percent-encoding", "serde_json", "test-case", "tokio", ] [[package]] name = "regex" version = "1.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" dependencies = [ "aho-corasick", "memchr", "regex-automata", "regex-syntax", ] [[package]] name = "regex-automata" version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" dependencies = [ "aho-corasick", "memchr", "regex-syntax", ] [[package]] name = "regex-syntax" version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" [[package]] name = "rustversion" version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "ryu" version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" [[package]] name = "same-file" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" dependencies = [ "winapi-util", ] [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "serde" version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" dependencies = [ "serde_core", "serde_derive", ] [[package]] name = "serde_core" version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "serde_json" version = "1.0.145" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" dependencies = [ "itoa", "memchr", "ryu", "serde", "serde_core", ] [[package]] name = "shlex" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "slab" version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" [[package]] name = "smallvec" version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "statrs" version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a3fe7c28c6512e766b0874335db33c94ad7b8f9054228ae1c2abd47ce7d335e" dependencies = [ "approx", "num-traits", ] [[package]] name = "syn" version = "2.0.111" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "test-case" version = "3.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb2550dd13afcd286853192af8601920d959b14c401fcece38071d53bf0768a8" dependencies = [ "test-case-macros", ] [[package]] name = "test-case-core" version = "3.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adcb7fd841cd518e279be3d5a3eb0636409487998a4aff22f3de87b81e88384f" dependencies = [ "cfg-if", "proc-macro2", "quote", "syn", ] [[package]] name = "test-case-macros" version = "3.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c89e72a01ed4c579669add59014b9a524d609c0c88c6a585ce37485879f6ffb" dependencies = [ "proc-macro2", "quote", "syn", "test-case-core", ] [[package]] name = "tinytemplate" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" dependencies = [ "serde", "serde_json", ] [[package]] name = "tokio" version = "1.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" dependencies = [ "pin-project-lite", "tokio-macros", ] [[package]] name = "tokio-macros" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "unicode-ident" version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" [[package]] name = "version_check" version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "walkdir" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" dependencies = [ "same-file", "winapi-util", ] [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasip2" version = "1.0.1+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" dependencies = [ "wit-bindgen", ] [[package]] name = "wasm-bindgen" version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd" dependencies = [ "cfg-if", "once_cell", "rustversion", "wasm-bindgen-macro", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3" dependencies = [ "quote", "wasm-bindgen-macro-support", ] [[package]] name = "wasm-bindgen-macro-support" version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40" dependencies = [ "bumpalo", "proc-macro2", "quote", "syn", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4" dependencies = [ "unicode-ident", ] [[package]] name = "winapi-util" version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ "windows-sys 0.61.2", ] [[package]] name = "windows-link" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] name = "windows-sys" version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ "windows-targets", ] [[package]] name = "windows-sys" version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ "windows-link", ] [[package]] name = "windows-targets" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", "windows_i686_gnullvm", "windows_i686_msvc", "windows_x86_64_gnu", "windows_x86_64_gnullvm", "windows_x86_64_msvc", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "wit-bindgen" version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" [[package]] name = "zerocopy" version = "0.8.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ea879c944afe8a2b25fef16bb4ba234f47c694565e97383b36f3a878219065c" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" version = "0.8.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf955aa904d6040f70dc8e9384444cb1030aed272ba3cb09bbc4ab9e7c1f34f5" dependencies = [ "proc-macro2", "quote", "syn", ] referencing-0.37.3/Cargo.toml0000644000000052550000000000100114220ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" rust-version = "1.83.0" name = "referencing" version = "0.37.3" authors = ["Dmitry Dygalo "] build = false autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = "An implementation-agnostic JSON reference resolution library for Rust." readme = "README.md" license = "MIT" repository = "https://github.com/Stranger6667/jsonschema" [features] default = [] retrieve-async = [ "dep:async-trait", "dep:futures", ] [lib] name = "referencing" path = "src/lib.rs" [[test]] name = "suite" path = "tests/suite.rs" [[bench]] name = "anchor" path = "benches/anchor.rs" harness = false [[bench]] name = "pointer" path = "benches/pointer.rs" harness = false [[bench]] name = "registry" path = "benches/registry.rs" harness = false [[bench]] name = "subresources" path = "benches/subresources.rs" harness = false [dependencies.ahash] version = "0.8" features = ["serde"] [dependencies.async-trait] version = "0.1.86" optional = true [dependencies.fluent-uri] version = "0.4.1" features = ["serde"] [dependencies.futures] version = "0.3.31" optional = true [dependencies.hashbrown] version = "0.16" [dependencies.parking_lot] version = "0.12.3" [dependencies.percent-encoding] version = "2.3.1" [dependencies.serde_json] version = "1" [dev-dependencies.codspeed-criterion-compat] version = "4.1" default-features = false [dev-dependencies.criterion] version = "0.7" default-features = false [dev-dependencies.getrandom] version = "0.2" features = ["js"] [dev-dependencies.test-case] version = "3.3.1" [dev-dependencies.tokio] version = "1" features = [ "macros", "rt", ] [target.'cfg(target_arch = "wasm32")'.dependencies.getrandom] version = "0.3.4" features = ["wasm_js"] [lints.clippy] dbg_macro = "warn" empty_drop = "warn" empty_structs_with_brackets = "warn" exit = "warn" get_unwrap = "warn" module_name_repetitions = "allow" print_stderr = "warn" print_stdout = "warn" rc_buffer = "warn" rc_mutex = "warn" rest_pat_in_fully_bound_structs = "warn" similar_names = "allow" too_many_arguments = "allow" too_many_lines = "allow" [lints.clippy.pedantic] level = "warn" priority = -2 [lints.rust] unreachable_pub = "warn" unsafe_code = "warn" referencing-0.37.3/Cargo.toml.orig000064400000000000000000000027411046102023000151000ustar 00000000000000[package] name = "referencing" version = "0.37.3" description = "An implementation-agnostic JSON reference resolution library for Rust." readme = "README.md" rust-version.workspace = true edition.workspace = true authors.workspace = true repository.workspace = true license.workspace = true [features] default = [] retrieve-async = ["dep:async-trait", "dep:futures"] [target.'cfg(target_arch = "wasm32")'.dependencies] getrandom = { version = "0.3.4", features = ["wasm_js"] } [dependencies] ahash.workspace = true async-trait = { version = "0.1.86", optional = true } fluent-uri = { version = "0.4.1", features = ["serde"] } futures = { version = "0.3.31", optional = true } parking_lot = "0.12.3" percent-encoding = "2.3.1" serde_json.workspace = true hashbrown = "0.16" [dev-dependencies] benchmark = { path = "../benchmark/" } codspeed-criterion-compat = { version = "4.1", default-features = false } criterion = { version = "0.7", default-features = false } # This one is to make `wasm32-unknown-unknown` lints happy (codspeed depends on it) getrandom = { version = "0.2", features = ["js"] } referencing_testsuite = { package = "jsonschema-referencing-testsuite", path = "../jsonschema-referencing-testsuite/" } test-case = "3.3.1" tokio = { version = "1", features = ["macros", "rt"] } [lints] workspace = true [[bench]] harness = false name = "subresources" [[bench]] harness = false name = "pointer" [[bench]] harness = false name = "anchor" [[bench]] harness = false name = "registry" referencing-0.37.3/LICENSE000064400000000000000000000020631046102023000132130ustar 00000000000000MIT License Copyright (c) 2020-2025 Dmitry Dygalo Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. referencing-0.37.3/README.md000064400000000000000000000014261046102023000134670ustar 00000000000000# jsonschema-referencing [crates.io](https://crates.io/crates/referencing) [docs.rs](https://docs.rs/referencing) An implementation-agnostic JSON reference resolution library for Rust. ## Features and Design - Eagerly resolves all external references - Does not support on-demand resolving of external references ## Acknowledgements This crate is heavily inspired by the Python [`referencing`](https://github.com/python-jsonschema/referencing) package. We extend our gratitude to its authors for their excellent work and design. referencing-0.37.3/benches/anchor.rs000064400000000000000000000020421046102023000154320ustar 00000000000000use codspeed_criterion_compat::{ black_box, criterion_group, criterion_main, BenchmarkId, Criterion, }; use referencing::{Draft, Registry}; use serde_json::json; fn bench_anchor_lookup(c: &mut Criterion) { let data = json!({ "definitions": { "foo": { "id": "#foo", "foo": "bar" } } }); let resource = Draft::Draft4.create_resource(data); let registry = Registry::try_new("http://example.com/", resource).expect("Invalid registry input"); let mut group = c.benchmark_group("Anchor Lookup"); // Benchmark lookup of existing anchor group.bench_with_input( BenchmarkId::new("resolve", "small"), ®istry, |b, registry| { let resolver = registry .try_resolver("http://example.com/") .expect("Invalid base URI"); b.iter_with_large_drop(|| resolver.lookup(black_box("#foo"))); }, ); group.finish(); } criterion_group!(benches, bench_anchor_lookup); criterion_main!(benches); referencing-0.37.3/benches/pointer.rs000064400000000000000000000034651046102023000156520ustar 00000000000000use codspeed_criterion_compat::{ black_box, criterion_group, criterion_main, BenchmarkId, Criterion, }; use referencing::{Draft, Registry}; use serde_json::{json, Value}; fn create_deep_nested_json(depth: usize) -> Value { let mut current = json!({ "leaf": "value", "array": [1, 2, 3], "special/field": "escaped", "encoded field": "percent encoded" }); for i in (0..depth).rev() { current = json!({ format!("level_{}", i): current, "sibling": format!("sibling_value_{}", i) }); } json!({ "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "object", "properties": current }) } fn bench_pointers(c: &mut Criterion) { let data = create_deep_nested_json(15); let resource = Draft::Draft202012.create_resource(data); let registry = Registry::try_new("http://example.com/schema.json", resource) .expect("Invalid registry input"); let cases = [ ("single", "#/properties"), ("double", "#/properties/level_0"), ("long", "#/properties/level_0/level_1/level_2/level_3/level_4/level_5/level_6/level_7/level_8/level_9/level_10/level_11/level_12/level_13/level_14/array/1"), ]; let mut group = c.benchmark_group("JSON Pointer"); for (name, pointer) in cases { group.bench_with_input( BenchmarkId::new("pointer", name), ®istry, |b, registry| { let resolver = registry .try_resolver("http://example.com/schema.json") .expect("Invalid base URI"); b.iter_with_large_drop(|| resolver.lookup(black_box(pointer))); }, ); } group.finish(); } criterion_group!(benches, bench_pointers); criterion_main!(benches); referencing-0.37.3/benches/registry.rs000064400000000000000000000051511046102023000160340ustar 00000000000000use codspeed_criterion_compat::{ criterion_group, criterion_main, BatchSize, BenchmarkId, Criterion, }; use referencing::{Draft, Registry, SPECIFICATIONS}; static DRAFT4: &[u8] = include_bytes!("../../benchmark/data/subresources/draft4.json"); static DRAFT6: &[u8] = include_bytes!("../../benchmark/data/subresources/draft6.json"); static DRAFT7: &[u8] = include_bytes!("../../benchmark/data/subresources/draft7.json"); static DRAFT201909: &[u8] = include_bytes!("../../benchmark/data/subresources/draft201909.json"); static DRAFT202012: &[u8] = include_bytes!("../../benchmark/data/subresources/draft202012.json"); fn bench_subresources(c: &mut Criterion) { let drafts = [ (Draft::Draft4, DRAFT4, "draft 4"), (Draft::Draft6, DRAFT6, "draft 6"), (Draft::Draft7, DRAFT7, "draft 7"), (Draft::Draft201909, DRAFT201909, "draft 2019-09"), (Draft::Draft202012, DRAFT202012, "draft 2020-12"), ]; let mut group = c.benchmark_group("registry"); for (draft, data, name) in &drafts { let schema = benchmark::read_json(data); group.bench_with_input(BenchmarkId::new("try_new", name), &schema, |b, schema| { b.iter_batched( || draft.create_resource(schema.clone()), |resource| { Registry::try_new("http://example.com/schema.json", resource) .expect("Invalid registry input") }, BatchSize::SmallInput, ); }); } let drafts = [ (Draft::Draft4, benchmark::GEOJSON, "GeoJSON"), (Draft::Draft4, benchmark::SWAGGER, "Swagger"), (Draft::Draft4, benchmark::OPEN_API, "Open API"), (Draft::Draft4, benchmark::CITM_SCHEMA, "CITM"), (Draft::Draft7, benchmark::FAST_SCHEMA, "Fast"), ]; for (draft, data, name) in &drafts { let schema = benchmark::read_json(data); group.bench_with_input( BenchmarkId::new("try_with_resource", name), &schema, |b, schema| { b.iter_batched( || { ( draft.create_resource(schema.clone()), SPECIFICATIONS.clone(), ) }, |(resource, registry)| { registry.try_with_resource("http://example.com/schema.json", resource) }, BatchSize::SmallInput, ); }, ); } group.finish(); } criterion_group!(benches, bench_subresources); criterion_main!(benches); referencing-0.37.3/benches/subresources.rs000064400000000000000000000033771046102023000167200ustar 00000000000000use codspeed_criterion_compat::{ black_box, criterion_group, criterion_main, BenchmarkId, Criterion, }; use referencing::Draft; static DRAFT4: &[u8] = include_bytes!("../../benchmark/data/subresources/draft4.json"); static DRAFT6: &[u8] = include_bytes!("../../benchmark/data/subresources/draft6.json"); static DRAFT7: &[u8] = include_bytes!("../../benchmark/data/subresources/draft7.json"); static DRAFT201909: &[u8] = include_bytes!("../../benchmark/data/subresources/draft201909.json"); static DRAFT202012: &[u8] = include_bytes!("../../benchmark/data/subresources/draft202012.json"); fn bench_subresources(c: &mut Criterion) { let drafts = [ (Draft::Draft4, DRAFT4, "draft 4"), (Draft::Draft6, DRAFT6, "draft 6"), (Draft::Draft7, DRAFT7, "draft 7"), (Draft::Draft201909, DRAFT201909, "draft 2019-09"), (Draft::Draft202012, DRAFT202012, "draft 2020-12"), (Draft::Draft4, benchmark::GEOJSON, "GeoJSON"), (Draft::Draft4, benchmark::SWAGGER, "Swagger"), (Draft::Draft4, benchmark::OPEN_API, "Open API"), (Draft::Draft4, benchmark::CITM_SCHEMA, "CITM"), (Draft::Draft7, benchmark::FAST_SCHEMA, "Fast"), ]; let mut group = c.benchmark_group("subresources"); for (draft, data, name) in &drafts { let schema = benchmark::read_json(data); group.bench_with_input( BenchmarkId::new("subresources_of", name), &schema, |b, schema| { b.iter(|| { for sub in draft.subresources_of(black_box(schema)) { let _ = sub; } }); }, ); } group.finish(); } criterion_group!(benches, bench_subresources); criterion_main!(benches); referencing-0.37.3/metaschemas/draft2019-09/meta/applicator.json000064400000000000000000000030361046102023000223220ustar 00000000000000{ "$schema": "https://json-schema.org/draft/2019-09/schema", "$id": "https://json-schema.org/draft/2019-09/meta/applicator", "$vocabulary": { "https://json-schema.org/draft/2019-09/vocab/applicator": true }, "$recursiveAnchor": true, "title": "Applicator vocabulary meta-schema", "type": ["object", "boolean"], "properties": { "additionalItems": {"$recursiveRef": "#"}, "unevaluatedItems": {"$recursiveRef": "#"}, "items": { "anyOf": [{"$recursiveRef": "#"}, {"$ref": "#/$defs/schemaArray"}] }, "contains": {"$recursiveRef": "#"}, "additionalProperties": {"$recursiveRef": "#"}, "unevaluatedProperties": {"$recursiveRef": "#"}, "properties": { "type": "object", "additionalProperties": {"$recursiveRef": "#"}, "default": {} }, "patternProperties": { "type": "object", "additionalProperties": {"$recursiveRef": "#"}, "propertyNames": {"format": "regex"}, "default": {} }, "dependentSchemas": { "type": "object", "additionalProperties": { "$recursiveRef": "#" } }, "propertyNames": {"$recursiveRef": "#"}, "if": {"$recursiveRef": "#"}, "then": {"$recursiveRef": "#"}, "else": {"$recursiveRef": "#"}, "allOf": {"$ref": "#/$defs/schemaArray"}, "anyOf": {"$ref": "#/$defs/schemaArray"}, "oneOf": {"$ref": "#/$defs/schemaArray"}, "not": {"$recursiveRef": "#"} }, "$defs": { "schemaArray": { "type": "array", "minItems": 1, "items": {"$recursiveRef": "#"} } } } referencing-0.37.3/metaschemas/draft2019-09/meta/content.json000064400000000000000000000007351046102023000216410ustar 00000000000000{ "$schema": "https://json-schema.org/draft/2019-09/schema", "$id": "https://json-schema.org/draft/2019-09/meta/content", "$vocabulary": { "https://json-schema.org/draft/2019-09/vocab/content": true }, "$recursiveAnchor": true, "title": "Content vocabulary meta-schema", "type": ["object", "boolean"], "properties": { "contentMediaType": {"type": "string"}, "contentEncoding": {"type": "string"}, "contentSchema": {"$recursiveRef": "#"} } } referencing-0.37.3/metaschemas/draft2019-09/meta/core.json000064400000000000000000000023711046102023000211150ustar 00000000000000{ "$schema": "https://json-schema.org/draft/2019-09/schema", "$id": "https://json-schema.org/draft/2019-09/meta/core", "$vocabulary": { "https://json-schema.org/draft/2019-09/vocab/core": true }, "$recursiveAnchor": true, "title": "Core vocabulary meta-schema", "type": ["object", "boolean"], "properties": { "$id": { "type": "string", "format": "uri-reference", "$comment": "Non-empty fragments not allowed.", "pattern": "^[^#]*#?$" }, "$schema": { "type": "string", "format": "uri" }, "$anchor": { "type": "string", "pattern": "^[A-Za-z][-A-Za-z0-9.:_]*$" }, "$ref": { "type": "string", "format": "uri-reference" }, "$recursiveRef": { "type": "string", "format": "uri-reference" }, "$recursiveAnchor": { "type": "boolean", "default": false }, "$vocabulary": { "type": "object", "propertyNames": { "type": "string", "format": "uri" }, "additionalProperties": { "type": "boolean" } }, "$comment": { "type": "string" }, "$defs": { "type": "object", "additionalProperties": {"$recursiveRef": "#"}, "default": {} } } } referencing-0.37.3/metaschemas/draft2019-09/meta/format.json000064400000000000000000000005671046102023000214620ustar 00000000000000{ "$schema": "https://json-schema.org/draft/2019-09/schema", "$id": "https://json-schema.org/draft/2019-09/meta/format", "$vocabulary": { "https://json-schema.org/draft/2019-09/vocab/format": true }, "$recursiveAnchor": true, "title": "Format vocabulary meta-schema", "type": ["object", "boolean"], "properties": { "format": {"type": "string"} } } referencing-0.37.3/metaschemas/draft2019-09/meta/meta-data.json000064400000000000000000000013661046102023000220250ustar 00000000000000{ "$schema": "https://json-schema.org/draft/2019-09/schema", "$id": "https://json-schema.org/draft/2019-09/meta/meta-data", "$vocabulary": { "https://json-schema.org/draft/2019-09/vocab/meta-data": true }, "$recursiveAnchor": true, "title": "Meta-data vocabulary meta-schema", "type": ["object", "boolean"], "properties": { "title": { "type": "string" }, "description": { "type": "string" }, "default": true, "deprecated": { "type": "boolean", "default": false }, "readOnly": { "type": "boolean", "default": false }, "writeOnly": { "type": "boolean", "default": false }, "examples": { "type": "array", "items": true } } } referencing-0.37.3/metaschemas/draft2019-09/meta/validation.json000064400000000000000000000043301046102023000223140ustar 00000000000000{ "$schema": "https://json-schema.org/draft/2019-09/schema", "$id": "https://json-schema.org/draft/2019-09/meta/validation", "$vocabulary": { "https://json-schema.org/draft/2019-09/vocab/validation": true }, "$recursiveAnchor": true, "title": "Validation vocabulary meta-schema", "type": ["object", "boolean"], "properties": { "multipleOf": { "type": "number", "exclusiveMinimum": 0 }, "maximum": { "type": "number" }, "exclusiveMaximum": { "type": "number" }, "minimum": { "type": "number" }, "exclusiveMinimum": { "type": "number" }, "maxLength": {"$ref": "#/$defs/nonNegativeInteger"}, "minLength": {"$ref": "#/$defs/nonNegativeIntegerDefault0"}, "pattern": { "type": "string", "format": "regex" }, "maxItems": {"$ref": "#/$defs/nonNegativeInteger"}, "minItems": {"$ref": "#/$defs/nonNegativeIntegerDefault0"}, "uniqueItems": { "type": "boolean", "default": false }, "maxContains": {"$ref": "#/$defs/nonNegativeInteger"}, "minContains": { "$ref": "#/$defs/nonNegativeInteger", "default": 1 }, "maxProperties": {"$ref": "#/$defs/nonNegativeInteger"}, "minProperties": {"$ref": "#/$defs/nonNegativeIntegerDefault0"}, "required": {"$ref": "#/$defs/stringArray"}, "dependentRequired": { "type": "object", "additionalProperties": { "$ref": "#/$defs/stringArray" } }, "const": true, "enum": { "type": "array", "items": true }, "type": { "anyOf": [ {"$ref": "#/$defs/simpleTypes"}, { "type": "array", "items": {"$ref": "#/$defs/simpleTypes"}, "minItems": 1, "uniqueItems": true } ] } }, "$defs": { "nonNegativeInteger": { "type": "integer", "minimum": 0 }, "nonNegativeIntegerDefault0": { "$ref": "#/$defs/nonNegativeInteger", "default": 0 }, "simpleTypes": { "enum": ["array", "boolean", "integer", "null", "number", "object", "string"] }, "stringArray": { "type": "array", "items": {"type": "string"}, "uniqueItems": true, "default": [] } } } referencing-0.37.3/metaschemas/draft2019-09/schema.json000064400000000000000000000033711046102023000205000ustar 00000000000000{ "$schema": "https://json-schema.org/draft/2019-09/schema", "$id": "https://json-schema.org/draft/2019-09/schema", "$vocabulary": { "https://json-schema.org/draft/2019-09/vocab/core": true, "https://json-schema.org/draft/2019-09/vocab/applicator": true, "https://json-schema.org/draft/2019-09/vocab/validation": true, "https://json-schema.org/draft/2019-09/vocab/meta-data": true, "https://json-schema.org/draft/2019-09/vocab/format": false, "https://json-schema.org/draft/2019-09/vocab/content": true }, "$recursiveAnchor": true, "title": "Core and Validation specifications meta-schema", "allOf": [ {"$ref": "meta/core"}, {"$ref": "meta/applicator"}, {"$ref": "meta/validation"}, {"$ref": "meta/meta-data"}, {"$ref": "meta/format"}, {"$ref": "meta/content"} ], "type": ["object", "boolean"], "properties": { "definitions": { "$comment": "While no longer an official keyword as it is replaced by $defs, this keyword is retained in the meta-schema to prevent incompatible extensions as it remains in common use.", "type": "object", "additionalProperties": { "$recursiveRef": "#" }, "default": {} }, "dependencies": { "$comment": "\"dependencies\" is no longer a keyword, but schema authors should avoid redefining it to facilitate a smooth transition to \"dependentSchemas\" and \"dependentRequired\"", "type": "object", "additionalProperties": { "anyOf": [ { "$recursiveRef": "#" }, { "$ref": "meta/validation#/$defs/stringArray" } ] } } } } referencing-0.37.3/metaschemas/draft2020-12/meta/applicator.json000064400000000000000000000031731046102023000223060ustar 00000000000000{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://json-schema.org/draft/2020-12/meta/applicator", "$vocabulary": { "https://json-schema.org/draft/2020-12/vocab/applicator": true }, "$dynamicAnchor": "meta", "title": "Applicator vocabulary meta-schema", "type": ["object", "boolean"], "properties": { "prefixItems": { "$ref": "#/$defs/schemaArray" }, "items": { "$dynamicRef": "#meta" }, "contains": { "$dynamicRef": "#meta" }, "additionalProperties": { "$dynamicRef": "#meta" }, "properties": { "type": "object", "additionalProperties": { "$dynamicRef": "#meta" }, "default": {} }, "patternProperties": { "type": "object", "additionalProperties": { "$dynamicRef": "#meta" }, "propertyNames": { "format": "regex" }, "default": {} }, "dependentSchemas": { "type": "object", "additionalProperties": { "$dynamicRef": "#meta" }, "default": {} }, "propertyNames": { "$dynamicRef": "#meta" }, "if": { "$dynamicRef": "#meta" }, "then": { "$dynamicRef": "#meta" }, "else": { "$dynamicRef": "#meta" }, "allOf": { "$ref": "#/$defs/schemaArray" }, "anyOf": { "$ref": "#/$defs/schemaArray" }, "oneOf": { "$ref": "#/$defs/schemaArray" }, "not": { "$dynamicRef": "#meta" } }, "$defs": { "schemaArray": { "type": "array", "minItems": 1, "items": { "$dynamicRef": "#meta" } } } } referencing-0.37.3/metaschemas/draft2020-12/meta/content.json000064400000000000000000000010071046102023000216140ustar 00000000000000{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://json-schema.org/draft/2020-12/meta/content", "$vocabulary": { "https://json-schema.org/draft/2020-12/vocab/content": true }, "$dynamicAnchor": "meta", "title": "Content vocabulary meta-schema", "type": ["object", "boolean"], "properties": { "contentEncoding": { "type": "string" }, "contentMediaType": { "type": "string" }, "contentSchema": { "$dynamicRef": "#meta" } } } referencing-0.37.3/metaschemas/draft2020-12/meta/core.json000064400000000000000000000030341046102023000210740ustar 00000000000000{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://json-schema.org/draft/2020-12/meta/core", "$vocabulary": { "https://json-schema.org/draft/2020-12/vocab/core": true }, "$dynamicAnchor": "meta", "title": "Core vocabulary meta-schema", "type": ["object", "boolean"], "properties": { "$id": { "$ref": "#/$defs/uriReferenceString", "$comment": "Non-empty fragments not allowed.", "pattern": "^[^#]*#?$" }, "$schema": { "$ref": "#/$defs/uriString" }, "$ref": { "$ref": "#/$defs/uriReferenceString" }, "$anchor": { "$ref": "#/$defs/anchorString" }, "$dynamicRef": { "$ref": "#/$defs/uriReferenceString" }, "$dynamicAnchor": { "$ref": "#/$defs/anchorString" }, "$vocabulary": { "type": "object", "propertyNames": { "$ref": "#/$defs/uriString" }, "additionalProperties": { "type": "boolean" } }, "$comment": { "type": "string" }, "$defs": { "type": "object", "additionalProperties": { "$dynamicRef": "#meta" } } }, "$defs": { "anchorString": { "type": "string", "pattern": "^[A-Za-z_][-A-Za-z0-9._]*$" }, "uriString": { "type": "string", "format": "uri" }, "uriReferenceString": { "type": "string", "format": "uri-reference" } } } referencing-0.37.3/metaschemas/draft2020-12/meta/format-annotation.json000064400000000000000000000006731046102023000236120ustar 00000000000000{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://json-schema.org/draft/2020-12/meta/format-annotation", "$vocabulary": { "https://json-schema.org/draft/2020-12/vocab/format-annotation": true }, "$dynamicAnchor": "meta", "title": "Format vocabulary meta-schema for annotation results", "type": [ "object", "boolean" ], "properties": { "format": { "type": "string" } } } referencing-0.37.3/metaschemas/draft2020-12/meta/format-assertion.json000064400000000000000000000005271046102023000234450ustar 00000000000000{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://json-schema.org/draft/2020-12/meta/format-assertion", "$dynamicAnchor": "meta", "title": "Format vocabulary meta-schema for assertion results", "type": [ "object", "boolean" ], "properties": { "format": { "type": "string" } } } referencing-0.37.3/metaschemas/draft2020-12/meta/meta-data.json000064400000000000000000000015741046102023000220100ustar 00000000000000{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://json-schema.org/draft/2020-12/meta/meta-data", "$vocabulary": { "https://json-schema.org/draft/2020-12/vocab/meta-data": true }, "$dynamicAnchor": "meta", "title": "Meta-data vocabulary meta-schema", "type": ["object", "boolean"], "properties": { "title": { "type": "string" }, "description": { "type": "string" }, "default": true, "deprecated": { "type": "boolean", "default": false }, "readOnly": { "type": "boolean", "default": false }, "writeOnly": { "type": "boolean", "default": false }, "examples": { "type": "array", "items": true } } } referencing-0.37.3/metaschemas/draft2020-12/meta/unevaluated.json000064400000000000000000000007721046102023000224670ustar 00000000000000{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://json-schema.org/draft/2020-12/meta/unevaluated", "$vocabulary": { "https://json-schema.org/draft/2020-12/vocab/unevaluated": true }, "$dynamicAnchor": "meta", "title": "Unevaluated applicator vocabulary meta-schema", "type": ["object", "boolean"], "properties": { "unevaluatedItems": { "$dynamicRef": "#meta" }, "unevaluatedProperties": { "$dynamicRef": "#meta" } } } referencing-0.37.3/metaschemas/draft2020-12/meta/validation.json000064400000000000000000000054221046102023000223010ustar 00000000000000{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://json-schema.org/draft/2020-12/meta/validation", "$vocabulary": { "https://json-schema.org/draft/2020-12/vocab/validation": true }, "$dynamicAnchor": "meta", "title": "Validation vocabulary meta-schema", "type": ["object", "boolean"], "properties": { "type": { "anyOf": [ { "$ref": "#/$defs/simpleTypes" }, { "type": "array", "items": { "$ref": "#/$defs/simpleTypes" }, "minItems": 1, "uniqueItems": true } ] }, "const": true, "enum": { "type": "array", "items": true }, "multipleOf": { "type": "number", "exclusiveMinimum": 0 }, "maximum": { "type": "number" }, "exclusiveMaximum": { "type": "number" }, "minimum": { "type": "number" }, "exclusiveMinimum": { "type": "number" }, "maxLength": { "$ref": "#/$defs/nonNegativeInteger" }, "minLength": { "$ref": "#/$defs/nonNegativeIntegerDefault0" }, "pattern": { "type": "string", "format": "regex" }, "maxItems": { "$ref": "#/$defs/nonNegativeInteger" }, "minItems": { "$ref": "#/$defs/nonNegativeIntegerDefault0" }, "uniqueItems": { "type": "boolean", "default": false }, "maxContains": { "$ref": "#/$defs/nonNegativeInteger" }, "minContains": { "$ref": "#/$defs/nonNegativeInteger", "default": 1 }, "maxProperties": { "$ref": "#/$defs/nonNegativeInteger" }, "minProperties": { "$ref": "#/$defs/nonNegativeIntegerDefault0" }, "required": { "$ref": "#/$defs/stringArray" }, "dependentRequired": { "type": "object", "additionalProperties": { "$ref": "#/$defs/stringArray" } } }, "$defs": { "nonNegativeInteger": { "type": "integer", "minimum": 0 }, "nonNegativeIntegerDefault0": { "$ref": "#/$defs/nonNegativeInteger", "default": 0 }, "simpleTypes": { "enum": [ "array", "boolean", "integer", "null", "number", "object", "string" ] }, "stringArray": { "type": "array", "items": { "type": "string" }, "uniqueItems": true, "default": [] } } } referencing-0.37.3/metaschemas/draft2020-12/schema.json000064400000000000000000000042321046102023000204570ustar 00000000000000{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://json-schema.org/draft/2020-12/schema", "$vocabulary": { "https://json-schema.org/draft/2020-12/vocab/core": true, "https://json-schema.org/draft/2020-12/vocab/applicator": true, "https://json-schema.org/draft/2020-12/vocab/unevaluated": true, "https://json-schema.org/draft/2020-12/vocab/validation": true, "https://json-schema.org/draft/2020-12/vocab/meta-data": true, "https://json-schema.org/draft/2020-12/vocab/format-annotation": true, "https://json-schema.org/draft/2020-12/vocab/content": true }, "$dynamicAnchor": "meta", "title": "Core and Validation specifications meta-schema", "allOf": [ {"$ref": "meta/core"}, {"$ref": "meta/applicator"}, {"$ref": "meta/unevaluated"}, {"$ref": "meta/validation"}, {"$ref": "meta/meta-data"}, {"$ref": "meta/format-annotation"}, {"$ref": "meta/content"} ], "type": ["object", "boolean"], "$comment": "This meta-schema also defines keywords that have appeared in previous drafts in order to prevent incompatible extensions as they remain in common use.", "properties": { "definitions": { "$comment": "\"definitions\" has been replaced by \"$defs\".", "type": "object", "additionalProperties": { "$dynamicRef": "#meta" }, "deprecated": true, "default": {} }, "dependencies": { "$comment": "\"dependencies\" has been split and replaced by \"dependentSchemas\" and \"dependentRequired\" in order to serve their differing semantics.", "type": "object", "additionalProperties": { "anyOf": [ { "$dynamicRef": "#meta" }, { "$ref": "meta/validation#/$defs/stringArray" } ] }, "deprecated": true, "default": {} }, "$recursiveAnchor": { "$comment": "\"$recursiveAnchor\" has been replaced by \"$dynamicAnchor\".", "$ref": "meta/core#/$defs/anchorString", "deprecated": true }, "$recursiveRef": { "$comment": "\"$recursiveRef\" has been replaced by \"$dynamicRef\".", "$ref": "meta/core#/$defs/uriReferenceString", "deprecated": true } } } referencing-0.37.3/metaschemas/draft4.json000064400000000000000000000103111046102023000165520ustar 00000000000000{ "id": "http://json-schema.org/draft-04/schema#", "$schema": "http://json-schema.org/draft-04/schema#", "description": "Core schema meta-schema", "definitions": { "schemaArray": { "type": "array", "minItems": 1, "items": { "$ref": "#" } }, "positiveInteger": { "type": "integer", "minimum": 0 }, "positiveIntegerDefault0": { "allOf": [ { "$ref": "#/definitions/positiveInteger" }, { "default": 0 } ] }, "simpleTypes": { "enum": [ "array", "boolean", "integer", "null", "number", "object", "string" ] }, "stringArray": { "type": "array", "items": { "type": "string" }, "minItems": 1, "uniqueItems": true } }, "type": "object", "properties": { "id": { "type": "string" }, "$schema": { "type": "string" }, "title": { "type": "string" }, "description": { "type": "string" }, "default": {}, "multipleOf": { "type": "number", "minimum": 0, "exclusiveMinimum": true }, "maximum": { "type": "number" }, "exclusiveMaximum": { "type": "boolean", "default": false }, "minimum": { "type": "number" }, "exclusiveMinimum": { "type": "boolean", "default": false }, "maxLength": { "$ref": "#/definitions/positiveInteger" }, "minLength": { "$ref": "#/definitions/positiveIntegerDefault0" }, "pattern": { "type": "string", "format": "regex" }, "additionalItems": { "anyOf": [ { "type": "boolean" }, { "$ref": "#" } ], "default": {} }, "items": { "anyOf": [ { "$ref": "#" }, { "$ref": "#/definitions/schemaArray" } ], "default": {} }, "maxItems": { "$ref": "#/definitions/positiveInteger" }, "minItems": { "$ref": "#/definitions/positiveIntegerDefault0" }, "uniqueItems": { "type": "boolean", "default": false }, "maxProperties": { "$ref": "#/definitions/positiveInteger" }, "minProperties": { "$ref": "#/definitions/positiveIntegerDefault0" }, "required": { "$ref": "#/definitions/stringArray" }, "additionalProperties": { "anyOf": [ { "type": "boolean" }, { "$ref": "#" } ], "default": {} }, "definitions": { "type": "object", "additionalProperties": { "$ref": "#" }, "default": {} }, "properties": { "type": "object", "additionalProperties": { "$ref": "#" }, "default": {} }, "patternProperties": { "type": "object", "additionalProperties": { "$ref": "#" }, "default": {} }, "dependencies": { "type": "object", "additionalProperties": { "anyOf": [ { "$ref": "#" }, { "$ref": "#/definitions/stringArray" } ] } }, "enum": { "type": "array" }, "type": { "anyOf": [ { "$ref": "#/definitions/simpleTypes" }, { "type": "array", "items": { "$ref": "#/definitions/simpleTypes" }, "minItems": 1, "uniqueItems": true } ] }, "format": { "type": "string" }, "allOf": { "$ref": "#/definitions/schemaArray" }, "anyOf": { "$ref": "#/definitions/schemaArray" }, "oneOf": { "$ref": "#/definitions/schemaArray" }, "not": { "$ref": "#" } }, "dependencies": { "exclusiveMaximum": [ "maximum" ], "exclusiveMinimum": [ "minimum" ] }, "default": {} } referencing-0.37.3/metaschemas/draft6.json000064400000000000000000000105251046102023000165630ustar 00000000000000{ "$schema": "http://json-schema.org/draft-06/schema#", "$id": "http://json-schema.org/draft-06/schema#", "title": "Core schema meta-schema", "definitions": { "schemaArray": { "type": "array", "minItems": 1, "items": { "$ref": "#" } }, "nonNegativeInteger": { "type": "integer", "minimum": 0 }, "nonNegativeIntegerDefault0": { "allOf": [ { "$ref": "#/definitions/nonNegativeInteger" }, { "default": 0 } ] }, "simpleTypes": { "enum": [ "array", "boolean", "integer", "null", "number", "object", "string" ] }, "stringArray": { "type": "array", "items": { "type": "string" }, "uniqueItems": true, "default": [] } }, "type": ["object", "boolean"], "properties": { "$id": { "type": "string", "format": "uri-reference" }, "$schema": { "type": "string", "format": "uri" }, "$ref": { "type": "string", "format": "uri-reference" }, "title": { "type": "string" }, "description": { "type": "string" }, "default": {}, "examples": { "type": "array", "items": {} }, "multipleOf": { "type": "number", "exclusiveMinimum": 0 }, "maximum": { "type": "number" }, "exclusiveMaximum": { "type": "number" }, "minimum": { "type": "number" }, "exclusiveMinimum": { "type": "number" }, "maxLength": { "$ref": "#/definitions/nonNegativeInteger" }, "minLength": { "$ref": "#/definitions/nonNegativeIntegerDefault0" }, "pattern": { "type": "string", "format": "regex" }, "additionalItems": { "$ref": "#" }, "items": { "anyOf": [ { "$ref": "#" }, { "$ref": "#/definitions/schemaArray" } ], "default": {} }, "maxItems": { "$ref": "#/definitions/nonNegativeInteger" }, "minItems": { "$ref": "#/definitions/nonNegativeIntegerDefault0" }, "uniqueItems": { "type": "boolean", "default": false }, "contains": { "$ref": "#" }, "maxProperties": { "$ref": "#/definitions/nonNegativeInteger" }, "minProperties": { "$ref": "#/definitions/nonNegativeIntegerDefault0" }, "required": { "$ref": "#/definitions/stringArray" }, "additionalProperties": { "$ref": "#" }, "definitions": { "type": "object", "additionalProperties": { "$ref": "#" }, "default": {} }, "properties": { "type": "object", "additionalProperties": { "$ref": "#" }, "default": {} }, "patternProperties": { "type": "object", "additionalProperties": { "$ref": "#" }, "propertyNames": { "format": "regex" }, "default": {} }, "dependencies": { "type": "object", "additionalProperties": { "anyOf": [ { "$ref": "#" }, { "$ref": "#/definitions/stringArray" } ] } }, "propertyNames": { "$ref": "#" }, "const": {}, "enum": { "type": "array" }, "type": { "anyOf": [ { "$ref": "#/definitions/simpleTypes" }, { "type": "array", "items": { "$ref": "#/definitions/simpleTypes" }, "minItems": 1, "uniqueItems": true } ] }, "format": { "type": "string" }, "allOf": { "$ref": "#/definitions/schemaArray" }, "anyOf": { "$ref": "#/definitions/schemaArray" }, "oneOf": { "$ref": "#/definitions/schemaArray" }, "not": { "$ref": "#" } }, "default": {} } referencing-0.37.3/metaschemas/draft7.json000064400000000000000000000114671046102023000165720ustar 00000000000000{ "$schema": "http://json-schema.org/draft-07/schema#", "$id": "http://json-schema.org/draft-07/schema#", "title": "Core schema meta-schema", "definitions": { "schemaArray": { "type": "array", "minItems": 1, "items": { "$ref": "#" } }, "nonNegativeInteger": { "type": "integer", "minimum": 0 }, "nonNegativeIntegerDefault0": { "allOf": [ { "$ref": "#/definitions/nonNegativeInteger" }, { "default": 0 } ] }, "simpleTypes": { "enum": [ "array", "boolean", "integer", "null", "number", "object", "string" ] }, "stringArray": { "type": "array", "items": { "type": "string" }, "uniqueItems": true, "default": [] } }, "type": ["object", "boolean"], "properties": { "$id": { "type": "string", "format": "uri-reference" }, "$schema": { "type": "string", "format": "uri" }, "$ref": { "type": "string", "format": "uri-reference" }, "$comment": { "type": "string" }, "title": { "type": "string" }, "description": { "type": "string" }, "default": true, "readOnly": { "type": "boolean", "default": false }, "writeOnly": { "type": "boolean", "default": false }, "examples": { "type": "array", "items": true }, "multipleOf": { "type": "number", "exclusiveMinimum": 0 }, "maximum": { "type": "number" }, "exclusiveMaximum": { "type": "number" }, "minimum": { "type": "number" }, "exclusiveMinimum": { "type": "number" }, "maxLength": { "$ref": "#/definitions/nonNegativeInteger" }, "minLength": { "$ref": "#/definitions/nonNegativeIntegerDefault0" }, "pattern": { "type": "string", "format": "regex" }, "additionalItems": { "$ref": "#" }, "items": { "anyOf": [ { "$ref": "#" }, { "$ref": "#/definitions/schemaArray" } ], "default": true }, "maxItems": { "$ref": "#/definitions/nonNegativeInteger" }, "minItems": { "$ref": "#/definitions/nonNegativeIntegerDefault0" }, "uniqueItems": { "type": "boolean", "default": false }, "contains": { "$ref": "#" }, "maxProperties": { "$ref": "#/definitions/nonNegativeInteger" }, "minProperties": { "$ref": "#/definitions/nonNegativeIntegerDefault0" }, "required": { "$ref": "#/definitions/stringArray" }, "additionalProperties": { "$ref": "#" }, "definitions": { "type": "object", "additionalProperties": { "$ref": "#" }, "default": {} }, "properties": { "type": "object", "additionalProperties": { "$ref": "#" }, "default": {} }, "patternProperties": { "type": "object", "additionalProperties": { "$ref": "#" }, "propertyNames": { "format": "regex" }, "default": {} }, "dependencies": { "type": "object", "additionalProperties": { "anyOf": [ { "$ref": "#" }, { "$ref": "#/definitions/stringArray" } ] } }, "propertyNames": { "$ref": "#" }, "const": true, "enum": { "type": "array", "items": true }, "type": { "anyOf": [ { "$ref": "#/definitions/simpleTypes" }, { "type": "array", "items": { "$ref": "#/definitions/simpleTypes" }, "minItems": 1, "uniqueItems": true } ] }, "format": { "type": "string" }, "contentMediaType": { "type": "string" }, "contentEncoding": { "type": "string" }, "if": { "$ref": "#" }, "then": { "$ref": "#" }, "else": { "$ref": "#" }, "allOf": { "$ref": "#/definitions/schemaArray" }, "anyOf": { "$ref": "#/definitions/schemaArray" }, "oneOf": { "$ref": "#/definitions/schemaArray" }, "not": { "$ref": "#" } }, "default": true } referencing-0.37.3/src/anchors/keys.rs000064400000000000000000000040211046102023000157470ustar 00000000000000//! This module provides a mechanism for creating and managing composite keys //! used in anchor lookups. It allows for efficient lookups without the need //! to construct data structures with owned values. //! //! The key components are: //! - `AnchorKey`: An owned version of the composite key. //! - `AnchorKeyRef`: A borrowed version of the composite key. //! - `BorrowDyn`: A trait that allows for dynamic borrowing of key components. //! //! This design enables the use of borrowed data in hash map lookups while //! still storing owned data. use std::{ borrow::Borrow, hash::{Hash, Hasher}, sync::Arc, }; use fluent_uri::Uri; use super::AnchorName; #[derive(Debug, Clone, Hash, PartialEq, Eq)] pub(crate) struct AnchorKey { uri: Arc>, name: AnchorName, } impl AnchorKey { pub(crate) fn new(uri: Arc>, name: AnchorName) -> Self { Self { uri, name } } } #[derive(Copy, Clone, Hash, PartialEq, Eq)] pub(crate) struct AnchorKeyRef<'a> { uri: &'a Uri, name: &'a str, } impl<'a> AnchorKeyRef<'a> { pub(crate) fn new(uri: &'a Uri, name: &'a str) -> Self { AnchorKeyRef { uri, name } } pub(crate) fn borrow_dyn(&self) -> &dyn BorrowDyn { self as &dyn BorrowDyn } } pub(crate) trait BorrowDyn { fn borrowed_key(&self) -> AnchorKeyRef<'_>; } impl BorrowDyn for AnchorKey { fn borrowed_key(&self) -> AnchorKeyRef<'_> { AnchorKeyRef::new(&self.uri, self.name.as_str()) } } impl BorrowDyn for AnchorKeyRef<'_> { fn borrowed_key(&self) -> AnchorKeyRef<'_> { *self } } impl<'a> Borrow for AnchorKey { fn borrow(&self) -> &(dyn BorrowDyn + 'a) { self } } impl Eq for dyn BorrowDyn + '_ {} impl PartialEq for dyn BorrowDyn + '_ { fn eq(&self, other: &dyn BorrowDyn) -> bool { self.borrowed_key().eq(&other.borrowed_key()) } } impl Hash for dyn BorrowDyn + '_ { fn hash(&self, state: &mut H) { self.borrowed_key().hash(state); } } referencing-0.37.3/src/anchors/mod.rs000064400000000000000000000355421046102023000155670ustar 00000000000000use std::{ hash::Hash, sync::atomic::{AtomicPtr, Ordering}, }; use serde_json::Value; mod keys; use crate::{resource::InnerResourcePtr, Draft, Error, Resolved, Resolver}; pub(crate) use keys::{AnchorKey, AnchorKeyRef}; #[derive(Debug)] pub(crate) struct AnchorName { ptr: AtomicPtr, len: usize, } impl AnchorName { fn new(s: &str) -> Self { Self { ptr: AtomicPtr::new(s.as_ptr().cast_mut()), len: s.len(), } } #[allow(unsafe_code)] fn as_str(&self) -> &str { // SAFETY: The pointer is valid as long as the registry exists unsafe { std::str::from_utf8_unchecked(std::slice::from_raw_parts( self.ptr.load(Ordering::Relaxed), self.len, )) } } } impl Clone for AnchorName { fn clone(&self) -> Self { Self { ptr: AtomicPtr::new(self.ptr.load(Ordering::Relaxed)), len: self.len, } } } impl Hash for AnchorName { fn hash(&self, state: &mut H) { self.as_str().hash(state); } } impl PartialEq for AnchorName { fn eq(&self, other: &Self) -> bool { self.as_str() == other.as_str() } } impl Eq for AnchorName {} /// An anchor within a resource. #[derive(Debug, Clone)] pub(crate) enum Anchor { Default { name: AnchorName, resource: InnerResourcePtr, }, Dynamic { name: AnchorName, resource: InnerResourcePtr, }, } impl Anchor { /// Anchor's name. pub(crate) fn name(&self) -> AnchorName { match self { Anchor::Default { name, .. } | Anchor::Dynamic { name, .. } => name.clone(), } } /// Get the resource for this anchor. pub(crate) fn resolve<'r>(&'r self, resolver: Resolver<'r>) -> Result, Error> { match self { Anchor::Default { resource, .. } => Ok(Resolved::new( resource.contents(), resolver, resource.draft(), )), Anchor::Dynamic { name, resource } => { let mut last = resource; for uri in &resolver.dynamic_scope() { match resolver.registry.anchor(uri, name.as_str()) { Ok(anchor) => { if let Anchor::Dynamic { resource, .. } = anchor { last = resource; } } Err(Error::NoSuchAnchor { .. }) => {} Err(err) => return Err(err), } } Ok(Resolved::new( last.contents(), resolver.in_subresource_inner(last)?, last.draft(), )) } } } } pub(crate) enum AnchorIter { Empty, One(Anchor), Two(Anchor, Anchor), } impl Iterator for AnchorIter { type Item = Anchor; fn next(&mut self) -> Option { match std::mem::replace(self, AnchorIter::Empty) { AnchorIter::Empty => None, AnchorIter::One(anchor) => Some(anchor), AnchorIter::Two(first, second) => { *self = AnchorIter::One(second); Some(first) } } } } pub(crate) fn anchor(draft: Draft, contents: &Value) -> AnchorIter { let Some(schema) = contents.as_object() else { return AnchorIter::Empty; }; // First check for top-level anchors let default_anchor = schema .get("$anchor") .and_then(Value::as_str) .map(|name| Anchor::Default { name: AnchorName::new(name), resource: InnerResourcePtr::new(contents, draft), }); let dynamic_anchor = schema .get("$dynamicAnchor") .and_then(Value::as_str) .map(|name| Anchor::Dynamic { name: AnchorName::new(name), resource: InnerResourcePtr::new(contents, draft), }); match (default_anchor, dynamic_anchor) { (Some(default), Some(dynamic)) => AnchorIter::Two(default, dynamic), (Some(default), None) => AnchorIter::One(default), (None, Some(dynamic)) => AnchorIter::One(dynamic), (None, None) => AnchorIter::Empty, } } pub(crate) fn anchor_2019(draft: Draft, contents: &Value) -> AnchorIter { match contents .as_object() .and_then(|schema| schema.get("$anchor")) .and_then(Value::as_str) { Some(name) => AnchorIter::One(Anchor::Default { name: AnchorName::new(name), resource: InnerResourcePtr::new(contents, draft), }), None => AnchorIter::Empty, } } pub(crate) fn legacy_anchor_in_dollar_id(draft: Draft, contents: &Value) -> AnchorIter { match contents .as_object() .and_then(|schema| schema.get("$id")) .and_then(Value::as_str) .and_then(|id| id.strip_prefix('#')) { Some(id) => AnchorIter::One(Anchor::Default { name: AnchorName::new(id), resource: InnerResourcePtr::new(contents, draft), }), None => AnchorIter::Empty, } } pub(crate) fn legacy_anchor_in_id(draft: Draft, contents: &Value) -> AnchorIter { match contents .as_object() .and_then(|schema| schema.get("id")) .and_then(Value::as_str) .and_then(|id| id.strip_prefix('#')) { Some(id) => AnchorIter::One(Anchor::Default { name: AnchorName::new(id), resource: InnerResourcePtr::new(contents, draft), }), None => AnchorIter::Empty, } } #[cfg(test)] mod tests { use crate::{Draft, Registry}; use serde_json::json; #[test] fn test_lookup_trivial_dynamic_ref() { let one = Draft::Draft202012.create_resource(json!({"$dynamicAnchor": "foo"})); let registry = Registry::try_new("http://example.com", one.clone()).expect("Invalid resources"); let resolver = registry .try_resolver("http://example.com") .expect("Invalid base URI"); let resolved = resolver.lookup("#foo").expect("Lookup failed"); assert_eq!(resolved.contents(), one.contents()); } #[test] fn test_multiple_lookup_trivial_dynamic_ref() { let true_resource = Draft::Draft202012.create_resource(json!(true)); let root = Draft::Draft202012.create_resource(json!({ "$id": "http://example.com", "$dynamicAnchor": "fooAnchor", "$defs": { "foo": { "$id": "foo", "$dynamicAnchor": "fooAnchor", "$defs": { "bar": true, "baz": { "$dynamicAnchor": "fooAnchor", }, }, }, }, })); let registry = Registry::try_from_resources([ ("http://example.com".to_string(), root.clone()), ("http://example.com/foo/".to_string(), true_resource), ("http://example.com/foo/bar".to_string(), root.clone()), ]) .expect("Invalid resources"); let resolver = registry .try_resolver("http://example.com") .expect("Invalid base URI"); let first = resolver.lookup("").expect("Lookup failed"); let second = first.resolver().lookup("foo/").expect("Lookup failed"); let third = second.resolver().lookup("bar").expect("Lookup failed"); let fourth = third .resolver() .lookup("#fooAnchor") .expect("Lookup failed"); assert_eq!(fourth.contents(), root.contents()); assert_eq!(format!("{:?}", fourth.resolver()), "Resolver { base_uri: \"http://example.com\", scopes: \"[http://example.com/foo/, http://example.com, http://example.com]\" }"); } #[test] fn test_multiple_lookup_dynamic_ref_to_nondynamic_ref() { let one = Draft::Draft202012.create_resource(json!({"$anchor": "fooAnchor"})); let two = Draft::Draft202012.create_resource(json!({ "$id": "http://example.com", "$dynamicAnchor": "fooAnchor", "$defs": { "foo": { "$id": "foo", "$dynamicAnchor": "fooAnchor", "$defs": { "bar": true, "baz": { "$dynamicAnchor": "fooAnchor", }, }, }, }, })); let registry = Registry::try_from_resources([ ("http://example.com".to_string(), two.clone()), ("http://example.com/foo/".to_string(), one), ("http://example.com/foo/bar".to_string(), two.clone()), ]) .expect("Invalid resources"); let resolver = registry .try_resolver("http://example.com") .expect("Invalid base URI"); let first = resolver.lookup("").expect("Lookup failed"); let second = first.resolver().lookup("foo/").expect("Lookup failed"); let third = second.resolver().lookup("bar").expect("Lookup failed"); let fourth = third .resolver() .lookup("#fooAnchor") .expect("Lookup failed"); assert_eq!(fourth.contents(), two.contents()); } #[test] fn test_unknown_anchor() { let schema = Draft::Draft202012.create_resource(json!({ "$defs": { "foo": { "$anchor": "knownAnchor" } } })); let registry = Registry::try_new("http://example.com", schema).expect("Invalid resources"); let resolver = registry .try_resolver("http://example.com") .expect("Invalid base URI"); let result = resolver.lookup("#unknownAnchor"); assert_eq!( result.unwrap_err().to_string(), "Anchor 'unknownAnchor' does not exist" ); } #[test] fn test_invalid_anchor_with_slash() { let schema = Draft::Draft202012.create_resource(json!({ "$defs": { "foo": { "$anchor": "knownAnchor" } } })); let registry = Registry::try_new("http://example.com", schema).expect("Invalid resources"); let resolver = registry .try_resolver("http://example.com") .expect("Invalid base URI"); let result = resolver.lookup("#invalid/anchor"); assert_eq!( result.unwrap_err().to_string(), "Anchor 'invalid/anchor' is invalid" ); } #[test] fn test_lookup_trivial_recursive_ref() { let one = Draft::Draft201909.create_resource(json!({"$recursiveAnchor": true})); let registry = Registry::try_new("http://example.com", one.clone()).expect("Invalid resources"); let resolver = registry .try_resolver("http://example.com") .expect("Invalid base URI"); let first = resolver.lookup("").expect("Lookup failed"); let resolved = first .resolver() .lookup_recursive_ref() .expect("Lookup failed"); assert_eq!(resolved.contents(), one.contents()); } #[test] fn test_lookup_recursive_ref_to_bool() { let true_resource = Draft::Draft201909.create_resource(json!(true)); let registry = Registry::try_new("http://example.com", true_resource.clone()) .expect("Invalid resources"); let resolver = registry .try_resolver("http://example.com") .expect("Invalid base URI"); let resolved = resolver.lookup_recursive_ref().expect("Lookup failed"); assert_eq!(resolved.contents(), true_resource.contents()); } #[test] fn test_multiple_lookup_recursive_ref_to_bool() { let true_resource = Draft::Draft201909.create_resource(json!(true)); let root = Draft::Draft201909.create_resource(json!({ "$id": "http://example.com", "$recursiveAnchor": true, "$defs": { "foo": { "$id": "foo", "$recursiveAnchor": true, "$defs": { "bar": true, "baz": { "$recursiveAnchor": true, "$anchor": "fooAnchor", }, }, }, }, })); let registry = Registry::try_from_resources(vec![ ("http://example.com".to_string(), root.clone()), ("http://example.com/foo/".to_string(), true_resource), ("http://example.com/foo/bar".to_string(), root.clone()), ]) .expect("Invalid resources"); let resolver = registry .try_resolver("http://example.com") .expect("Invalid base URI"); let first = resolver.lookup("").expect("Lookup failed"); let second = first.resolver().lookup("foo/").expect("Lookup failed"); let third = second.resolver().lookup("bar").expect("Lookup failed"); let fourth = third .resolver() .lookup_recursive_ref() .expect("Lookup failed"); assert_eq!(fourth.contents(), root.contents()); } #[test] fn test_multiple_lookup_recursive_ref_with_nonrecursive_ref() { let one = Draft::Draft201909.create_resource(json!({"$recursiveAnchor": true})); let two = Draft::Draft201909.create_resource(json!({ "$id": "http://example.com", "$recursiveAnchor": true, "$defs": { "foo": { "$id": "foo", "$recursiveAnchor": true, "$defs": { "bar": true, "baz": { "$recursiveAnchor": true, "$anchor": "fooAnchor", }, }, }, }, })); let three = Draft::Draft201909.create_resource(json!({"$recursiveAnchor": false})); let registry = Registry::try_from_resources(vec![ ("http://example.com".to_string(), three), ("http://example.com/foo/".to_string(), two.clone()), ("http://example.com/foo/bar".to_string(), one), ]) .expect("Invalid resources"); let resolver = registry .try_resolver("http://example.com") .expect("Invalid base URI"); let first = resolver.lookup("").expect("Lookup failed"); let second = first.resolver().lookup("foo/").expect("Lookup failed"); let third = second.resolver().lookup("bar").expect("Lookup failed"); let fourth = third .resolver() .lookup_recursive_ref() .expect("Lookup failed"); assert_eq!(fourth.contents(), two.contents()); } } referencing-0.37.3/src/cache.rs000064400000000000000000000102301046102023000144010ustar 00000000000000use std::sync::Arc; use fluent_uri::Uri; use hashbrown::hash_map::{EntryRef, HashMap}; use parking_lot::{RwLock, RwLockUpgradableReadGuard}; use crate::{uri, Error}; type CacheBucket = HashMap>>; type CacheMap = HashMap; #[derive(Debug, Clone)] pub(crate) struct UriCache { cache: CacheMap, } impl UriCache { pub(crate) fn new() -> Self { Self { cache: HashMap::new(), } } pub(crate) fn with_capacity(capacity: usize) -> Self { Self { cache: HashMap::with_capacity(capacity), } } pub(crate) fn resolve_against( &mut self, base: &Uri<&str>, uri: impl AsRef, ) -> Result>, Error> { let base_str = base.as_str(); let reference = uri.as_ref(); let resolved = match self.cache.entry_ref(base_str) { EntryRef::Occupied(mut entry) => { if let Some(cached) = entry.get().get(reference) { return Ok(Arc::clone(cached)); } let resolved = Arc::new(uri::resolve_against(base, reference)?); entry .get_mut() .insert(reference.to_owned(), Arc::clone(&resolved)); resolved } EntryRef::Vacant(entry) => { let resolved = Arc::new(uri::resolve_against(base, reference)?); let mut inner = HashMap::with_capacity(1); inner.insert(reference.to_owned(), Arc::clone(&resolved)); entry.insert(inner); resolved } }; Ok(resolved) } pub(crate) fn into_shared(self) -> SharedUriCache { SharedUriCache { cache: RwLock::new(self.cache), } } } /// A dedicated type for URI resolution caching. #[derive(Debug)] pub(crate) struct SharedUriCache { cache: RwLock, } impl Clone for SharedUriCache { fn clone(&self) -> Self { Self { cache: RwLock::new( self.cache .read() .iter() .map(|(base, entries)| { ( base.clone(), entries .iter() .map(|(reference, value)| (reference.clone(), Arc::clone(value))) .collect(), ) }) .collect(), ), } } } impl SharedUriCache { pub(crate) fn resolve_against( &self, base: &Uri<&str>, uri: impl AsRef, ) -> Result>, Error> { let base_str = base.as_str(); let reference = uri.as_ref(); if let Some(cached) = self .cache .read() .get(base_str) .and_then(|inner| inner.get(reference)) { return Ok(Arc::clone(cached)); } let cache = self.cache.upgradable_read(); if let Some(inner) = cache.get(base_str).and_then(|inner| inner.get(reference)) { return Ok(Arc::clone(inner)); } let resolved = Arc::new(uri::resolve_against(base, reference)?); let mut cache = RwLockUpgradableReadGuard::upgrade(cache); let inserted = match cache.entry_ref(base_str) { EntryRef::Occupied(mut entry) => { if let Some(existing) = entry.get().get(reference) { return Ok(Arc::clone(existing)); } entry .get_mut() .insert(reference.to_owned(), Arc::clone(&resolved)); resolved } EntryRef::Vacant(entry) => { let mut inner = HashMap::with_capacity(1); inner.insert(reference.to_owned(), Arc::clone(&resolved)); entry.insert(inner); resolved } }; Ok(inserted) } pub(crate) fn into_local(self) -> UriCache { UriCache { cache: self.cache.into_inner(), } } } referencing-0.37.3/src/error.rs000064400000000000000000000153221046102023000144760ustar 00000000000000use core::fmt; use std::{num::ParseIntError, str::Utf8Error}; use fluent_uri::{resolve::ResolveError, ParseError, Uri}; /// Errors that can occur during reference resolution and resource handling. #[derive(Debug)] pub enum Error { /// A resource is not present in a registry and retrieving it failed. Unretrievable { uri: String, source: Box, }, /// A JSON Pointer leads to a part of a document that does not exist. PointerToNowhere { pointer: String }, /// JSON Pointer contains invalid percent-encoded data. InvalidPercentEncoding { pointer: String, source: Utf8Error }, /// Failed to parse array index in JSON Pointer. InvalidArrayIndex { pointer: String, index: String, source: ParseIntError, }, /// An anchor does not exist within a particular resource. NoSuchAnchor { anchor: String }, /// An anchor which could never exist in a resource was dereferenced. InvalidAnchor { anchor: String }, /// An error occurred while parsing or manipulating a URI. InvalidUri(UriError), /// An unknown JSON Schema specification was encountered. UnknownSpecification { specification: String }, /// A circular reference was detected in a meta-schema chain. CircularMetaschema { uri: String }, } impl Error { pub(crate) fn pointer_to_nowhere(pointer: impl Into) -> Error { Error::PointerToNowhere { pointer: pointer.into(), } } pub(crate) fn invalid_percent_encoding(pointer: impl Into, source: Utf8Error) -> Error { Error::InvalidPercentEncoding { pointer: pointer.into(), source, } } pub(crate) fn invalid_array_index( pointer: impl Into, index: impl Into, source: ParseIntError, ) -> Error { Error::InvalidArrayIndex { pointer: pointer.into(), index: index.into(), source, } } pub(crate) fn invalid_anchor(anchor: impl Into) -> Error { Error::InvalidAnchor { anchor: anchor.into(), } } pub(crate) fn no_such_anchor(anchor: impl Into) -> Error { Error::NoSuchAnchor { anchor: anchor.into(), } } pub fn unknown_specification(specification: impl Into) -> Error { Error::UnknownSpecification { specification: specification.into(), } } pub fn circular_metaschema(uri: impl Into) -> Error { Error::CircularMetaschema { uri: uri.into() } } pub fn unretrievable( uri: impl Into, source: Box, ) -> Error { Error::Unretrievable { uri: uri.into(), source, } } pub(crate) fn uri_parsing_error(uri: impl Into, error: ParseError) -> Error { Error::InvalidUri(UriError::Parse { uri: uri.into(), is_reference: false, error, }) } pub(crate) fn uri_reference_parsing_error(uri: impl Into, error: ParseError) -> Error { Error::InvalidUri(UriError::Parse { uri: uri.into(), is_reference: true, error, }) } pub(crate) fn uri_resolving_error( uri: impl Into, base: Uri<&str>, error: ResolveError, ) -> Error { Error::InvalidUri(UriError::Resolve { uri: uri.into(), base: base.to_owned(), error, }) } } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Error::Unretrievable { uri, source } => { f.write_fmt(format_args!("Resource '{uri}' is not present in a registry and retrieving it failed: {source}")) }, Error::PointerToNowhere { pointer } => { f.write_fmt(format_args!("Pointer '{pointer}' does not exist")) } Error::InvalidPercentEncoding { pointer, .. } => { f.write_fmt(format_args!("Invalid percent encoding in pointer '{pointer}': the decoded bytes do not represent valid UTF-8")) } Error::InvalidArrayIndex { pointer, index, .. } => { f.write_fmt(format_args!("Failed to parse array index '{index}' in pointer '{pointer}'")) } Error::NoSuchAnchor { anchor } => { f.write_fmt(format_args!("Anchor '{anchor}' does not exist")) } Error::InvalidAnchor { anchor } => { f.write_fmt(format_args!("Anchor '{anchor}' is invalid")) } Error::InvalidUri(error) => error.fmt(f), Error::UnknownSpecification { specification } => { write!(f, "Unknown meta-schema: '{specification}'. Custom meta-schemas must be registered in the registry before use") } Error::CircularMetaschema { uri } => { write!(f, "Circular meta-schema reference detected at '{uri}'") } } } } impl std::error::Error for Error { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { Error::Unretrievable { source, .. } => Some(&**source), Error::InvalidUri(error) => Some(error), Error::InvalidPercentEncoding { source, .. } => Some(source), Error::InvalidArrayIndex { source, .. } => Some(source), _ => None, } } } /// Errors that can occur during URI handling. #[derive(Debug)] pub enum UriError { Parse { uri: String, is_reference: bool, error: ParseError, }, Resolve { uri: String, base: Uri, error: ResolveError, }, } impl fmt::Display for UriError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { UriError::Parse { uri, is_reference, error, } => { if *is_reference { f.write_fmt(format_args!("Invalid URI reference '{uri}': {error}")) } else { f.write_fmt(format_args!("Invalid URI '{uri}': {error}")) } } UriError::Resolve { uri, base, error } => f.write_fmt(format_args!( "Failed to resolve '{uri}' against '{base}': {error}" )), } } } impl std::error::Error for UriError { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { UriError::Parse { error, .. } => Some(error), UriError::Resolve { error, .. } => Some(error), } } } referencing-0.37.3/src/lib.rs000064400000000000000000000015011046102023000141050ustar 00000000000000//! # referencing //! //! An implementation-agnostic JSON reference resolution library for Rust. mod anchors; mod cache; mod error; mod list; pub mod meta; mod registry; mod resolver; mod resource; mod retriever; mod segments; mod specification; pub mod uri; mod vocabularies; pub(crate) use anchors::Anchor; pub use error::{Error, UriError}; pub use fluent_uri::{Iri, IriRef, Uri, UriRef}; pub use list::List; pub use registry::{parse_index, pointer, Registry, RegistryOptions, SPECIFICATIONS}; pub use resolver::{Resolved, Resolver}; pub use resource::{unescape_segment, Resource, ResourceRef}; pub use retriever::{DefaultRetriever, Retrieve}; pub(crate) use segments::Segments; pub use specification::Draft; pub use vocabularies::{Vocabulary, VocabularySet}; #[cfg(feature = "retrieve-async")] pub use retriever::AsyncRetrieve; referencing-0.37.3/src/list.rs000064400000000000000000000047421046102023000143240ustar 00000000000000use std::sync::Arc; /// An immutable singly-linked list. pub struct List { head: Option>>, } impl Clone for List { fn clone(&self) -> Self { List { head: self.head.clone(), } } } impl std::fmt::Debug for List { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_list().entries(self.iter()).finish() } } impl PartialEq for List { fn eq(&self, other: &Self) -> bool { self.iter().eq(other.iter()) } } impl Eq for List {} impl std::hash::Hash for List { fn hash(&self, state: &mut H) { for item in self { item.hash(state); } } } impl Drop for List { fn drop(&mut self) { let mut current = self.head.take(); while let Some(node) = current { if let Ok(mut node) = Arc::try_unwrap(node) { current = node.next.take(); } else { break; } } } } impl List { pub(crate) fn new() -> Self { Self { head: None } } /// Returns true if the list contains no elements. #[must_use] pub fn is_empty(&self) -> bool { self.head.is_none() } /// Creates a new list with the given value at the front, sharing the rest of the nodes. #[must_use] pub fn push_front(&self, value: Arc) -> Self { List { head: Some(Arc::new(Node { value, next: self.head.clone(), })), } } /// Returns an iterator over references to the list elements. #[must_use] pub fn iter(&self) -> Iter<'_, T> { Iter { current: self.head.as_ref(), } } } #[derive(Debug)] pub(crate) struct Node { value: Arc, next: Option>>, } /// Iterator over references to elements in a `List`. #[derive(Debug)] pub struct Iter<'a, T> { current: Option<&'a Arc>>, } impl<'a, T> Iterator for Iter<'a, T> { type Item = &'a T; fn next(&mut self) -> Option { self.current.map(|current| { let value = ¤t.value; self.current = current.next.as_ref(); &**value }) } } impl<'a, T> IntoIterator for &'a List { type IntoIter = Iter<'a, T>; type Item = &'a T; fn into_iter(self) -> Self::IntoIter { self.iter() } } referencing-0.37.3/src/meta.rs000064400000000000000000000177071046102023000143040ustar 00000000000000//! Built-in JSON Schema meta-schemas. //! //! This module provides access to the official JSON Schema meta-schemas for different draft versions. use serde_json::Value; use std::sync::{Arc, LazyLock}; use crate::Draft; macro_rules! schema { ($vis:vis $name:ident, $path:expr) => { $vis static $name: LazyLock> = LazyLock::new(|| { Arc::new(serde_json::from_slice(include_bytes!($path)).expect("Invalid schema")) }); }; ($name:ident, $path:expr) => { schema!(pub(crate) $name, $path); }; } schema!(pub DRAFT4, "../metaschemas/draft4.json"); schema!(pub DRAFT6, "../metaschemas/draft6.json"); schema!(pub DRAFT7, "../metaschemas/draft7.json"); schema!(pub DRAFT201909, "../metaschemas/draft2019-09/schema.json"); schema!( pub DRAFT201909_APPLICATOR, "../metaschemas/draft2019-09/meta/applicator.json" ); schema!( pub DRAFT201909_CONTENT, "../metaschemas/draft2019-09/meta/content.json" ); schema!( pub DRAFT201909_CORE, "../metaschemas/draft2019-09/meta/core.json" ); schema!( pub DRAFT201909_FORMAT, "../metaschemas/draft2019-09/meta/format.json" ); schema!( pub DRAFT201909_META_DATA, "../metaschemas/draft2019-09/meta/meta-data.json" ); schema!( pub DRAFT201909_VALIDATION, "../metaschemas/draft2019-09/meta/validation.json" ); schema!(pub DRAFT202012, "../metaschemas/draft2020-12/schema.json"); schema!( pub DRAFT202012_CORE, "../metaschemas/draft2020-12/meta/core.json" ); schema!( pub DRAFT202012_APPLICATOR, "../metaschemas/draft2020-12/meta/applicator.json" ); schema!( pub DRAFT202012_UNEVALUATED, "../metaschemas/draft2020-12/meta/unevaluated.json" ); schema!( pub DRAFT202012_VALIDATION, "../metaschemas/draft2020-12/meta/validation.json" ); schema!( pub DRAFT202012_META_DATA, "../metaschemas/draft2020-12/meta/meta-data.json" ); schema!( pub DRAFT202012_FORMAT_ANNOTATION, "../metaschemas/draft2020-12/meta/format-annotation.json" ); schema!( pub DRAFT202012_FORMAT_ASSERTION, "../metaschemas/draft2020-12/meta/format-assertion.json" ); schema!( pub DRAFT202012_CONTENT, "../metaschemas/draft2020-12/meta/content.json" ); pub(crate) static META_SCHEMAS_ALL: LazyLock<[(&'static str, &'static Value); 18]> = LazyLock::new(|| { [ ("http://json-schema.org/draft-04/schema#", &*DRAFT4), ("http://json-schema.org/draft-06/schema#", &*DRAFT6), ("http://json-schema.org/draft-07/schema#", &*DRAFT7), ( "https://json-schema.org/draft/2019-09/schema", &*DRAFT201909, ), ( "https://json-schema.org/draft/2019-09/meta/applicator", &*DRAFT201909_APPLICATOR, ), ( "https://json-schema.org/draft/2019-09/meta/content", &*DRAFT201909_CONTENT, ), ( "https://json-schema.org/draft/2019-09/meta/core", &*DRAFT201909_CORE, ), ( "https://json-schema.org/draft/2019-09/meta/format", &*DRAFT201909_FORMAT, ), ( "https://json-schema.org/draft/2019-09/meta/meta-data", &*DRAFT201909_META_DATA, ), ( "https://json-schema.org/draft/2019-09/meta/validation", &*DRAFT201909_VALIDATION, ), ( "https://json-schema.org/draft/2020-12/schema", &*DRAFT202012, ), ( "https://json-schema.org/draft/2020-12/meta/core", &*DRAFT202012_CORE, ), ( "https://json-schema.org/draft/2020-12/meta/applicator", &*DRAFT202012_APPLICATOR, ), ( "https://json-schema.org/draft/2020-12/meta/unevaluated", &*DRAFT202012_UNEVALUATED, ), ( "https://json-schema.org/draft/2020-12/meta/validation", &*DRAFT202012_VALIDATION, ), ( "https://json-schema.org/draft/2020-12/meta/meta-data", &*DRAFT202012_META_DATA, ), ( "https://json-schema.org/draft/2020-12/meta/format-annotation", &*DRAFT202012_FORMAT_ANNOTATION, ), ( "https://json-schema.org/draft/2020-12/meta/content", &*DRAFT202012_CONTENT, ), ] }); pub(crate) static META_SCHEMAS_DRAFT4: LazyLock<[(&'static str, &'static Value); 1]> = LazyLock::new(|| [("http://json-schema.org/draft-04/schema#", &*DRAFT4)]); pub(crate) static META_SCHEMAS_DRAFT6: LazyLock<[(&'static str, &'static Value); 1]> = LazyLock::new(|| [("http://json-schema.org/draft-06/schema#", &*DRAFT6)]); pub(crate) static META_SCHEMAS_DRAFT7: LazyLock<[(&'static str, &'static Value); 1]> = LazyLock::new(|| [("http://json-schema.org/draft-07/schema#", &*DRAFT7)]); pub(crate) static META_SCHEMAS_DRAFT2019: LazyLock<[(&'static str, &'static Value); 7]> = LazyLock::new(|| { [ ( "https://json-schema.org/draft/2019-09/schema", &*DRAFT201909, ), ( "https://json-schema.org/draft/2019-09/meta/applicator", &*DRAFT201909_APPLICATOR, ), ( "https://json-schema.org/draft/2019-09/meta/content", &*DRAFT201909_CONTENT, ), ( "https://json-schema.org/draft/2019-09/meta/core", &*DRAFT201909_CORE, ), ( "https://json-schema.org/draft/2019-09/meta/format", &*DRAFT201909_FORMAT, ), ( "https://json-schema.org/draft/2019-09/meta/meta-data", &*DRAFT201909_META_DATA, ), ( "https://json-schema.org/draft/2019-09/meta/validation", &*DRAFT201909_VALIDATION, ), ] }); pub(crate) static META_SCHEMAS_DRAFT2020: LazyLock<[(&'static str, &'static Value); 8]> = LazyLock::new(|| { [ ( "https://json-schema.org/draft/2020-12/schema", &*DRAFT202012, ), ( "https://json-schema.org/draft/2020-12/meta/core", &*DRAFT202012_CORE, ), ( "https://json-schema.org/draft/2020-12/meta/applicator", &*DRAFT202012_APPLICATOR, ), ( "https://json-schema.org/draft/2020-12/meta/unevaluated", &*DRAFT202012_UNEVALUATED, ), ( "https://json-schema.org/draft/2020-12/meta/validation", &*DRAFT202012_VALIDATION, ), ( "https://json-schema.org/draft/2020-12/meta/meta-data", &*DRAFT202012_META_DATA, ), ( "https://json-schema.org/draft/2020-12/meta/format-annotation", &*DRAFT202012_FORMAT_ANNOTATION, ), ( "https://json-schema.org/draft/2020-12/meta/content", &*DRAFT202012_CONTENT, ), ] }); /// Return all the meta-schemas which are part of a given draft. pub(crate) fn metas_for_draft(draft: Draft) -> &'static [(&'static str, &'static Value)] { match draft { Draft::Draft4 => &*META_SCHEMAS_DRAFT4, Draft::Draft6 => &*META_SCHEMAS_DRAFT6, Draft::Draft7 => &*META_SCHEMAS_DRAFT7, Draft::Draft201909 => &*META_SCHEMAS_DRAFT2019, // Unknown drafts default to 2020-12 vocabularies. // Custom meta-schemas should explicitly declare vocabularies in their $vocabulary field. Draft::Draft202012 | Draft::Unknown => &*META_SCHEMAS_DRAFT2020, } } referencing-0.37.3/src/registry.rs000064400000000000000000002032061046102023000152150ustar 00000000000000use std::{ collections::{hash_map::Entry, VecDeque}, num::NonZeroUsize, pin::Pin, sync::{Arc, LazyLock}, }; use ahash::{AHashMap, AHashSet}; use fluent_uri::Uri; use serde_json::Value; use crate::{ anchors::{AnchorKey, AnchorKeyRef}, cache::{SharedUriCache, UriCache}, meta::{self, metas_for_draft}, resource::{unescape_segment, InnerResourcePtr, JsonSchemaResource}, uri, vocabularies::{self, VocabularySet}, Anchor, DefaultRetriever, Draft, Error, Resolver, Resource, ResourceRef, Retrieve, }; /// An owned-or-refstatic wrapper for JSON `Value`. #[derive(Debug)] pub(crate) enum ValueWrapper { Owned(Value), StaticRef(&'static Value), } impl AsRef for ValueWrapper { fn as_ref(&self) -> &Value { match self { ValueWrapper::Owned(value) => value, ValueWrapper::StaticRef(value) => value, } } } // SAFETY: `Pin` guarantees stable memory locations for resource pointers, // while `Arc` enables cheap sharing between multiple registries type DocumentStore = AHashMap>, Pin>>; type ResourceMap = AHashMap>, InnerResourcePtr>; /// Pre-loaded registry containing all JSON Schema meta-schemas and their vocabularies pub static SPECIFICATIONS: LazyLock = LazyLock::new(|| Registry::build_from_meta_schemas(meta::META_SCHEMAS_ALL.as_slice())); /// A registry of JSON Schema resources, each identified by their canonical URIs. /// /// Registries store a collection of in-memory resources and their anchors. /// They eagerly process all added resources, including their subresources and anchors. /// This means that subresources contained within any added resources are immediately /// discoverable and retrievable via their own IDs. /// /// # Resource Retrieval /// /// Registry supports both blocking and non-blocking retrieval of external resources. /// /// ## Blocking Retrieval /// /// ```rust /// use referencing::{Registry, Resource, Retrieve, Uri}; /// use serde_json::{json, Value}; /// /// struct ExampleRetriever; /// /// impl Retrieve for ExampleRetriever { /// fn retrieve( /// &self, /// uri: &Uri /// ) -> Result> { /// // Always return the same value for brevity /// Ok(json!({"type": "string"})) /// } /// } /// /// # fn example() -> Result<(), Box> { /// let registry = Registry::options() /// .retriever(ExampleRetriever) /// .build([ /// // Initial schema that might reference external schemas /// ( /// "https://example.com/user.json", /// Resource::from_contents(json!({ /// "type": "object", /// "properties": { /// // Should be retrieved by `ExampleRetriever` /// "role": {"$ref": "https://example.com/role.json"} /// } /// })) /// ) /// ])?; /// # Ok(()) /// # } /// ``` /// /// ## Non-blocking Retrieval /// /// ```rust /// # #[cfg(feature = "retrieve-async")] /// # mod example { /// use referencing::{Registry, Resource, AsyncRetrieve, Uri}; /// use serde_json::{json, Value}; /// /// struct ExampleRetriever; /// /// #[cfg_attr(target_family = "wasm", async_trait::async_trait(?Send))] /// #[cfg_attr(not(target_family = "wasm"), async_trait::async_trait)] /// impl AsyncRetrieve for ExampleRetriever { /// async fn retrieve( /// &self, /// uri: &Uri /// ) -> Result> { /// // Always return the same value for brevity /// Ok(json!({"type": "string"})) /// } /// } /// /// # async fn example() -> Result<(), Box> { /// let registry = Registry::options() /// .async_retriever(ExampleRetriever) /// .build([ /// ( /// "https://example.com/user.json", /// Resource::from_contents(json!({ /// // Should be retrieved by `ExampleRetriever` /// "$ref": "https://example.com/common/user.json" /// })) /// ) /// ]) /// .await?; /// # Ok(()) /// # } /// # } /// ``` /// /// The registry will automatically: /// /// - Resolve external references /// - Cache retrieved schemas /// - Handle nested references /// - Process JSON Schema anchors /// #[derive(Debug)] pub struct Registry { documents: DocumentStore, pub(crate) resources: ResourceMap, anchors: AHashMap, resolution_cache: SharedUriCache, } impl Clone for Registry { fn clone(&self) -> Self { Self { documents: self.documents.clone(), resources: self.resources.clone(), anchors: self.anchors.clone(), resolution_cache: self.resolution_cache.clone(), } } } /// Configuration options for creating a [`Registry`]. pub struct RegistryOptions { retriever: R, draft: Draft, } impl RegistryOptions { /// Set specification version under which the resources should be interpreted under. #[must_use] pub fn draft(mut self, draft: Draft) -> Self { self.draft = draft; self } } impl RegistryOptions> { /// Create a new [`RegistryOptions`] with default settings. #[must_use] pub fn new() -> Self { Self { retriever: Arc::new(DefaultRetriever), draft: Draft::default(), } } /// Set a custom retriever for the [`Registry`]. #[must_use] pub fn retriever(mut self, retriever: impl IntoRetriever) -> Self { self.retriever = retriever.into_retriever(); self } /// Set a custom async retriever for the [`Registry`]. #[cfg(feature = "retrieve-async")] #[must_use] pub fn async_retriever( self, retriever: impl IntoAsyncRetriever, ) -> RegistryOptions> { RegistryOptions { retriever: retriever.into_retriever(), draft: self.draft, } } /// Create a [`Registry`] from multiple resources using these options. /// /// # Errors /// /// Returns an error if: /// - Any URI is invalid /// - Any referenced resources cannot be retrieved pub fn build( self, pairs: impl IntoIterator, Resource)>, ) -> Result { Registry::try_from_resources_impl(pairs, &*self.retriever, self.draft) } } #[cfg(feature = "retrieve-async")] impl RegistryOptions> { /// Create a [`Registry`] from multiple resources using these options with async retrieval. /// /// # Errors /// /// Returns an error if: /// - Any URI is invalid /// - Any referenced resources cannot be retrieved pub async fn build( self, pairs: impl IntoIterator, Resource)>, ) -> Result { Registry::try_from_resources_async_impl(pairs, &*self.retriever, self.draft).await } } pub trait IntoRetriever { fn into_retriever(self) -> Arc; } impl IntoRetriever for T { fn into_retriever(self) -> Arc { Arc::new(self) } } impl IntoRetriever for Arc { fn into_retriever(self) -> Arc { self } } #[cfg(feature = "retrieve-async")] pub trait IntoAsyncRetriever { fn into_retriever(self) -> Arc; } #[cfg(feature = "retrieve-async")] impl IntoAsyncRetriever for T { fn into_retriever(self) -> Arc { Arc::new(self) } } #[cfg(feature = "retrieve-async")] impl IntoAsyncRetriever for Arc { fn into_retriever(self) -> Arc { self } } impl Default for RegistryOptions> { fn default() -> Self { Self::new() } } impl Registry { /// Get [`RegistryOptions`] for configuring a new [`Registry`]. #[must_use] pub fn options() -> RegistryOptions> { RegistryOptions::new() } /// Create a new [`Registry`] with a single resource. /// /// # Arguments /// /// * `uri` - The URI of the resource. /// * `resource` - The resource to add. /// /// # Errors /// /// Returns an error if the URI is invalid or if there's an issue processing the resource. pub fn try_new(uri: impl AsRef, resource: Resource) -> Result { Self::try_new_impl(uri, resource, &DefaultRetriever, Draft::default()) } /// Create a new [`Registry`] from an iterator of (URI, Resource) pairs. /// /// # Arguments /// /// * `pairs` - An iterator of (URI, Resource) pairs. /// /// # Errors /// /// Returns an error if any URI is invalid or if there's an issue processing the resources. pub fn try_from_resources( pairs: impl IntoIterator, Resource)>, ) -> Result { Self::try_from_resources_impl(pairs, &DefaultRetriever, Draft::default()) } fn try_new_impl( uri: impl AsRef, resource: Resource, retriever: &dyn Retrieve, draft: Draft, ) -> Result { Self::try_from_resources_impl([(uri, resource)], retriever, draft) } fn try_from_resources_impl( pairs: impl IntoIterator, Resource)>, retriever: &dyn Retrieve, draft: Draft, ) -> Result { let mut documents = AHashMap::new(); let mut resources = ResourceMap::new(); let mut anchors = AHashMap::new(); let mut resolution_cache = UriCache::new(); let custom_metaschemas = process_resources( pairs, retriever, &mut documents, &mut resources, &mut anchors, &mut resolution_cache, draft, )?; // Validate that all custom $schema references are registered validate_custom_metaschemas(&custom_metaschemas, &resources)?; Ok(Registry { documents, resources, anchors, resolution_cache: resolution_cache.into_shared(), }) } /// Create a new [`Registry`] from an iterator of (URI, Resource) pairs using an async retriever. /// /// # Arguments /// /// * `pairs` - An iterator of (URI, Resource) pairs. /// /// # Errors /// /// Returns an error if any URI is invalid or if there's an issue processing the resources. #[cfg(feature = "retrieve-async")] async fn try_from_resources_async_impl( pairs: impl IntoIterator, Resource)>, retriever: &dyn crate::AsyncRetrieve, draft: Draft, ) -> Result { let mut documents = AHashMap::new(); let mut resources = ResourceMap::new(); let mut anchors = AHashMap::new(); let mut resolution_cache = UriCache::new(); let custom_metaschemas = process_resources_async( pairs, retriever, &mut documents, &mut resources, &mut anchors, &mut resolution_cache, draft, ) .await?; // Validate that all custom $schema references are registered validate_custom_metaschemas(&custom_metaschemas, &resources)?; Ok(Registry { documents, resources, anchors, resolution_cache: resolution_cache.into_shared(), }) } /// Create a new registry with a new resource. /// /// # Errors /// /// Returns an error if the URI is invalid or if there's an issue processing the resource. pub fn try_with_resource( self, uri: impl AsRef, resource: Resource, ) -> Result { let draft = resource.draft(); self.try_with_resources([(uri, resource)], draft) } /// Create a new registry with new resources. /// /// # Errors /// /// Returns an error if any URI is invalid or if there's an issue processing the resources. pub fn try_with_resources( self, pairs: impl IntoIterator, Resource)>, draft: Draft, ) -> Result { self.try_with_resources_and_retriever(pairs, &DefaultRetriever, draft) } /// Create a new registry with new resources and using the given retriever. /// /// # Errors /// /// Returns an error if any URI is invalid or if there's an issue processing the resources. pub fn try_with_resources_and_retriever( self, pairs: impl IntoIterator, Resource)>, retriever: &dyn Retrieve, draft: Draft, ) -> Result { let mut documents = self.documents; let mut resources = self.resources; let mut anchors = self.anchors; let mut resolution_cache = self.resolution_cache.into_local(); let custom_metaschemas = process_resources( pairs, retriever, &mut documents, &mut resources, &mut anchors, &mut resolution_cache, draft, )?; validate_custom_metaschemas(&custom_metaschemas, &resources)?; Ok(Registry { documents, resources, anchors, resolution_cache: resolution_cache.into_shared(), }) } /// Create a new registry with new resources and using the given non-blocking retriever. /// /// # Errors /// /// Returns an error if any URI is invalid or if there's an issue processing the resources. #[cfg(feature = "retrieve-async")] pub async fn try_with_resources_and_retriever_async( self, pairs: impl IntoIterator, Resource)>, retriever: &dyn crate::AsyncRetrieve, draft: Draft, ) -> Result { let mut documents = self.documents; let mut resources = self.resources; let mut anchors = self.anchors; let mut resolution_cache = self.resolution_cache.into_local(); let custom_metaschemas = process_resources_async( pairs, retriever, &mut documents, &mut resources, &mut anchors, &mut resolution_cache, draft, ) .await?; validate_custom_metaschemas(&custom_metaschemas, &resources)?; Ok(Registry { documents, resources, anchors, resolution_cache: resolution_cache.into_shared(), }) } /// Create a new [`Resolver`] for this registry with the given base URI. /// /// # Errors /// /// Returns an error if the base URI is invalid. pub fn try_resolver(&self, base_uri: &str) -> Result, Error> { let base = uri::from_str(base_uri)?; Ok(self.resolver(base)) } /// Create a new [`Resolver`] for this registry with a known valid base URI. #[must_use] pub fn resolver(&self, base_uri: Uri) -> Resolver<'_> { Resolver::new(self, Arc::new(base_uri)) } pub(crate) fn anchor<'a>(&self, uri: &'a Uri, name: &'a str) -> Result<&Anchor, Error> { let key = AnchorKeyRef::new(uri, name); if let Some(value) = self.anchors.get(key.borrow_dyn()) { return Ok(value); } let resource = &self.resources[uri]; if let Some(id) = resource.id() { let uri = uri::from_str(id)?; let key = AnchorKeyRef::new(&uri, name); if let Some(value) = self.anchors.get(key.borrow_dyn()) { return Ok(value); } } if name.contains('/') { Err(Error::invalid_anchor(name.to_string())) } else { Err(Error::no_such_anchor(name.to_string())) } } /// Resolves a reference URI against a base URI using registry's cache. /// /// # Errors /// /// Returns an error if base has not schema or there is a fragment. pub fn resolve_against(&self, base: &Uri<&str>, uri: &str) -> Result>, Error> { self.resolution_cache.resolve_against(base, uri) } /// Returns vocabulary set configured for given draft and contents. /// /// For custom meta-schemas (`Draft::Unknown`), looks up the meta-schema in the registry /// and extracts its `$vocabulary` declaration. If the meta-schema is not registered, /// returns the default Draft 2020-12 vocabularies. #[must_use] pub fn find_vocabularies(&self, draft: Draft, contents: &Value) -> VocabularySet { match draft.detect(contents) { Draft::Unknown => { // Custom/unknown meta-schema - try to look it up in the registry if let Some(specification) = contents .as_object() .and_then(|obj| obj.get("$schema")) .and_then(|s| s.as_str()) { if let Ok(mut uri) = uri::from_str(specification) { // Remove fragment for lookup (e.g., "http://example.com/schema#" -> "http://example.com/schema") // Resources are stored without fragments, so we must strip it to find the meta-schema uri.set_fragment(None); if let Some(resource) = self.resources.get(&uri) { // Found the custom meta-schema - extract vocabularies if let Ok(Some(vocabularies)) = vocabularies::find(resource.contents()) { return vocabularies; } } // Meta-schema not registered - this will be caught during compilation // For now, return default vocabularies to allow resource creation } } // Default to Draft 2020-12 vocabularies for unknown meta-schemas Draft::Unknown.default_vocabularies() } draft => draft.default_vocabularies(), } } /// Build a registry with all the given meta-schemas from specs. pub(crate) fn build_from_meta_schemas(schemas: &[(&'static str, &'static Value)]) -> Self { let schemas_count = schemas.len(); let pairs = schemas .iter() .map(|(uri, schema)| (uri, ResourceRef::from_contents(schema))); let mut documents = DocumentStore::with_capacity(schemas_count); let mut resources = ResourceMap::with_capacity(schemas_count); // The actual number of anchors and cache-entries varies across // drafts. We overshoot here to avoid reallocations, using the sum // over all specifications. let mut anchors = AHashMap::with_capacity(8); let mut resolution_cache = UriCache::with_capacity(35); process_meta_schemas( pairs, &mut documents, &mut resources, &mut anchors, &mut resolution_cache, ) .expect("Failed to process meta schemas"); Self { documents, resources, anchors, resolution_cache: resolution_cache.into_shared(), } } } fn process_meta_schemas( pairs: impl IntoIterator, ResourceRef<'static>)>, documents: &mut DocumentStore, resources: &mut ResourceMap, anchors: &mut AHashMap, resolution_cache: &mut UriCache, ) -> Result<(), Error> { let mut queue = VecDeque::with_capacity(32); for (uri, resource) in pairs { let uri = uri::from_str(uri.as_ref().trim_end_matches('#'))?; let key = Arc::new(uri); let contents: &'static Value = resource.contents(); let wrapped_value = Arc::pin(ValueWrapper::StaticRef(contents)); let resource = InnerResourcePtr::new((*wrapped_value).as_ref(), resource.draft()); documents.insert(Arc::clone(&key), wrapped_value); resources.insert(Arc::clone(&key), resource.clone()); queue.push_back((key, resource)); } // Process current queue and collect references to external resources while let Some((mut base, resource)) = queue.pop_front() { if let Some(id) = resource.id() { base = resolution_cache.resolve_against(&base.borrow(), id)?; resources.insert(base.clone(), resource.clone()); } // Look for anchors for anchor in resource.anchors() { anchors.insert(AnchorKey::new(base.clone(), anchor.name()), anchor); } // Process subresources for contents in resource.draft().subresources_of(resource.contents()) { let subresource = InnerResourcePtr::new(contents, resource.draft()); queue.push_back((base.clone(), subresource)); } } Ok(()) } #[derive(Hash, Eq, PartialEq)] struct ReferenceKey { base_ptr: NonZeroUsize, reference: String, } impl ReferenceKey { fn new(base: &Arc>, reference: &str) -> Self { Self { base_ptr: NonZeroUsize::new(Arc::as_ptr(base) as usize) .expect("Arc pointer should never be null"), reference: reference.to_owned(), } } } type ReferenceTracker = AHashSet; #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] enum ReferenceKind { Ref, Schema, } struct ProcessingState { queue: VecDeque<(Arc>, InnerResourcePtr)>, seen: ReferenceTracker, external: AHashSet<(String, Uri, ReferenceKind)>, scratch: String, refers_metaschemas: bool, custom_metaschemas: Vec>>, } impl ProcessingState { fn new() -> Self { Self { queue: VecDeque::with_capacity(32), seen: ReferenceTracker::new(), external: AHashSet::new(), scratch: String::new(), refers_metaschemas: false, custom_metaschemas: Vec::new(), } } } fn process_input_resources( pairs: impl IntoIterator, Resource)>, documents: &mut DocumentStore, resources: &mut ResourceMap, state: &mut ProcessingState, ) -> Result<(), Error> { for (uri, resource) in pairs { let uri = uri::from_str(uri.as_ref().trim_end_matches('#'))?; let key = Arc::new(uri); match documents.entry(Arc::clone(&key)) { Entry::Occupied(_) => {} Entry::Vacant(entry) => { let (draft, contents) = resource.into_inner(); let wrapped_value = Arc::pin(ValueWrapper::Owned(contents)); let resource = InnerResourcePtr::new((*wrapped_value).as_ref(), draft); resources.insert(Arc::clone(&key), resource.clone()); // Track resources with custom meta-schemas for later validation if draft == Draft::Unknown { state.custom_metaschemas.push(Arc::clone(&key)); } state.queue.push_back((key, resource)); entry.insert(wrapped_value); } } } Ok(()) } fn process_queue( state: &mut ProcessingState, resources: &mut ResourceMap, anchors: &mut AHashMap, resolution_cache: &mut UriCache, ) -> Result<(), Error> { while let Some((mut base, resource)) = state.queue.pop_front() { if let Some(id) = resource.id() { base = resolution_cache.resolve_against(&base.borrow(), id)?; resources.insert(base.clone(), resource.clone()); } for anchor in resource.anchors() { anchors.insert(AnchorKey::new(base.clone(), anchor.name()), anchor); } collect_external_resources( &base, resource.contents(), resource.contents(), &mut state.external, &mut state.seen, resolution_cache, &mut state.scratch, &mut state.refers_metaschemas, resource.draft(), )?; for contents in resource.draft().subresources_of(resource.contents()) { let subresource = InnerResourcePtr::new(contents, resource.draft()); state.queue.push_back((base.clone(), subresource)); } } Ok(()) } fn handle_fragment( uri: &Uri, resource: &InnerResourcePtr, key: &Arc>, default_draft: Draft, queue: &mut VecDeque<(Arc>, InnerResourcePtr)>, ) { if let Some(fragment) = uri.fragment() { if let Some(resolved) = pointer(resource.contents(), fragment.as_str()) { let draft = default_draft.detect(resolved); let contents = std::ptr::addr_of!(*resolved); let resource = InnerResourcePtr::new(contents, draft); queue.push_back((Arc::clone(key), resource)); } } } fn handle_metaschemas( refers_metaschemas: bool, resources: &mut ResourceMap, anchors: &mut AHashMap, draft_version: Draft, ) { if refers_metaschemas { let schemas = metas_for_draft(draft_version); let draft_registry = Registry::build_from_meta_schemas(schemas); resources.reserve(draft_registry.resources.len()); for (key, resource) in draft_registry.resources { resources.insert(key, resource.clone()); } anchors.reserve(draft_registry.anchors.len()); for (key, anchor) in draft_registry.anchors { anchors.insert(key, anchor); } } } fn create_resource( retrieved: Value, fragmentless: Uri, default_draft: Draft, documents: &mut DocumentStore, resources: &mut ResourceMap, custom_metaschemas: &mut Vec>>, ) -> (Arc>, InnerResourcePtr) { let draft = default_draft.detect(&retrieved); let wrapped_value = Arc::pin(ValueWrapper::Owned(retrieved)); let resource = InnerResourcePtr::new((*wrapped_value).as_ref(), draft); let key = Arc::new(fragmentless); documents.insert(Arc::clone(&key), wrapped_value); resources.insert(Arc::clone(&key), resource.clone()); // Track resources with custom meta-schemas for later validation if draft == Draft::Unknown { custom_metaschemas.push(Arc::clone(&key)); } (key, resource) } fn process_resources( pairs: impl IntoIterator, Resource)>, retriever: &dyn Retrieve, documents: &mut DocumentStore, resources: &mut ResourceMap, anchors: &mut AHashMap, resolution_cache: &mut UriCache, default_draft: Draft, ) -> Result>>, Error> { let mut state = ProcessingState::new(); process_input_resources(pairs, documents, resources, &mut state)?; loop { if state.queue.is_empty() && state.external.is_empty() { break; } process_queue(&mut state, resources, anchors, resolution_cache)?; // Retrieve external resources for (original, uri, kind) in state.external.drain() { let mut fragmentless = uri.clone(); fragmentless.set_fragment(None); if !resources.contains_key(&fragmentless) { let retrieved = match retriever.retrieve(&fragmentless) { Ok(retrieved) => retrieved, Err(error) => { handle_retrieve_error(&uri, &original, &fragmentless, error, kind)?; continue; } }; let (key, resource) = create_resource( retrieved, fragmentless, default_draft, documents, resources, &mut state.custom_metaschemas, ); handle_fragment(&uri, &resource, &key, default_draft, &mut state.queue); state.queue.push_back((key, resource)); } } } handle_metaschemas(state.refers_metaschemas, resources, anchors, default_draft); Ok(state.custom_metaschemas) } #[cfg(feature = "retrieve-async")] async fn process_resources_async( pairs: impl IntoIterator, Resource)>, retriever: &dyn crate::AsyncRetrieve, documents: &mut DocumentStore, resources: &mut ResourceMap, anchors: &mut AHashMap, resolution_cache: &mut UriCache, default_draft: Draft, ) -> Result>>, Error> { let mut state = ProcessingState::new(); process_input_resources(pairs, documents, resources, &mut state)?; loop { if state.queue.is_empty() && state.external.is_empty() { break; } process_queue(&mut state, resources, anchors, resolution_cache)?; if !state.external.is_empty() { let data = state .external .drain() .filter_map(|(original, uri, kind)| { let mut fragmentless = uri.clone(); fragmentless.set_fragment(None); if resources.contains_key(&fragmentless) { None } else { Some((original, uri, kind, fragmentless)) } }) .collect::>(); let results = { let futures = data .iter() .map(|(_, _, _, fragmentless)| retriever.retrieve(fragmentless)); futures::future::join_all(futures).await }; for ((original, uri, kind, fragmentless), result) in data.iter().zip(results) { let retrieved = match result { Ok(retrieved) => retrieved, Err(error) => { handle_retrieve_error(uri, original, fragmentless, error, *kind)?; continue; } }; let (key, resource) = create_resource( retrieved, fragmentless.clone(), default_draft, documents, resources, &mut state.custom_metaschemas, ); handle_fragment(uri, &resource, &key, default_draft, &mut state.queue); state.queue.push_back((key, resource)); } } } handle_metaschemas(state.refers_metaschemas, resources, anchors, default_draft); Ok(state.custom_metaschemas) } fn handle_retrieve_error( uri: &Uri, original: &str, fragmentless: &Uri, error: Box, kind: ReferenceKind, ) -> Result<(), Error> { match kind { ReferenceKind::Schema => { // $schema fetch failures are non-fatal during resource processing // Unregistered custom meta-schemas will be caught in validate_custom_metaschemas() Ok(()) } ReferenceKind::Ref => { // $ref fetch failures are fatal - they're required for validation if uri.scheme().as_str() == "json-schema" { Err(Error::unretrievable( original, "No base URI is available".into(), )) } else { Err(Error::unretrievable(fragmentless.as_str(), error)) } } } } fn validate_custom_metaschemas( custom_metaschemas: &[Arc>], resources: &ResourceMap, ) -> Result<(), Error> { // Only validate resources with Draft::Unknown for uri in custom_metaschemas { if let Some(resource) = resources.get(uri) { // Extract the $schema value from this resource if let Some(schema_uri) = resource .contents() .as_object() .and_then(|obj| obj.get("$schema")) .and_then(|s| s.as_str()) { // Check if this meta-schema is registered match uri::from_str(schema_uri) { Ok(mut meta_uri) => { // Remove fragment for lookup (e.g., "http://example.com/schema#" -> "http://example.com/schema") meta_uri.set_fragment(None); if !resources.contains_key(&meta_uri) { return Err(Error::unknown_specification(schema_uri)); } } Err(_) => { return Err(Error::unknown_specification(schema_uri)); } } } } } Ok(()) } fn collect_external_resources( base: &Arc>, root: &Value, contents: &Value, collected: &mut AHashSet<(String, Uri, ReferenceKind)>, seen: &mut ReferenceTracker, resolution_cache: &mut UriCache, scratch: &mut String, refers_metaschemas: &mut bool, draft: Draft, ) -> Result<(), Error> { // URN schemes are not supported for external resolution if base.scheme().as_str() == "urn" { return Ok(()); } macro_rules! on_reference { ($reference:expr, $key:literal) => { // Skip well-known schema references if $reference.starts_with("https://json-schema.org/draft/") || $reference.starts_with("http://json-schema.org/draft-") || base.as_str().starts_with("https://json-schema.org/draft/") { if $key == "$ref" { *refers_metaschemas = true; } } else if $reference != "#" { if mark_reference(seen, base, $reference) { // Handle local references separately as they may have nested references to external resources if $reference.starts_with('#') { // Use the root document for pointer resolution since local refs are always // relative to the document root, not the current subschema if let Some(referenced) = pointer(root, $reference.trim_start_matches('#')) { // Recursively collect from the referenced schema and all its subresources collect_external_resources_recursive( base, root, referenced, collected, seen, resolution_cache, scratch, refers_metaschemas, draft, )?; } } else { let resolved = if base.has_fragment() { let mut base_without_fragment = base.as_ref().clone(); base_without_fragment.set_fragment(None); let (path, fragment) = match $reference.split_once('#') { Some((path, fragment)) => (path, Some(fragment)), None => ($reference, None), }; let mut resolved = (*resolution_cache .resolve_against(&base_without_fragment.borrow(), path)?) .clone(); // Add the fragment back if present if let Some(fragment) = fragment { // It is cheaper to check if it is properly encoded than allocate given that // the majority of inputs do not need to be additionally encoded if let Some(encoded) = uri::EncodedString::new(fragment) { resolved = resolved.with_fragment(Some(encoded)); } else { uri::encode_to(fragment, scratch); resolved = resolved.with_fragment(Some( uri::EncodedString::new_or_panic(scratch), )); scratch.clear(); } } resolved } else { (*resolution_cache .resolve_against(&base.borrow(), $reference)?) .clone() }; let kind = if $key == "$schema" { ReferenceKind::Schema } else { ReferenceKind::Ref }; collected.insert(($reference.to_string(), resolved, kind)); } } } }; } if let Some(object) = contents.as_object() { if object.len() < 3 { for (key, value) in object { if key == "$ref" { if let Some(reference) = value.as_str() { on_reference!(reference, "$ref"); } } else if key == "$schema" { if let Some(reference) = value.as_str() { on_reference!(reference, "$schema"); } } } } else { if let Some(reference) = object.get("$ref").and_then(Value::as_str) { on_reference!(reference, "$ref"); } if let Some(reference) = object.get("$schema").and_then(Value::as_str) { on_reference!(reference, "$schema"); } } } Ok(()) } /// Recursively collect external resources from a schema and all its subresources. fn collect_external_resources_recursive( base: &Arc>, root: &Value, contents: &Value, collected: &mut AHashSet<(String, Uri, ReferenceKind)>, seen: &mut ReferenceTracker, resolution_cache: &mut UriCache, scratch: &mut String, refers_metaschemas: &mut bool, draft: Draft, ) -> Result<(), Error> { // First, collect from the current schema collect_external_resources( base, root, contents, collected, seen, resolution_cache, scratch, refers_metaschemas, draft, )?; // Then recursively process all subresources for subresource in draft.subresources_of(contents) { collect_external_resources_recursive( base, root, subresource, collected, seen, resolution_cache, scratch, refers_metaschemas, draft, )?; } Ok(()) } fn mark_reference(seen: &mut ReferenceTracker, base: &Arc>, reference: &str) -> bool { seen.insert(ReferenceKey::new(base, reference)) } /// Look up a value by a JSON Pointer. /// /// **NOTE**: A slightly faster version of pointer resolution based on `Value::pointer` from `serde_json`. pub fn pointer<'a>(document: &'a Value, pointer: &str) -> Option<&'a Value> { if pointer.is_empty() { return Some(document); } if !pointer.starts_with('/') { return None; } pointer.split('/').skip(1).map(unescape_segment).try_fold( document, |target, token| match target { Value::Object(map) => map.get(&*token), Value::Array(list) => parse_index(&token).and_then(|x| list.get(x)), _ => None, }, ) } // Taken from `serde_json`. #[must_use] pub fn parse_index(s: &str) -> Option { if s.starts_with('+') || (s.starts_with('0') && s.len() != 1) { return None; } s.parse().ok() } #[cfg(test)] mod tests { use std::error::Error as _; use ahash::AHashMap; use fluent_uri::Uri; use serde_json::{json, Value}; use test_case::test_case; use crate::{uri::from_str, Draft, Registry, Resource, Retrieve}; use super::{pointer, RegistryOptions, SPECIFICATIONS}; #[test] fn test_empty_pointer() { let document = json!({}); assert_eq!(pointer(&document, ""), Some(&document)); } #[test] fn test_invalid_uri_on_registry_creation() { let schema = Draft::Draft202012.create_resource(json!({})); let result = Registry::try_new(":/example.com", schema); let error = result.expect_err("Should fail"); assert_eq!( error.to_string(), "Invalid URI reference ':/example.com': unexpected character at index 0" ); let source_error = error.source().expect("Should have a source"); let inner_source = source_error.source().expect("Should have a source"); assert_eq!(inner_source.to_string(), "unexpected character at index 0"); } #[test] fn test_lookup_unresolvable_url() { // Create a registry with a single resource let schema = Draft::Draft202012.create_resource(json!({ "type": "object", "properties": { "foo": { "type": "string" } } })); let registry = Registry::try_new("http://example.com/schema1", schema).expect("Invalid resources"); // Attempt to create a resolver for a URL not in the registry let resolver = registry .try_resolver("http://example.com/non_existent_schema") .expect("Invalid base URI"); let result = resolver.lookup(""); assert_eq!( result.unwrap_err().to_string(), "Resource 'http://example.com/non_existent_schema' is not present in a registry and retrieving it failed: Retrieving external resources is not supported once the registry is populated" ); } #[test] fn test_relative_uri_without_base() { let schema = Draft::Draft202012.create_resource(json!({"$ref": "./virtualNetwork.json"})); let error = Registry::try_new("json-schema:///", schema).expect_err("Should fail"); assert_eq!(error.to_string(), "Resource './virtualNetwork.json' is not present in a registry and retrieving it failed: No base URI is available"); } #[test] fn test_try_with_resources_requires_registered_custom_meta_schema() { let base_registry = Registry::try_new( "http://example.com/root", Resource::from_contents(json!({"type": "object"})), ) .expect("Base registry should be created"); let custom_schema = Resource::from_contents(json!({ "$id": "http://example.com/custom", "$schema": "http://example.com/meta/custom", "type": "string" })); let error = base_registry .try_with_resources( [("http://example.com/custom", custom_schema)], Draft::default(), ) .expect_err("Extending registry must fail when the custom $schema is not registered"); let error_msg = error.to_string(); assert_eq!( error_msg, "Unknown meta-schema: 'http://example.com/meta/custom'. Custom meta-schemas must be registered in the registry before use" ); } #[test] fn test_try_with_resources_accepts_registered_custom_meta_schema_fragment() { let meta_schema = Resource::from_contents(json!({ "$id": "http://example.com/meta/custom#", "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "object" })); let registry = Registry::try_new("http://example.com/meta/custom#", meta_schema) .expect("Meta-schema should be registered successfully"); let schema = Resource::from_contents(json!({ "$id": "http://example.com/schemas/my-schema", "$schema": "http://example.com/meta/custom#", "type": "string" })); registry .clone() .try_with_resources( [("http://example.com/schemas/my-schema", schema)], Draft::default(), ) .expect("Schema should accept registered meta-schema URI with trailing '#'"); } #[test] fn test_chained_custom_meta_schemas() { // Meta-schema B (uses standard Draft 2020-12) let meta_schema_b = json!({ "$id": "json-schema:///meta/level-b", "$schema": "https://json-schema.org/draft/2020-12/schema", "$vocabulary": { "https://json-schema.org/draft/2020-12/vocab/core": true, "https://json-schema.org/draft/2020-12/vocab/validation": true, }, "type": "object", "properties": { "customProperty": {"type": "string"} } }); // Meta-schema A (uses Meta-schema B) let meta_schema_a = json!({ "$id": "json-schema:///meta/level-a", "$schema": "json-schema:///meta/level-b", "customProperty": "level-a-meta", "type": "object" }); // Schema (uses Meta-schema A) let schema = json!({ "$id": "json-schema:///schemas/my-schema", "$schema": "json-schema:///meta/level-a", "customProperty": "my-schema", "type": "string" }); // Register all meta-schemas and schema in a chained manner // All resources are provided upfront, so no external retrieval should occur Registry::try_from_resources([ ( "json-schema:///meta/level-b", Resource::from_contents(meta_schema_b), ), ( "json-schema:///meta/level-a", Resource::from_contents(meta_schema_a), ), ( "json-schema:///schemas/my-schema", Resource::from_contents(schema), ), ]) .expect("Chained custom meta-schemas should be accepted when all are registered"); } struct TestRetriever { schemas: AHashMap, } impl TestRetriever { fn new(schemas: AHashMap) -> Self { TestRetriever { schemas } } } impl Retrieve for TestRetriever { fn retrieve( &self, uri: &Uri, ) -> Result> { if let Some(value) = self.schemas.get(uri.as_str()) { Ok(value.clone()) } else { Err(format!("Failed to find {uri}").into()) } } } fn create_test_retriever(schemas: &[(&str, Value)]) -> TestRetriever { TestRetriever::new( schemas .iter() .map(|&(k, ref v)| (k.to_string(), v.clone())) .collect(), ) } struct TestCase { input_resources: Vec<(&'static str, Value)>, remote_resources: Vec<(&'static str, Value)>, expected_resolved_uris: Vec<&'static str>, } #[test_case( TestCase { input_resources: vec![ ("http://example.com/schema1", json!({"$ref": "http://example.com/schema2"})), ], remote_resources: vec![ ("http://example.com/schema2", json!({"type": "object"})), ], expected_resolved_uris: vec!["http://example.com/schema1", "http://example.com/schema2"], } ;"External ref at top")] #[test_case( TestCase { input_resources: vec![ ("http://example.com/schema1", json!({ "$defs": { "subschema": {"type": "string"} }, "$ref": "#/$defs/subschema" })), ], remote_resources: vec![], expected_resolved_uris: vec!["http://example.com/schema1"], } ;"Internal ref at top")] #[test_case( TestCase { input_resources: vec![ ("http://example.com/schema1", json!({"$ref": "http://example.com/schema2"})), ("http://example.com/schema2", json!({"type": "object"})), ], remote_resources: vec![], expected_resolved_uris: vec!["http://example.com/schema1", "http://example.com/schema2"], } ;"Ref to later resource")] #[test_case( TestCase { input_resources: vec![ ("http://example.com/schema1", json!({ "type": "object", "properties": { "prop1": {"$ref": "http://example.com/schema2"} } })), ], remote_resources: vec![ ("http://example.com/schema2", json!({"type": "string"})), ], expected_resolved_uris: vec!["http://example.com/schema1", "http://example.com/schema2"], } ;"External ref in subresource")] #[test_case( TestCase { input_resources: vec![ ("http://example.com/schema1", json!({ "type": "object", "properties": { "prop1": {"$ref": "#/$defs/subschema"} }, "$defs": { "subschema": {"type": "string"} } })), ], remote_resources: vec![], expected_resolved_uris: vec!["http://example.com/schema1"], } ;"Internal ref in subresource")] #[test_case( TestCase { input_resources: vec![ ("file:///schemas/main.json", json!({"$ref": "file:///schemas/external.json"})), ], remote_resources: vec![ ("file:///schemas/external.json", json!({"type": "object"})), ], expected_resolved_uris: vec!["file:///schemas/main.json", "file:///schemas/external.json"], } ;"File scheme: external ref at top")] #[test_case( TestCase { input_resources: vec![ ("file:///schemas/main.json", json!({"$ref": "subfolder/schema.json"})), ], remote_resources: vec![ ("file:///schemas/subfolder/schema.json", json!({"type": "string"})), ], expected_resolved_uris: vec!["file:///schemas/main.json", "file:///schemas/subfolder/schema.json"], } ;"File scheme: relative path ref")] #[test_case( TestCase { input_resources: vec![ ("file:///schemas/main.json", json!({ "type": "object", "properties": { "local": {"$ref": "local.json"}, "remote": {"$ref": "http://example.com/schema"} } })), ], remote_resources: vec![ ("file:///schemas/local.json", json!({"type": "string"})), ("http://example.com/schema", json!({"type": "number"})), ], expected_resolved_uris: vec![ "file:///schemas/main.json", "file:///schemas/local.json", "http://example.com/schema" ], } ;"File scheme: mixing with http scheme")] #[test_case( TestCase { input_resources: vec![ ("file:///C:/schemas/main.json", json!({"$ref": "/D:/other_schemas/schema.json"})), ], remote_resources: vec![ ("file:///D:/other_schemas/schema.json", json!({"type": "boolean"})), ], expected_resolved_uris: vec![ "file:///C:/schemas/main.json", "file:///D:/other_schemas/schema.json" ], } ;"File scheme: absolute path in Windows style")] #[test_case( TestCase { input_resources: vec![ ("http://example.com/schema1", json!({"$ref": "http://example.com/schema2"})), ], remote_resources: vec![ ("http://example.com/schema2", json!({"$ref": "http://example.com/schema3"})), ("http://example.com/schema3", json!({"$ref": "http://example.com/schema4"})), ("http://example.com/schema4", json!({"$ref": "http://example.com/schema5"})), ("http://example.com/schema5", json!({"type": "object"})), ], expected_resolved_uris: vec![ "http://example.com/schema1", "http://example.com/schema2", "http://example.com/schema3", "http://example.com/schema4", "http://example.com/schema5", ], } ;"Four levels of external references")] #[test_case( TestCase { input_resources: vec![ ("http://example.com/schema1", json!({"$ref": "http://example.com/schema2"})), ], remote_resources: vec![ ("http://example.com/schema2", json!({"$ref": "http://example.com/schema3"})), ("http://example.com/schema3", json!({"$ref": "http://example.com/schema4"})), ("http://example.com/schema4", json!({"$ref": "http://example.com/schema5"})), ("http://example.com/schema5", json!({"$ref": "http://example.com/schema6"})), ("http://example.com/schema6", json!({"$ref": "http://example.com/schema1"})), ], expected_resolved_uris: vec![ "http://example.com/schema1", "http://example.com/schema2", "http://example.com/schema3", "http://example.com/schema4", "http://example.com/schema5", "http://example.com/schema6", ], } ;"Five levels of external references with circular reference")] fn test_references_processing(test_case: TestCase) { let retriever = create_test_retriever(&test_case.remote_resources); let input_pairs = test_case .input_resources .clone() .into_iter() .map(|(uri, value)| (uri, Resource::from_contents(value))); let registry = Registry::options() .retriever(retriever) .build(input_pairs) .expect("Invalid resources"); // Verify that all expected URIs are resolved and present in resources for uri in test_case.expected_resolved_uris { let resolver = registry.try_resolver("").expect("Invalid base URI"); assert!(resolver.lookup(uri).is_ok()); } } #[test] fn test_default_retriever_with_remote_refs() { let result = Registry::try_from_resources([( "http://example.com/schema1", Resource::from_contents(json!({"$ref": "http://example.com/schema2"})), )]); let error = result.expect_err("Should fail"); assert_eq!(error.to_string(), "Resource 'http://example.com/schema2' is not present in a registry and retrieving it failed: Default retriever does not fetch resources"); assert!(error.source().is_some()); } #[test] fn test_options() { let _registry = RegistryOptions::default() .build([("", Resource::from_contents(json!({})))]) .expect("Invalid resources"); } #[test] fn test_registry_with_duplicate_input_uris() { let input_resources = vec![ ( "http://example.com/schema", json!({ "type": "object", "properties": { "foo": { "type": "string" } } }), ), ( "http://example.com/schema", json!({ "type": "object", "properties": { "bar": { "type": "number" } } }), ), ]; let result = Registry::try_from_resources( input_resources .into_iter() .map(|(uri, value)| (uri, Draft::Draft202012.create_resource(value))), ); assert!( result.is_ok(), "Failed to create registry with duplicate input URIs" ); let registry = result.unwrap(); let resource = registry .resources .get(&from_str("http://example.com/schema").expect("Invalid URI")) .unwrap(); let properties = resource .contents() .get("properties") .and_then(|v| v.as_object()) .unwrap(); assert!( !properties.contains_key("bar"), "Registry should contain the earliest added schema" ); assert!( properties.contains_key("foo"), "Registry should contain the overwritten schema" ); } #[test] fn test_resolver_debug() { let registry = SPECIFICATIONS .clone() .try_with_resource("http://example.com", Resource::from_contents(json!({}))) .expect("Invalid resource"); let resolver = registry .try_resolver("http://127.0.0.1/schema") .expect("Invalid base URI"); assert_eq!( format!("{resolver:?}"), "Resolver { base_uri: \"http://127.0.0.1/schema\", scopes: \"[]\" }" ); } #[test] fn test_try_with_resource() { let registry = SPECIFICATIONS .clone() .try_with_resource("http://example.com", Resource::from_contents(json!({}))) .expect("Invalid resource"); let resolver = registry.try_resolver("").expect("Invalid base URI"); let resolved = resolver .lookup("http://json-schema.org/draft-06/schema#/definitions/schemaArray") .expect("Lookup failed"); assert_eq!( resolved.contents(), &json!({ "type": "array", "minItems": 1, "items": { "$ref": "#" } }) ); } #[test] fn test_invalid_reference() { // Found via fuzzing let resource = Draft::Draft202012.create_resource(json!({"$schema": "$##"})); let _ = Registry::try_new("http://#/", resource); } } #[cfg(all(test, feature = "retrieve-async"))] mod async_tests { use crate::{uri, DefaultRetriever, Draft, Registry, Resource, Uri}; use ahash::AHashMap; use serde_json::{json, Value}; use std::error::Error; struct TestAsyncRetriever { schemas: AHashMap, } impl TestAsyncRetriever { fn with_schema(uri: impl Into, schema: Value) -> Self { TestAsyncRetriever { schemas: { AHashMap::from_iter([(uri.into(), schema)]) }, } } } #[cfg_attr(target_family = "wasm", async_trait::async_trait(?Send))] #[cfg_attr(not(target_family = "wasm"), async_trait::async_trait)] impl crate::AsyncRetrieve for TestAsyncRetriever { async fn retrieve( &self, uri: &Uri, ) -> Result> { self.schemas .get(uri.as_str()) .cloned() .ok_or_else(|| "Schema not found".into()) } } #[tokio::test] async fn test_default_async_retriever_with_remote_refs() { let result = Registry::options() .async_retriever(DefaultRetriever) .build([( "http://example.com/schema1", Resource::from_contents(json!({"$ref": "http://example.com/schema2"})), )]) .await; let error = result.expect_err("Should fail"); assert_eq!(error.to_string(), "Resource 'http://example.com/schema2' is not present in a registry and retrieving it failed: Default retriever does not fetch resources"); assert!(error.source().is_some()); } #[tokio::test] async fn test_async_options() { let _registry = Registry::options() .async_retriever(DefaultRetriever) .build([("", Draft::default().create_resource(json!({})))]) .await .expect("Invalid resources"); } #[tokio::test] async fn test_async_registry_with_duplicate_input_uris() { let input_resources = vec![ ( "http://example.com/schema", json!({ "type": "object", "properties": { "foo": { "type": "string" } } }), ), ( "http://example.com/schema", json!({ "type": "object", "properties": { "bar": { "type": "number" } } }), ), ]; let result = Registry::options() .async_retriever(DefaultRetriever) .build( input_resources .into_iter() .map(|(uri, value)| (uri, Draft::Draft202012.create_resource(value))), ) .await; assert!( result.is_ok(), "Failed to create registry with duplicate input URIs" ); let registry = result.unwrap(); let resource = registry .resources .get(&uri::from_str("http://example.com/schema").expect("Invalid URI")) .unwrap(); let properties = resource .contents() .get("properties") .and_then(|v| v.as_object()) .unwrap(); assert!( !properties.contains_key("bar"), "Registry should contain the earliest added schema" ); assert!( properties.contains_key("foo"), "Registry should contain the overwritten schema" ); } #[tokio::test] async fn test_async_try_with_resource() { let retriever = TestAsyncRetriever::with_schema( "http://example.com/schema2", json!({"type": "object"}), ); let registry = Registry::options() .async_retriever(retriever) .build([( "http://example.com", Resource::from_contents(json!({"$ref": "http://example.com/schema2"})), )]) .await .expect("Invalid resource"); let resolver = registry.try_resolver("").expect("Invalid base URI"); let resolved = resolver .lookup("http://example.com/schema2") .expect("Lookup failed"); assert_eq!(resolved.contents(), &json!({"type": "object"})); } #[tokio::test] async fn test_async_registry_with_multiple_refs() { let retriever = TestAsyncRetriever { schemas: AHashMap::from_iter([ ( "http://example.com/schema2".to_string(), json!({"type": "object"}), ), ( "http://example.com/schema3".to_string(), json!({"type": "string"}), ), ]), }; let registry = Registry::options() .async_retriever(retriever) .build([( "http://example.com/schema1", Resource::from_contents(json!({ "type": "object", "properties": { "obj": {"$ref": "http://example.com/schema2"}, "str": {"$ref": "http://example.com/schema3"} } })), )]) .await .expect("Invalid resource"); let resolver = registry.try_resolver("").expect("Invalid base URI"); // Check both references are resolved correctly let resolved2 = resolver .lookup("http://example.com/schema2") .expect("Lookup failed"); assert_eq!(resolved2.contents(), &json!({"type": "object"})); let resolved3 = resolver .lookup("http://example.com/schema3") .expect("Lookup failed"); assert_eq!(resolved3.contents(), &json!({"type": "string"})); } #[tokio::test] async fn test_async_registry_with_nested_refs() { let retriever = TestAsyncRetriever { schemas: AHashMap::from_iter([ ( "http://example.com/address".to_string(), json!({ "type": "object", "properties": { "street": {"type": "string"}, "city": {"$ref": "http://example.com/city"} } }), ), ( "http://example.com/city".to_string(), json!({ "type": "string", "minLength": 1 }), ), ]), }; let registry = Registry::options() .async_retriever(retriever) .build([( "http://example.com/person", Resource::from_contents(json!({ "type": "object", "properties": { "name": {"type": "string"}, "address": {"$ref": "http://example.com/address"} } })), )]) .await .expect("Invalid resource"); let resolver = registry.try_resolver("").expect("Invalid base URI"); // Verify nested reference resolution let resolved = resolver .lookup("http://example.com/city") .expect("Lookup failed"); assert_eq!( resolved.contents(), &json!({"type": "string", "minLength": 1}) ); } } referencing-0.37.3/src/resolver.rs000064400000000000000000000162671046102023000152170ustar 00000000000000use core::fmt; use std::sync::Arc; use fluent_uri::Uri; use serde_json::Value; use crate::{list::List, resource::JsonSchemaResource, Draft, Error, Registry, ResourceRef}; /// A reference resolver. /// /// Resolves references against the base URI and looks up the result in the registry. #[derive(Clone)] pub struct Resolver<'r> { pub(crate) registry: &'r Registry, base_uri: Arc>, scopes: List>, } impl PartialEq for Resolver<'_> { fn eq(&self, other: &Self) -> bool { self.base_uri == other.base_uri } } impl Eq for Resolver<'_> {} impl fmt::Debug for Resolver<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Resolver") .field("base_uri", &self.base_uri.as_str()) .field("scopes", &{ let mut buf = String::from("["); let mut values = self.scopes.iter(); if let Some(value) = values.next() { buf.push_str(value.as_str()); } for value in values { buf.push_str(", "); buf.push_str(value.as_str()); } buf.push(']'); buf }) .finish() } } impl<'r> Resolver<'r> { /// Create a new `Resolver` with the given registry and base URI. pub(crate) fn new(registry: &'r Registry, base_uri: Arc>) -> Self { Self { registry, base_uri, scopes: List::new(), } } #[must_use] pub fn base_uri(&self) -> Arc> { self.base_uri.clone() } /// Resolve a reference to the resource it points to. /// /// # Errors /// /// If the reference cannot be resolved or is invalid. pub fn lookup(&self, reference: &str) -> Result, Error> { let (uri, fragment) = if let Some(reference) = reference.strip_prefix('#') { (self.base_uri.clone(), reference) } else { let (uri, fragment) = if let Some((uri, fragment)) = reference.rsplit_once('#') { (uri, fragment) } else { (reference, "") }; let uri = self .registry .resolve_against(&self.base_uri.borrow(), uri)?; (uri, fragment) }; let Some(retrieved) = self.registry.resources.get(&*uri) else { return Err(Error::unretrievable( uri.as_str(), "Retrieving external resources is not supported once the registry is populated" .into(), )); }; if fragment.starts_with('/') { let resolver = self.evolve(uri); return retrieved.pointer(fragment, resolver); } if !fragment.is_empty() { let retrieved = self.registry.anchor(&uri, fragment)?; let resolver = self.evolve(uri); return retrieved.resolve(resolver); } let resolver = self.evolve(uri); Ok(Resolved::new( retrieved.contents(), resolver, retrieved.draft(), )) } /// Resolve a recursive reference. /// /// This method implements the recursive reference resolution algorithm /// as specified in JSON Schema Draft 2019-09. /// /// It starts by resolving "#" and then follows the dynamic scope, /// looking for resources with `$recursiveAnchor: true`. /// /// # Errors /// /// This method can return any error that [`Resolver::lookup`] can return. pub fn lookup_recursive_ref(&self) -> Result, Error> { let mut resolved = self.lookup("#")?; if let Value::Object(obj) = resolved.contents { if obj .get("$recursiveAnchor") .and_then(Value::as_bool) .unwrap_or(false) { for uri in &self.dynamic_scope() { let next_resolved = self.lookup(uri.as_str())?; match next_resolved.contents { Value::Object(next_obj) => { if !next_obj .get("$recursiveAnchor") .and_then(Value::as_bool) .unwrap_or(false) { break; } } _ => break, } resolved = next_resolved; } } } Ok(resolved) } /// Create a resolver for a subresource. /// /// # Errors /// /// Returns an error if the resource id cannot be resolved against the base URI of this resolver. pub fn in_subresource(&self, subresource: ResourceRef<'_>) -> Result { self.in_subresource_inner(&subresource) } pub(crate) fn in_subresource_inner( &self, subresource: &impl JsonSchemaResource, ) -> Result { if let Some(id) = subresource.id() { let base_uri = self.registry.resolve_against(&self.base_uri.borrow(), id)?; Ok(Resolver { registry: self.registry, base_uri, scopes: self.scopes.clone(), }) } else { Ok(self.clone()) } } #[must_use] pub fn dynamic_scope(&self) -> List> { self.scopes.clone() } fn evolve(&self, base_uri: Arc>) -> Resolver<'r> { if !self.base_uri.as_str().is_empty() && (self.scopes.is_empty() || base_uri != self.base_uri) { Resolver { registry: self.registry, base_uri, scopes: self.scopes.push_front(self.base_uri.clone()), } } else { Resolver { registry: self.registry, base_uri, scopes: self.scopes.clone(), } } } /// Resolve a reference against a base. /// /// # Errors /// /// If the reference is invalid. pub fn resolve_against(&self, base: &Uri<&str>, uri: &str) -> Result>, Error> { self.registry.resolve_against(base, uri) } } /// A reference resolved to its contents by a [`Resolver`]. #[derive(Debug)] pub struct Resolved<'r> { /// The contents of the resolved reference. contents: &'r Value, /// The resolver that resolved this reference, which can be used for further resolutions. resolver: Resolver<'r>, draft: Draft, } impl<'r> Resolved<'r> { pub(crate) fn new(contents: &'r Value, resolver: Resolver<'r>, draft: Draft) -> Self { Self { contents, resolver, draft, } } /// Resolved contents. #[must_use] pub fn contents(&self) -> &'r Value { self.contents } /// Resolver used to resolve this contents. #[must_use] pub fn resolver(&self) -> &Resolver<'r> { &self.resolver } #[must_use] pub fn into_inner(self) -> (&'r Value, Resolver<'r>, Draft) { (self.contents, self.resolver, self.draft) } } referencing-0.37.3/src/resource.rs000064400000000000000000000303271046102023000151760ustar 00000000000000use std::{ borrow::Cow, sync::atomic::{AtomicPtr, Ordering}, }; use serde_json::Value; use crate::{Anchor, Draft, Error, Resolved, Resolver, Segments}; pub(crate) trait JsonSchemaResource { fn contents(&self) -> &Value; fn draft(&self) -> Draft; fn id(&self) -> Option<&str> { self.draft() .id_of(self.contents()) .map(|id| id.trim_end_matches('#')) } } /// An owned document with a concrete interpretation under a JSON Schema specification. #[derive(Debug, Clone, PartialEq, Eq)] pub struct Resource { contents: Value, draft: Draft, } impl Resource { pub(crate) fn new(contents: Value, draft: Draft) -> Self { Self { contents, draft } } pub(crate) fn into_inner(self) -> (Draft, Value) { (self.draft, self.contents) } /// Resource contents. #[must_use] pub fn contents(&self) -> &Value { &self.contents } /// JSON Schema draft under which this contents is interpreted. #[must_use] pub fn draft(&self) -> Draft { self.draft } /// Create a resource with automatically detecting specification which applies to the contents. /// /// Unknown `$schema` values are treated as `Draft::Unknown`. #[must_use] pub fn from_contents(contents: Value) -> Resource { Draft::default().detect(&contents).create_resource(contents) } } /// A borrowed document with a concrete interpretation under a JSON Schema specification. #[derive(Debug, Clone, Copy)] pub struct ResourceRef<'a> { contents: &'a Value, draft: Draft, } impl<'a> ResourceRef<'a> { #[must_use] pub fn new(contents: &'a Value, draft: Draft) -> Self { Self { contents, draft } } #[must_use] pub fn contents(&self) -> &'a Value { self.contents } #[must_use] pub fn draft(&self) -> Draft { self.draft } /// Create a resource-ref with automatically detecting specification which applies to the contents. /// /// Unknown `$schema` values are treated as `Draft::Unknown`. #[must_use] pub fn from_contents(contents: &'a Value) -> Self { let draft = Draft::default().detect(contents); Self::new(contents, draft) } #[must_use] pub fn id(&self) -> Option<&str> { JsonSchemaResource::id(self) } } impl JsonSchemaResource for ResourceRef<'_> { fn contents(&self) -> &Value { self.contents } fn draft(&self) -> Draft { self.draft } } /// A pointer to a pinned resource. pub(crate) struct InnerResourcePtr { contents: AtomicPtr, draft: Draft, } impl Clone for InnerResourcePtr { fn clone(&self) -> Self { Self { contents: AtomicPtr::new(self.contents.load(Ordering::Relaxed)), draft: self.draft, } } } impl std::fmt::Debug for InnerResourcePtr { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("InnerResourcePtr") .field("contents", self.contents()) .field("draft", &self.draft) .finish() } } impl InnerResourcePtr { pub(crate) fn new(contents: *const Value, draft: Draft) -> Self { Self { contents: AtomicPtr::new(contents.cast_mut()), draft, } } #[allow(unsafe_code)] pub(crate) fn contents(&self) -> &Value { // SAFETY: The pointer is valid as long as the registry exists unsafe { &*self.contents.load(Ordering::Relaxed) } } #[inline] pub(crate) fn draft(&self) -> Draft { self.draft } pub(crate) fn anchors(&self) -> impl Iterator + '_ { self.draft().anchors(self.contents()) } pub(crate) fn pointer<'r>( &'r self, pointer: &str, mut resolver: Resolver<'r>, ) -> Result, Error> { // INVARIANT: Pointer always starts with `/` let mut contents = self.contents(); let mut segments = Segments::new(); let original_pointer = pointer; let pointer = percent_encoding::percent_decode_str(&pointer[1..]) .decode_utf8() .map_err(|err| Error::invalid_percent_encoding(original_pointer, err))?; for segment in pointer.split('/') { if let Some(array) = contents.as_array() { let idx = segment .parse::() .map_err(|err| Error::invalid_array_index(original_pointer, segment, err))?; if let Some(next) = array.get(idx) { contents = next; } else { return Err(Error::pointer_to_nowhere(original_pointer)); } segments.push(idx); } else { let segment = unescape_segment(segment); if let Some(next) = contents.get(segment.as_ref()) { contents = next; } else { return Err(Error::pointer_to_nowhere(original_pointer)); } segments.push(segment); } let last = &resolver; let new_resolver = self.draft().maybe_in_subresource( &segments, &resolver, &InnerResourcePtr::new(contents, self.draft()), )?; if new_resolver != *last { segments = Segments::new(); } resolver = new_resolver; } Ok(Resolved::new(contents, resolver, self.draft())) } } impl JsonSchemaResource for InnerResourcePtr { fn contents(&self) -> &Value { self.contents() } fn draft(&self) -> Draft { self.draft } } /// Unescape JSON Pointer path segments by converting `~1` to `/` and `~0` to `~`. #[must_use] pub fn unescape_segment(mut segment: &str) -> Cow<'_, str> { // Naively, checking for `~` and then replacing implies two passes // over the input buffer. First, search in the first `contains('~')` call // and then replacing `~1` & `~0` at once in a single pass. // // This implementation is ~3x faster than the naive one. // // **NOTE**: Heavily inspired by the implementation in `boon`: // `https://github.com/santhosh-tekuri/boon/blob/fb09df2db19be75c32c0970b4bdedf1655f5f943/src/util.rs#L31` let Some(mut tilde_idx) = segment.find('~') else { return Cow::Borrowed(segment); }; let mut buffer = String::with_capacity(segment.len()); loop { let (before, after) = segment.split_at(tilde_idx); buffer.push_str(before); segment = &after[1..]; let next_char_size = match segment.chars().next() { Some('1') => { buffer.push('/'); 1 } Some('0') => { buffer.push('~'); 1 } Some(next) => { buffer.push('~'); buffer.push(next); next.len_utf8() } None => { buffer.push('~'); break; } }; segment = &segment[next_char_size..]; let Some(next_tilde_idx) = segment.find('~') else { buffer.push_str(segment); break; }; tilde_idx = next_tilde_idx; } Cow::Owned(buffer) } #[cfg(test)] mod tests { use std::{error::Error, sync::Arc}; use crate::{resource::InnerResourcePtr, Draft, Registry}; use super::unescape_segment; use serde_json::json; use test_case::test_case; #[test_case("abc")] #[test_case("a~0b")] #[test_case("a~1b")] #[test_case("~01")] #[test_case("~10")] #[test_case("a~0~1b")] #[test_case("~"; "single tilde")] #[test_case("~~"; "double tilde")] #[test_case("~~~~~"; "many tildas")] #[test_case("~2")] #[test_case("a~c")] #[test_case("~0~1~")] #[test_case("")] #[test_case("a/d")] #[test_case("a~01b")] #[test_case("🌟~0🌠~1🌡️"; "Emojis with escapes")] #[test_case("~🌟"; "Tilde followed by emoji")] #[test_case("Café~0~1"; "Accented characters with escapes")] #[test_case("~é"; "Tilde followed by accented character")] #[test_case("αβγ"; "Greek")] #[test_case("~αβγ"; "Tilde followed by Greek")] #[test_case("∀∂∈ℝ∧∪≡∞"; "Mathematical symbols")] #[test_case("~∀∂∈ℝ∧∪≡∞"; "Tilde followed by mathematical symbols")] #[test_case("¡¢£¤¥¦§¨©"; "Special characters")] #[test_case("~¡¢£¤¥¦§¨©"; "Tilde followed by special characters")] #[test_case("\u{10FFFF}"; "Highest valid Unicode code point")] #[test_case("~\u{10FFFF}"; "Tilde followed by highest valid Unicode code point")] fn test_unescape_segment_equivalence(input: &str) { let unescaped = unescape_segment(input); let double_replaced = input.replace("~1", "/").replace("~0", "~"); assert_eq!(unescaped, double_replaced, "Failed for: {input}"); } fn create_test_registry() -> Registry { let schema = Draft::Draft202012.create_resource(json!({ "type": "object", "properties": { "foo": { "type": "string" }, "bar": { "type": "array", "items": [{"type": "number"}, {"type": "boolean"}] } } })); Registry::try_new("http://example.com", schema).expect("Invalid resources") } #[test] fn test_empty_ref() { let schema = Draft::Draft202012.create_resource(json!({ "type": "object", "properties": { "foo": { "type": "string" } } })); let registry = Registry::try_new("http://example.com", schema.clone()).expect("Invalid resources"); let resolver = registry .try_resolver("http://example.com") .expect("Invalid base URI"); let resolved = resolver.lookup("#").expect("Lookup failed"); assert_eq!(resolved.contents(), schema.contents()); } #[test] fn test_inner_resource_ptr_debug() { let value = Arc::pin(json!({ "foo": "bar", "number": 42 })); let ptr = InnerResourcePtr::new(std::ptr::addr_of!(*value), Draft::Draft202012); let expected = format!( "InnerResourcePtr {{ contents: {:?}, draft: Draft202012 }}", *value ); assert_eq!(format!("{ptr:?}"), expected); } #[test] fn test_percent_encoded_non_utf8() { let registry = create_test_registry(); let resolver = registry .try_resolver("http://example.com") .expect("Invalid base URI"); let result = resolver.lookup("#/%FF"); let error = result.expect_err("Should fail"); assert_eq!( error.to_string(), "Invalid percent encoding in pointer '/%FF': the decoded bytes do not represent valid UTF-8" ); assert!(error.source().is_some()); } #[test] fn test_array_index_as_string() { let registry = create_test_registry(); let resolver = registry .try_resolver("http://example.com") .expect("Invalid base URI"); let result = resolver.lookup("#/properties/bar/items/one"); let error = result.expect_err("Should fail"); assert_eq!( error.to_string(), "Failed to parse array index 'one' in pointer '/properties/bar/items/one'" ); assert!(error.source().is_some()); } #[test] fn test_array_index_out_of_bounds() { let registry = create_test_registry(); let resolver = registry .try_resolver("http://example.com") .expect("Invalid base URI"); let result = resolver.lookup("#/properties/bar/items/2"); assert_eq!( result.expect_err("Should fail").to_string(), "Pointer '/properties/bar/items/2' does not exist" ); } #[test] fn test_unknown_property() { let registry = create_test_registry(); let resolver = registry .try_resolver("http://example.com") .expect("Invalid base URI"); let result = resolver.lookup("#/properties/baz"); assert_eq!( result.expect_err("Should fail").to_string(), "Pointer '/properties/baz' does not exist" ); } } referencing-0.37.3/src/retriever.rs000064400000000000000000000054621046102023000153600ustar 00000000000000use core::fmt; use fluent_uri::Uri; use serde_json::Value; #[cfg(target_family = "wasm")] pub trait RetrieverBounds {} #[cfg(target_family = "wasm")] impl RetrieverBounds for T {} #[cfg(not(target_family = "wasm"))] pub trait RetrieverBounds: Send + Sync {} #[cfg(not(target_family = "wasm"))] impl RetrieverBounds for T {} /// Trait for retrieving resources from external sources. /// /// Implementors of this trait can be used to fetch resources that are not /// initially present in a [`crate::Registry`]. pub trait Retrieve: RetrieverBounds { /// Attempt to retrieve a resource from the given URI. /// /// # Arguments /// /// * `uri` - The URI of the resource to retrieve. /// /// # Errors /// /// This method can fail for various reasons: /// - Resource not found /// - Network errors (for remote resources) /// - Permission errors fn retrieve( &self, uri: &Uri, ) -> Result>; } #[derive(Debug, Clone)] struct DefaultRetrieverError; impl fmt::Display for DefaultRetrieverError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str("Default retriever does not fetch resources") } } impl std::error::Error for DefaultRetrieverError {} /// A retriever that always fails, used as a default when external resource fetching is not needed. #[derive(Debug, PartialEq, Eq)] pub struct DefaultRetriever; impl Retrieve for DefaultRetriever { fn retrieve(&self, _: &Uri) -> Result> { Err(Box::new(DefaultRetrieverError)) } } #[cfg(feature = "retrieve-async")] #[cfg_attr(target_family = "wasm", async_trait::async_trait(?Send))] #[cfg_attr(not(target_family = "wasm"), async_trait::async_trait)] pub trait AsyncRetrieve: RetrieverBounds { /// Asynchronously retrieve a resource from the given URI. /// /// This is the non-blocking equivalent of [`Retrieve::retrieve`]. /// /// # Arguments /// /// * `uri` - The URI of the resource to retrieve. /// /// # Errors /// /// This method can fail for various reasons: /// - Resource not found /// - Network errors (for remote resources) /// - Permission errors async fn retrieve( &self, uri: &Uri, ) -> Result>; } #[cfg(feature = "retrieve-async")] #[cfg_attr(target_family = "wasm", async_trait::async_trait(?Send))] #[cfg_attr(not(target_family = "wasm"), async_trait::async_trait)] impl AsyncRetrieve for DefaultRetriever { async fn retrieve( &self, _: &Uri, ) -> Result> { Err(Box::new(DefaultRetrieverError)) } } referencing-0.37.3/src/segments.rs000064400000000000000000000021461046102023000151720ustar 00000000000000use std::borrow::Cow; /// Represents a sequence of segments in a JSON pointer. /// /// Used to track the path during JSON pointer resolution. pub(crate) struct Segments<'a>(Vec>); impl<'a> Segments<'a> { /// Creates a new, empty `Segments` instance. pub(crate) fn new() -> Self { Self(Vec::new()) } /// Adds a new segment to the sequence. pub(crate) fn push(&mut self, segment: impl Into>) { self.0.push(segment.into()); } /// Returns an iterator over the segments. pub(crate) fn iter(&self) -> impl Iterator> { self.0.iter() } } /// Represents a single segment in a JSON pointer. #[derive(Debug, PartialEq, Eq, Clone)] pub(crate) enum Segment<'a> { /// A string key for object properties. Key(Cow<'a, str>), /// A numeric index for array elements. Index(usize), } impl<'a> From> for Segment<'a> { fn from(value: Cow<'a, str>) -> Self { Segment::Key(value) } } impl From for Segment<'_> { fn from(value: usize) -> Self { Segment::Index(value) } } referencing-0.37.3/src/specification/draft201909.rs000064400000000000000000000055741046102023000177620ustar 00000000000000use serde_json::Value; use crate::{resource::InnerResourcePtr, segments::Segment, Error, Resolver, Segments}; use super::subresources::SubresourceIteratorInner; pub(crate) fn object_iter<'a>( (key, value): (&'a String, &'a Value), ) -> SubresourceIteratorInner<'a> { match key.as_str() { // For these keys, yield the value once. "additionalItems" | "additionalProperties" | "contains" | "contentSchema" | "else" | "if" | "not" | "propertyNames" | "then" | "unevaluatedItems" | "unevaluatedProperties" => SubresourceIteratorInner::Once(value), // For these keys, if the value is an array, iterate over its items. "allOf" | "anyOf" | "oneOf" => { if let Some(arr) = value.as_array() { SubresourceIteratorInner::Array(arr.iter()) } else { SubresourceIteratorInner::Empty } } // For these keys, if the value is an object, iterate over its values. "$defs" | "definitions" | "dependentSchemas" | "patternProperties" | "properties" => { if let Some(obj) = value.as_object() { SubresourceIteratorInner::Object(obj.values()) } else { SubresourceIteratorInner::Empty } } // For "items": if it's an array, iterate over its items; otherwise, yield the value once. "items" => match value { Value::Array(arr) => SubresourceIteratorInner::Array(arr.iter()), _ => SubresourceIteratorInner::Once(value), }, // For any other key, yield nothing. _ => SubresourceIteratorInner::Empty, } } pub(crate) fn maybe_in_subresource<'r>( segments: &Segments, resolver: &Resolver<'r>, subresource: &InnerResourcePtr, ) -> Result, Error> { const IN_VALUE: &[&str] = &[ "additionalItems", "additionalProperties", "contains", "contentSchema", "else", "if", "not", "propertyNames", "then", "unevaluatedItems", "unevaluatedProperties", ]; const IN_CHILD: &[&str] = &[ "allOf", "anyOf", "oneOf", "$defs", "definitions", "dependentSchemas", "patternProperties", "properties", ]; let mut iter = segments.iter(); while let Some(segment) = iter.next() { if let Segment::Key(key) = segment { if *key == "items" && subresource.contents().is_object() { return resolver.in_subresource_inner(subresource); } if !IN_VALUE.contains(&key.as_ref()) && (!IN_CHILD.contains(&key.as_ref()) || iter.next().is_none()) { return Ok(resolver.clone()); } } } resolver.in_subresource_inner(subresource) } referencing-0.37.3/src/specification/draft4.rs000064400000000000000000000055641046102023000173600ustar 00000000000000use serde_json::Value; use crate::{resource::InnerResourcePtr, Error, Resolver, Segments}; use super::subresources::{self, SubresourceIteratorInner}; pub(crate) fn object_iter<'a>( (key, value): (&'a String, &'a Value), ) -> SubresourceIteratorInner<'a> { match key.as_str() { // For "items": if it’s an array, iterate over it; otherwise, yield one element. "items" => match value { Value::Array(arr) => SubresourceIteratorInner::Array(arr.iter()), _ => SubresourceIteratorInner::Once(value), }, // For "allOf", "anyOf", "oneOf", "prefixItems": if the value is an array, iterate over it. "allOf" | "anyOf" | "oneOf" | "prefixItems" => { if let Some(arr) = value.as_array() { SubresourceIteratorInner::Array(arr.iter()) } else { SubresourceIteratorInner::Empty } } // For "$defs", "definitions", "dependentSchemas", "patternProperties", "properties": // if the value is an object, iterate over its values. "$defs" | "definitions" | "dependentSchemas" | "patternProperties" | "properties" => { if let Some(obj) = value.as_object() { SubresourceIteratorInner::Object(obj.values()) } else { SubresourceIteratorInner::Empty } } // For "dependencies": if the value is an object, iterate over its values filtered to only those that are objects. "dependencies" => { if let Some(obj) = value.as_object() { SubresourceIteratorInner::FilteredObject(obj.values()) } else { SubresourceIteratorInner::Empty } } // For "additionalItems" and "additionalProperties", only if the value is an object. "additionalItems" | "additionalProperties" if value.is_object() => { SubresourceIteratorInner::Once(value) } // For other keys that were originally in the “single element” group: "contains" | "contentSchema" | "else" | "if" | "propertyNames" | "not" | "then" | "unevaluatedItems" | "unevaluatedProperties" => SubresourceIteratorInner::Once(value), _ => SubresourceIteratorInner::Empty, } } pub(crate) fn maybe_in_subresource<'r>( segments: &Segments, resolver: &Resolver<'r>, subresource: &InnerResourcePtr, ) -> Result, Error> { const IN_VALUE: &[&str] = &["additionalItems", "additionalProperties", "not"]; const IN_CHILD: &[&str] = &[ "allOf", "anyOf", "oneOf", "definitions", "patternProperties", "properties", ]; subresources::maybe_in_subresource_with_items_and_dependencies( segments, resolver, subresource, IN_VALUE, IN_CHILD, ) } referencing-0.37.3/src/specification/draft6.rs000064400000000000000000000040541046102023000173530ustar 00000000000000use serde_json::Value; use crate::{resource::InnerResourcePtr, Error, Resolver, Segments}; use super::subresources::{self, SubresourceIteratorInner}; pub(crate) fn object_iter<'a>( (key, value): (&'a String, &'a Value), ) -> SubresourceIteratorInner<'a> { match key.as_str() { "additionalItems" | "additionalProperties" | "contains" | "not" | "propertyNames" => { SubresourceIteratorInner::Once(value) } "allOf" | "anyOf" | "oneOf" => { if let Some(arr) = value.as_array() { SubresourceIteratorInner::Array(arr.iter()) } else { SubresourceIteratorInner::Empty } } "definitions" | "patternProperties" | "properties" => { if let Some(obj) = value.as_object() { SubresourceIteratorInner::Object(obj.values()) } else { SubresourceIteratorInner::Empty } } "items" => match value { Value::Array(arr) => SubresourceIteratorInner::Array(arr.iter()), _ => SubresourceIteratorInner::Once(value), }, "dependencies" => { if let Some(obj) = value.as_object() { SubresourceIteratorInner::FilteredObject(obj.values()) } else { SubresourceIteratorInner::Empty } } _ => SubresourceIteratorInner::Empty, } } pub(crate) fn maybe_in_subresource<'r>( segments: &Segments, resolver: &Resolver<'r>, subresource: &InnerResourcePtr, ) -> Result, Error> { const IN_VALUE: &[&str] = &[ "additionalItems", "additionalProperties", "contains", "not", "propertyNames", ]; const IN_CHILD: &[&str] = &[ "allOf", "anyOf", "oneOf", "definitions", "patternProperties", "properties", ]; subresources::maybe_in_subresource_with_items_and_dependencies( segments, resolver, subresource, IN_VALUE, IN_CHILD, ) } referencing-0.37.3/src/specification/draft7.rs000064400000000000000000000054571046102023000173640ustar 00000000000000use serde_json::Value; use crate::{resource::InnerResourcePtr, Error, Resolver, Segments}; use super::subresources::{self, SubresourceIteratorInner}; pub(crate) fn object_iter<'a>( (key, value): (&'a String, &'a Value), ) -> SubresourceIteratorInner<'a> { match key.as_str() { // For these keys, yield the value once. "additionalItems" | "additionalProperties" | "contains" | "else" | "if" | "not" | "propertyNames" | "then" => SubresourceIteratorInner::Once(value), // For these keys, if the value is an array, iterate over its items. "allOf" | "anyOf" | "oneOf" => { if let Some(arr) = value.as_array() { // In the old draft, flatten() was used. // Here we simply iterate over the array. SubresourceIteratorInner::Array(arr.iter()) } else { SubresourceIteratorInner::Empty } } // For these keys, if the value is an object, iterate over its values. "definitions" | "patternProperties" | "properties" => { if let Some(obj) = value.as_object() { // flat_map in the old draft: iterate over the object's values. SubresourceIteratorInner::Object(obj.values()) } else { SubresourceIteratorInner::Empty } } // For "items": if it's an array, iterate over its items; otherwise, yield the value once. "items" => match value { Value::Array(arr) => SubresourceIteratorInner::Array(arr.iter()), _ => SubresourceIteratorInner::Once(value), }, // For "dependencies": if the value is an object, iterate over its values filtered to only those that are objects. "dependencies" => { if let Some(obj) = value.as_object() { SubresourceIteratorInner::FilteredObject(obj.values()) } else { SubresourceIteratorInner::Empty } } // For any other key, yield nothing. _ => SubresourceIteratorInner::Empty, } } pub(crate) fn maybe_in_subresource<'r>( segments: &Segments, resolver: &Resolver<'r>, subresource: &InnerResourcePtr, ) -> Result, Error> { const IN_VALUE: &[&str] = &[ "additionalItems", "additionalProperties", "contains", "else", "if", "not", "propertyNames", "then", ]; const IN_CHILD: &[&str] = &[ "allOf", "anyOf", "oneOf", "definitions", "patternProperties", "properties", ]; subresources::maybe_in_subresource_with_items_and_dependencies( segments, resolver, subresource, IN_VALUE, IN_CHILD, ) } referencing-0.37.3/src/specification/ids.rs000064400000000000000000000015561046102023000167500ustar 00000000000000//! Extracting schema ID. use serde_json::Value; pub(crate) fn dollar_id(contents: &Value) -> Option<&str> { contents .as_object() .and_then(|obj| obj.get("$id")) .and_then(|id| id.as_str()) } pub(crate) fn legacy_dollar_id(contents: &Value) -> Option<&str> { let object = contents.as_object()?; if object.contains_key("$ref") { return None; } if let Some(id) = contents.get("$id").and_then(|id| id.as_str()) { if !id.starts_with('#') { return Some(id); } } None } pub(crate) fn legacy_id(contents: &Value) -> Option<&str> { let object = contents.as_object()?; if object.contains_key("$ref") { return None; } if let Some(id) = object.get("id").and_then(|id| id.as_str()) { if !id.starts_with('#') { return Some(id); } } None } referencing-0.37.3/src/specification/mod.rs000064400000000000000000000227461046102023000167540ustar 00000000000000use serde_json::Value; use subresources::SubresourceIterator; mod draft201909; mod draft4; mod draft6; mod draft7; mod ids; mod subresources; use crate::{ anchors, resource::InnerResourcePtr, vocabularies::{VocabularySet, DRAFT_2019_09_VOCABULARIES, DRAFT_2020_12_VOCABULARIES}, Anchor, Error, Resolver, Resource, ResourceRef, Segments, }; /// JSON Schema specification versions. #[non_exhaustive] #[derive(Debug, Default, PartialEq, Copy, Clone, Hash, Eq, PartialOrd, Ord)] pub enum Draft { /// JSON Schema Draft 4 Draft4, /// JSON Schema Draft 6 Draft6, /// JSON Schema Draft 7 Draft7, /// JSON Schema Draft 2019-09 Draft201909, /// JSON Schema Draft 2020-12 #[default] Draft202012, /// Internal use only: Represents custom/unrecognized meta-schemas. /// Do not use directly. Custom meta-schemas are resolved automatically /// when registered in the Registry. #[doc(hidden)] Unknown, } impl Draft { #[must_use] pub fn create_resource(self, contents: Value) -> Resource { Resource::new(contents, self) } #[must_use] pub fn create_resource_ref(self, contents: &Value) -> ResourceRef<'_> { ResourceRef::new(contents, self) } /// Detect what specification could be applied to the given contents. /// /// Returns `Draft::Unknown` for custom/unknown `$schema` values. /// Validation of custom meta-schemas happens during registry building. #[must_use] pub fn detect(self, contents: &Value) -> Draft { if let Some(schema) = contents .as_object() .and_then(|contents| contents.get("$schema")) .and_then(|schema| schema.as_str()) { match schema.trim_end_matches('#') { // Accept both HTTPS and HTTP for all known drafts "https://json-schema.org/draft/2020-12/schema" | "http://json-schema.org/draft/2020-12/schema" => Draft::Draft202012, "https://json-schema.org/draft/2019-09/schema" | "http://json-schema.org/draft/2019-09/schema" => Draft::Draft201909, "https://json-schema.org/draft-07/schema" | "http://json-schema.org/draft-07/schema" => Draft::Draft7, "https://json-schema.org/draft-06/schema" | "http://json-schema.org/draft-06/schema" => Draft::Draft6, "https://json-schema.org/draft-04/schema" | "http://json-schema.org/draft-04/schema" => Draft::Draft4, // Custom/unknown meta-schemas return Unknown // Validation of custom meta-schemas happens during registry building _ => Draft::Unknown, } } else { self } } pub(crate) fn id_of(self, contents: &Value) -> Option<&str> { match self { Draft::Draft4 => ids::legacy_id(contents), Draft::Draft6 | Draft::Draft7 => ids::legacy_dollar_id(contents), Draft::Draft201909 | Draft::Draft202012 | Draft::Unknown => ids::dollar_id(contents), } } pub fn subresources_of(self, contents: &Value) -> impl Iterator { match contents.as_object() { Some(schema) => { let object_iter = match self { Draft::Draft4 => draft4::object_iter, Draft::Draft6 => draft6::object_iter, Draft::Draft7 => draft7::object_iter, Draft::Draft201909 => draft201909::object_iter, Draft::Draft202012 | Draft::Unknown => subresources::object_iter, }; SubresourceIterator::Object(schema.iter().flat_map(object_iter)) } None => SubresourceIterator::Empty, } } pub(crate) fn anchors(self, contents: &Value) -> impl Iterator { match self { Draft::Draft4 => anchors::legacy_anchor_in_id(self, contents), Draft::Draft6 | Draft::Draft7 => anchors::legacy_anchor_in_dollar_id(self, contents), Draft::Draft201909 => anchors::anchor_2019(self, contents), Draft::Draft202012 | Draft::Unknown => anchors::anchor(self, contents), } } pub(crate) fn maybe_in_subresource<'r>( self, segments: &Segments, resolver: &Resolver<'r>, subresource: &InnerResourcePtr, ) -> Result, Error> { match self { Draft::Draft4 => draft4::maybe_in_subresource(segments, resolver, subresource), Draft::Draft6 => draft6::maybe_in_subresource(segments, resolver, subresource), Draft::Draft7 => draft7::maybe_in_subresource(segments, resolver, subresource), Draft::Draft201909 => { draft201909::maybe_in_subresource(segments, resolver, subresource) } Draft::Draft202012 | Draft::Unknown => { subresources::maybe_in_subresource(segments, resolver, subresource) } } } /// Identifies known JSON schema keywords per draft. #[must_use] pub fn is_known_keyword(&self, keyword: &str) -> bool { match keyword { "$ref" | "$schema" | "additionalItems" | "additionalProperties" | "allOf" | "anyOf" | "dependencies" | "enum" | "exclusiveMaximum" | "exclusiveMinimum" | "format" | "items" | "maxItems" | "maxLength" | "maxProperties" | "maximum" | "minItems" | "minLength" | "minProperties" | "minimum" | "multipleOf" | "not" | "oneOf" | "pattern" | "patternProperties" | "properties" | "required" | "type" | "uniqueItems" => true, "id" if *self == Draft::Draft4 => true, "$id" | "const" | "contains" | "propertyNames" if *self >= Draft::Draft6 || *self == Draft::Unknown => { true } "contentEncoding" | "contentMediaType" if matches!(self, Draft::Draft6 | Draft::Draft7) => { true } "else" | "if" | "then" if *self >= Draft::Draft7 || *self == Draft::Unknown => true, "$anchor" | "$defs" | "$recursiveAnchor" | "$recursiveRef" | "dependentRequired" | "dependentSchemas" | "maxContains" | "minContains" | "prefixItems" | "unevaluatedItems" | "unevaluatedProperties" if *self >= Draft::Draft201909 || *self == Draft::Unknown => { true } "$dynamicAnchor" | "$dynamicRef" if *self == Draft::Draft202012 || *self == Draft::Unknown => { true } _ => false, } } pub(crate) fn default_vocabularies(self) -> VocabularySet { match self { Draft::Draft4 | Draft::Draft6 | Draft::Draft7 => VocabularySet::new(), Draft::Draft201909 => VocabularySet::from_known(DRAFT_2019_09_VOCABULARIES), Draft::Draft202012 | Draft::Unknown => { VocabularySet::from_known(DRAFT_2020_12_VOCABULARIES) } } } } #[cfg(test)] mod tests { use crate::Draft; use serde_json::json; use test_case::test_case; #[test_case(&json!({"$schema": "https://json-schema.org/draft/2020-12/schema"}), Draft::Draft202012; "detect Draft 2020-12")] #[test_case(&json!({"$schema": "https://json-schema.org/draft/2020-12/schema#"}), Draft::Draft202012; "detect Draft 2020-12 with fragment")] #[test_case(&json!({"$schema": "https://json-schema.org/draft/2019-09/schema"}), Draft::Draft201909; "detect Draft 2019-09")] #[test_case(&json!({"$schema": "http://json-schema.org/draft-07/schema"}), Draft::Draft7; "detect Draft 7")] #[test_case(&json!({"$schema": "https://json-schema.org/draft-07/schema"}), Draft::Draft7; "detect Draft 7 https")] #[test_case(&json!({"$schema": "http://json-schema.org/draft-06/schema"}), Draft::Draft6; "detect Draft 6")] #[test_case(&json!({"$schema": "https://json-schema.org/draft-06/schema"}), Draft::Draft6; "detect Draft 6 https")] #[test_case(&json!({"$schema": "http://json-schema.org/draft-04/schema"}), Draft::Draft4; "detect Draft 4")] #[test_case(&json!({"$schema": "https://json-schema.org/draft-04/schema"}), Draft::Draft4; "detect Draft 4 https")] #[test_case(&json!({}), Draft::Draft7; "default to Draft 7 when no $schema")] fn test_detect(contents: &serde_json::Value, expected: Draft) { let result = Draft::Draft7.detect(contents); assert_eq!(result, expected); } #[test] fn test_unknown_specification() { let draft = Draft::Draft7.detect(&json!({"$schema": "invalid"})); assert_eq!(draft, Draft::Unknown); } #[test_case(Draft::Draft4; "Draft 4 stays Draft 4")] #[test_case(Draft::Draft6; "Draft 6 stays Draft 6")] #[test_case(Draft::Draft7; "Draft 7 stays Draft 7")] #[test_case(Draft::Draft201909; "Draft 2019-09 stays Draft 2019-09")] #[test_case(Draft::Draft202012; "Draft 2020-12 stays Draft 2020-12")] fn test_detect_no_change(draft: Draft) { let contents = json!({}); let result = draft.detect(&contents); assert_eq!(result, draft); } } referencing-0.37.3/src/specification/subresources.rs000064400000000000000000000266151046102023000207200ustar 00000000000000use core::slice; use std::iter::FlatMap; use serde_json::Value; use crate::{resource::InnerResourcePtr, segments::Segment, Error, Resolver, Segments}; type ObjectIter<'a> = FlatMap< serde_json::map::Iter<'a>, SubresourceIteratorInner<'a>, fn((&'a std::string::String, &'a Value)) -> SubresourceIteratorInner<'a>, >; /// A simple iterator that either wraps an iterator producing &Value or is empty. /// NOTE: It is noticeably slower if `Object` is boxed. #[allow(clippy::large_enum_variant)] pub(crate) enum SubresourceIterator<'a> { Object(ObjectIter<'a>), Empty, } impl<'a> Iterator for SubresourceIterator<'a> { type Item = &'a Value; fn next(&mut self) -> Option { match self { SubresourceIterator::Object(iter) => iter.next(), SubresourceIterator::Empty => None, } } } pub(crate) enum SubresourceIteratorInner<'a> { Once(&'a Value), Array(slice::Iter<'a, Value>), Object(serde_json::map::Values<'a>), FilteredObject(serde_json::map::Values<'a>), Empty, } impl<'a> Iterator for SubresourceIteratorInner<'a> { type Item = &'a Value; fn next(&mut self) -> Option { match self { SubresourceIteratorInner::Once(_) => { let SubresourceIteratorInner::Once(value) = std::mem::replace(self, SubresourceIteratorInner::Empty) else { unreachable!() }; Some(value) } SubresourceIteratorInner::Array(iter) => iter.next(), SubresourceIteratorInner::Object(iter) => iter.next(), SubresourceIteratorInner::FilteredObject(iter) => { for next in iter.by_ref() { if !next.is_object() { continue; } return Some(next); } None } SubresourceIteratorInner::Empty => None, } } } pub(crate) fn object_iter<'a>( (key, value): (&'a String, &'a Value), ) -> SubresourceIteratorInner<'a> { match key.as_str() { "additionalProperties" | "contains" | "contentSchema" | "else" | "if" | "items" | "not" | "propertyNames" | "then" | "unevaluatedItems" | "unevaluatedProperties" => SubresourceIteratorInner::Once(value), "allOf" | "anyOf" | "oneOf" | "prefixItems" => { if let Some(arr) = value.as_array() { SubresourceIteratorInner::Array(arr.iter()) } else { SubresourceIteratorInner::Empty } } "$defs" | "definitions" | "dependentSchemas" | "patternProperties" | "properties" => { if let Some(obj) = value.as_object() { SubresourceIteratorInner::Object(obj.values()) } else { SubresourceIteratorInner::Empty } } _ => SubresourceIteratorInner::Empty, } } pub(crate) fn maybe_in_subresource<'r>( segments: &Segments, resolver: &Resolver<'r>, subresource: &InnerResourcePtr, ) -> Result, Error> { const IN_VALUE: &[&str] = &[ "additionalProperties", "contains", "contentSchema", "else", "if", "items", "not", "propertyNames", "then", "unevaluatedItems", "unevaluatedProperties", ]; const IN_CHILD: &[&str] = &[ "allOf", "anyOf", "oneOf", "prefixItems", "$defs", "definitions", "dependentSchemas", "patternProperties", "properties", ]; let mut iter = segments.iter(); while let Some(segment) = iter.next() { if let Segment::Key(key) = segment { if !IN_VALUE.contains(&key.as_ref()) && (!IN_CHILD.contains(&key.as_ref()) || iter.next().is_none()) { return Ok(resolver.clone()); } } } resolver.in_subresource_inner(subresource) } #[inline] pub(crate) fn maybe_in_subresource_with_items_and_dependencies<'r>( segments: &Segments, resolver: &Resolver<'r>, subresource: &InnerResourcePtr, in_value: &[&str], in_child: &[&str], ) -> Result, Error> { let mut iter = segments.iter(); while let Some(segment) = iter.next() { if let Segment::Key(key) = segment { if (*key == "items" || *key == "dependencies") && subresource.contents().is_object() { return resolver.in_subresource_inner(subresource); } if !in_value.contains(&key.as_ref()) && (!in_child.contains(&key.as_ref()) || iter.next().is_none()) { return Ok(resolver.clone()); } } } resolver.in_subresource_inner(subresource) } #[cfg(test)] mod tests { use crate::Draft; use super::{object_iter, SubresourceIterator}; use ahash::HashSet; use serde_json::{json, Value}; use test_case::test_case; pub(crate) fn subresources_of(contents: &Value) -> SubresourceIterator<'_> { match contents.as_object() { Some(schema) => SubresourceIterator::Object(schema.iter().flat_map(object_iter)), None => SubresourceIterator::Empty, } } #[test_case(&json!(true), &[] ; "boolean schema")] #[test_case(&json!(false), &[] ; "boolean schema false")] #[test_case(&json!({}), &[] ; "empty object")] #[test_case(&json!({"type": "string"}), &[] ; "no subresources")] #[test_case( &json!({"additionalProperties": {"type": "string"}}), &[json!({"type": "string"})] ; "in_value single" )] #[test_case( &json!({"if": {"type": "string"}, "then": {"minimum": 0}}), &[json!({"type": "string"}), json!({"minimum": 0})] ; "in_value multiple" )] #[test_case( &json!({"properties": {"foo": {"type": "string"}, "bar": {"type": "number"}}}), &[json!({"type": "string"}), json!({"type": "number"})] ; "in_subvalues" )] #[test_case( &json!({"allOf": [{"type": "string"}, {"minLength": 1}]}), &[json!({"type": "string"}), json!({"minLength": 1})] ; "in_subarray" )] #[test_case( &json!({ "type": "object", "properties": { "foo": {"type": "string"}, "bar": {"type": "number"} }, "additionalProperties": {"type": "boolean"}, "allOf": [ {"required": ["foo"]}, {"required": ["bar"]} ] }), &[ json!({"type": "string"}), json!({"type": "number"}), json!({"type": "boolean"}), json!({"required": ["foo"]}), json!({"required": ["bar"]}) ] ; "complex schema" )] #[test_case( &json!({ "$defs": { "positiveInteger": { "type": "integer", "exclusiveMinimum": 0 } }, "properties": { "count": { "$ref": "#/$defs/positiveInteger" } } }), &[ json!({"type": "integer", "exclusiveMinimum": 0}), json!({"$ref": "#/$defs/positiveInteger"}) ] ; "with $defs" )] fn test_subresources_of(schema: &serde_json::Value, expected: &[serde_json::Value]) { let subresources: HashSet<&serde_json::Value> = subresources_of(schema).collect(); let expected_set: HashSet<&serde_json::Value> = expected.iter().collect(); assert_eq!( subresources.len(), expected.len(), "Number of subresources doesn't match" ); assert_eq!( subresources, expected_set, "Subresources don't match expected values" ); } #[test] fn test_all_keywords() { let schema = json!({ "additionalProperties": {"type": "string"}, "contains": {"minimum": 0}, "contentSchema": {"format": "email"}, "else": {"maximum": 100}, "if": {"type": "number"}, "items": {"type": "array"}, "not": {"type": "null"}, "propertyNames": {"minLength": 1}, "then": {"multipleOf": 2}, "unevaluatedItems": {"type": "boolean"}, "unevaluatedProperties": {"type": "integer"}, "allOf": [{"type": "object"}, {"required": ["foo"]}], "anyOf": [{"minimum": 0}, {"maximum": 100}], "oneOf": [{"type": "string"}, {"type": "number"}], "prefixItems": [{"type": "string"}, {"type": "number"}], "$defs": { "positiveInteger": {"type": "integer", "minimum": 1} }, "definitions": { "negativeInteger": {"type": "integer", "maximum": -1} }, "dependentSchemas": { "foo": {"required": ["bar"]} }, "patternProperties": { "^S_": {"type": "string"}, "^I_": {"type": "integer"} }, "properties": { "prop1": {"type": "string"}, "prop2": {"type": "number"} } }); let subresources: Vec<&serde_json::Value> = subresources_of(&schema).collect(); assert_eq!(subresources.len(), 26); assert!(subresources.contains(&&json!({"type": "string"}))); assert!(subresources.contains(&&json!({"minimum": 0}))); assert!(subresources.contains(&&json!({"format": "email"}))); assert!(subresources.contains(&&json!({"maximum": 100}))); assert!(subresources.contains(&&json!({"type": "number"}))); assert!(subresources.contains(&&json!({"type": "array"}))); assert!(subresources.contains(&&json!({"type": "null"}))); assert!(subresources.contains(&&json!({"minLength": 1}))); assert!(subresources.contains(&&json!({"multipleOf": 2}))); assert!(subresources.contains(&&json!({"type": "boolean"}))); assert!(subresources.contains(&&json!({"type": "integer"}))); assert!(subresources.contains(&&json!({"type": "object"}))); assert!(subresources.contains(&&json!({"required": ["foo"]}))); assert!(subresources.contains(&&json!({"minimum": 0}))); assert!(subresources.contains(&&json!({"maximum": 100}))); assert!(subresources.contains(&&json!({"type": "string"}))); assert!(subresources.contains(&&json!({"type": "number"}))); assert!(subresources.contains(&&json!({"type": "integer", "minimum": 1}))); assert!(subresources.contains(&&json!({"type": "integer", "maximum": -1}))); assert!(subresources.contains(&&json!({"required": ["bar"]}))); assert!(subresources.contains(&&json!({"type": "string"}))); assert!(subresources.contains(&&json!({"type": "integer"}))); } #[test_case(Draft::Draft4)] #[test_case(Draft::Draft6)] #[test_case(Draft::Draft7)] #[test_case(Draft::Draft201909)] #[test_case(Draft::Draft202012)] fn test_subresources_of_bool_schema(draft: Draft) { let bool_schema = json!(true); assert!( draft .subresources_of(&bool_schema) .collect::>() .is_empty(), "Draft {draft:?} should return empty subresources for boolean schema", ); } } referencing-0.37.3/src/uri.rs000064400000000000000000000050441046102023000141440ustar 00000000000000//! URI handling utilities for JSON Schema references. use fluent_uri::{ pct_enc::{encoder::Fragment, EStr, Encoder}, Uri, UriRef, }; use std::sync::LazyLock; use crate::Error; pub use fluent_uri::pct_enc::encoder::Path; /// Resolves the URI reference against the given base URI and returns the target URI. /// /// # Errors /// /// Returns an error if base has not schema or there is a fragment. pub fn resolve_against(base: &Uri<&str>, uri: &str) -> Result, Error> { if uri.starts_with('#') && base.as_str().ends_with(uri) { return Ok(base.to_owned()); } Ok(UriRef::parse(uri) .map_err(|error| Error::uri_reference_parsing_error(uri, error))? .resolve_against(base) .map_err(|error| Error::uri_resolving_error(uri, *base, error))? .normalize()) } /// Parses a URI reference from a string into a [`crate::Uri`]. /// /// # Errors /// /// Returns an error if the input string does not conform to URI-reference from RFC 3986. pub fn from_str(uri: &str) -> Result, Error> { let uriref = UriRef::parse(uri) .map_err(|error| Error::uri_reference_parsing_error(uri, error))? .normalize(); if uriref.has_scheme() { Ok(Uri::try_from(uriref.as_str()) .map_err(|error| Error::uri_parsing_error(uriref.as_str(), error))? .into()) } else { Ok(uriref .resolve_against(&DEFAULT_ROOT_URI.borrow()) .map_err(|error| Error::uri_resolving_error(uri, DEFAULT_ROOT_URI.borrow(), error))?) } } pub(crate) static DEFAULT_ROOT_URI: LazyLock> = LazyLock::new(|| Uri::parse("json-schema:///".to_string()).expect("Invalid URI")); pub type EncodedString = EStr; // Adapted from `https://github.com/yescallop/fluent-uri-rs/blob/main/src/encoding/table.rs#L153` pub fn encode_to(input: &str, buffer: &mut String) { const HEX_TABLE: [u8; 512] = { const HEX_DIGITS: &[u8; 16] = b"0123456789ABCDEF"; let mut i = 0; let mut table = [0; 512]; while i < 256 { table[i * 2] = HEX_DIGITS[i >> 4]; table[i * 2 + 1] = HEX_DIGITS[i & 0b1111]; i += 1; } table }; for ch in input.chars() { if Path::TABLE.allows(ch) { buffer.push(ch); } else { for x in ch.encode_utf8(&mut [0; 4]).bytes() { buffer.push('%'); buffer.push(HEX_TABLE[x as usize * 2] as char); buffer.push(HEX_TABLE[x as usize * 2 + 1] as char); } } } } referencing-0.37.3/src/vocabularies.rs000064400000000000000000000257301046102023000160300ustar 00000000000000use core::fmt; use std::str::FromStr; use crate::{uri, Error}; use ahash::AHashSet; use fluent_uri::Uri; use serde_json::Value; /// A JSON Schema vocabulary identifier, representing standard vocabularies (Core, Applicator, etc.) /// or custom ones via URI. #[derive(Debug, PartialEq, Eq, Clone)] pub enum Vocabulary { Core, Applicator, Unevaluated, Validation, Metadata, Format, FormatAnnotation, Content, Custom(Uri), } impl FromStr for Vocabulary { type Err = Error; fn from_str(s: &str) -> Result { match s { "https://json-schema.org/draft/2020-12/vocab/core" | "https://json-schema.org/draft/2019-09/vocab/core" => Ok(Vocabulary::Core), "https://json-schema.org/draft/2020-12/vocab/applicator" | "https://json-schema.org/draft/2019-09/vocab/applicator" => { Ok(Vocabulary::Applicator) } "https://json-schema.org/draft/2020-12/vocab/unevaluated" => { Ok(Vocabulary::Unevaluated) } "https://json-schema.org/draft/2020-12/vocab/validation" | "https://json-schema.org/draft/2019-09/vocab/validation" => { Ok(Vocabulary::Validation) } "https://json-schema.org/draft/2020-12/vocab/meta-data" | "https://json-schema.org/draft/2019-09/vocab/meta-data" => Ok(Vocabulary::Metadata), "https://json-schema.org/draft/2020-12/vocab/format" | "https://json-schema.org/draft/2019-09/vocab/format" => Ok(Vocabulary::Format), "https://json-schema.org/draft/2020-12/vocab/format-annotation" => { Ok(Vocabulary::FormatAnnotation) } "https://json-schema.org/draft/2020-12/vocab/content" | "https://json-schema.org/draft/2019-09/vocab/content" => Ok(Vocabulary::Content), _ => Ok(Vocabulary::Custom(uri::from_str(s)?)), } } } /// A set of enabled JSON Schema vocabularies. #[derive(Clone, Default, PartialEq, Eq)] pub struct VocabularySet { known: u8, custom: AHashSet>, } impl fmt::Debug for VocabularySet { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut debug_list = f.debug_list(); // Add known vocabularies if self.known & (1 << 0) != 0 { debug_list.entry(&"core"); } if self.known & (1 << 1) != 0 { debug_list.entry(&"applicator"); } if self.known & (1 << 2) != 0 { debug_list.entry(&"unevaluated"); } if self.known & (1 << 3) != 0 { debug_list.entry(&"validation"); } if self.known & (1 << 4) != 0 { debug_list.entry(&"meta-data"); } if self.known & (1 << 5) != 0 { debug_list.entry(&"format"); } if self.known & (1 << 6) != 0 { debug_list.entry(&"format-annotation"); } if self.known & (1 << 7) != 0 { debug_list.entry(&"content"); } // Add custom vocabularies if !self.custom.is_empty() { let mut custom: Vec<_> = self.custom.iter().map(Uri::as_str).collect(); custom.sort_unstable(); for uri in custom { debug_list.entry(&uri); } } debug_list.finish() } } impl VocabularySet { pub(crate) fn new() -> Self { Self::default() } pub(crate) fn from_known(known: u8) -> Self { Self { known, custom: AHashSet::new(), } } pub(crate) fn add(&mut self, vocabulary: Vocabulary) { match vocabulary { Vocabulary::Core => self.known |= 1 << 0, Vocabulary::Applicator => self.known |= 1 << 1, Vocabulary::Unevaluated => self.known |= 1 << 2, Vocabulary::Validation => self.known |= 1 << 3, Vocabulary::Metadata => self.known |= 1 << 4, Vocabulary::Format => self.known |= 1 << 5, Vocabulary::FormatAnnotation => self.known |= 1 << 6, Vocabulary::Content => self.known |= 1 << 7, Vocabulary::Custom(uri) => { self.custom.insert(uri); } } } #[must_use] pub fn contains(&self, vocabulary: &Vocabulary) -> bool { match vocabulary { Vocabulary::Core => self.known & (1 << 0) != 0, Vocabulary::Applicator => self.known & (1 << 1) != 0, Vocabulary::Unevaluated => self.known & (1 << 2) != 0, Vocabulary::Validation => self.known & (1 << 3) != 0, Vocabulary::Metadata => self.known & (1 << 4) != 0, Vocabulary::Format => self.known & (1 << 5) != 0, Vocabulary::FormatAnnotation => self.known & (1 << 6) != 0, Vocabulary::Content => self.known & (1 << 7) != 0, Vocabulary::Custom(uri) => self.custom.contains(uri), } } } pub(crate) const DRAFT_2020_12_VOCABULARIES: u8 = 0b1111_1111; pub(crate) const DRAFT_2019_09_VOCABULARIES: u8 = 0b1001_1011; pub(crate) fn find(document: &Value) -> Result, Error> { if let Some(schema) = document.get("$id").and_then(|s| s.as_str()) { match schema { "https://json-schema.org/schema" | "https://json-schema.org/draft/2020-12/schema" => { // All known vocabularies Ok(Some(VocabularySet::from_known(DRAFT_2020_12_VOCABULARIES))) } "https://json-schema.org/draft/2019-09/schema" => { // Core, Applicator, Validation, Metadata, Content Ok(Some(VocabularySet::from_known(DRAFT_2019_09_VOCABULARIES))) } "https://json-schema.org/draft-07/schema" | "https://json-schema.org/draft-06/schema" | "https://json-schema.org/draft-04/schema" => Ok(None), _ => { // For unknown schemas, parse the $vocabulary object if let Some(vocab_obj) = document.get("$vocabulary").and_then(|v| v.as_object()) { let mut set = VocabularySet::new(); for (uri, enabled) in vocab_obj { if enabled.as_bool().unwrap_or(false) { set.add(Vocabulary::from_str(uri)?); } } Ok(Some(set)) } else { Ok(None) } } } } else { Ok(None) } } #[cfg(test)] mod tests { use super::*; use test_case::test_case; #[test_case(&Vocabulary::Core, 0b0000_0001, true)] #[test_case(&Vocabulary::Applicator, 0b0000_0010, true)] #[test_case(&Vocabulary::Unevaluated, 0b0000_0100, true)] #[test_case(&Vocabulary::Validation, 0b0000_1000, true)] #[test_case(&Vocabulary::Metadata, 0b0001_0000, true)] #[test_case(&Vocabulary::Format, 0b0010_0000, true)] #[test_case(&Vocabulary::FormatAnnotation, 0b0100_0000, true)] #[test_case(&Vocabulary::Content, 0b1000_0000, true)] #[test_case(&Vocabulary::Core, 0b1111_1110, false)] #[test_case(&Vocabulary::Applicator, 0b1111_1101, false)] #[test_case(&Vocabulary::Unevaluated, 0b111_11011, false)] #[test_case(&Vocabulary::Validation, 0b1111_0111, false)] #[test_case(&Vocabulary::Metadata, 0b1110_1111, false)] #[test_case(&Vocabulary::Format, 0b1101_1111, false)] #[test_case(&Vocabulary::FormatAnnotation, 0b1011_1111, false)] #[test_case(&Vocabulary::Content, 0b0111_1111, false)] fn test_vocabulary_set(vocabulary: &Vocabulary, known: u8, expected: bool) { let set = VocabularySet::from_known(known); assert_eq!(set.contains(vocabulary), expected); } #[test] fn test_vocabulary_set_add_and_contains() { let mut set = VocabularySet::new(); set.add(Vocabulary::Core); set.add(Vocabulary::Applicator); set.add(Vocabulary::Validation); set.add(Vocabulary::Metadata); set.add(Vocabulary::Content); assert!(set.contains(&Vocabulary::Core)); assert!(set.contains(&Vocabulary::Applicator)); assert!(set.contains(&Vocabulary::Validation)); assert!(set.contains(&Vocabulary::Metadata)); assert!(set.contains(&Vocabulary::Content)); assert!(!set.contains(&Vocabulary::Unevaluated)); assert!(!set.contains(&Vocabulary::Format)); assert!(!set.contains(&Vocabulary::FormatAnnotation)); set.add(Vocabulary::Unevaluated); set.add(Vocabulary::Format); set.add(Vocabulary::FormatAnnotation); assert!(set.contains(&Vocabulary::Unevaluated)); assert!(set.contains(&Vocabulary::Format)); assert!(set.contains(&Vocabulary::FormatAnnotation)); } #[test] fn test_vocabulary_set_debug() { let mut set = VocabularySet::from_known(0b0001_1111); // Core, Applicator, Unevaluated, Validation, Metadata set.add(Vocabulary::Custom( uri::from_str("https://example.com/custom-vocab").unwrap(), )); assert_eq!( format!("{set:?}"), "[\"core\", \"applicator\", \"unevaluated\", \"validation\", \"meta-data\", \"https://example.com/custom-vocab\"]" ); } #[test] fn test_custom_vocabulary() { let custom_uri = uri::from_str("https://example.com/custom-vocab").expect("Invalid URI"); let mut set = VocabularySet::new(); set.add(Vocabulary::Custom(custom_uri.clone())); assert!(set.contains(&Vocabulary::Custom(custom_uri))); assert!(!set.contains(&Vocabulary::Custom( uri::from_str("https://example.com/other-vocab").expect("Invalid URI") ))); } #[test_case( &serde_json::json!({"$id": "https://json-schema.org/draft/2020-12/schema"}), "Some([\"core\", \"applicator\", \"unevaluated\", \"validation\", \"meta-data\", \"format\", \"format-annotation\", \"content\"])" ; "2020-12 draft" )] #[test_case( &serde_json::json!({"$id": "https://json-schema.org/draft/2019-09/schema"}), "Some([\"core\", \"applicator\", \"validation\", \"meta-data\", \"content\"])" ; "2019-09 draft" )] #[test_case( &serde_json::json!({"$id": "https://json-schema.org/draft-07/schema"}), "None" ; "draft-07" )] #[test_case( &serde_json::json!({ "$id": "https://example.com/custom-schema", "$vocabulary": { "https://example.com/custom-vocab1": true, "https://example.com/custom-vocab2": true, "https://example.com/custom-vocab3": false, } }), "Some([\"https://example.com/custom-vocab1\", \"https://example.com/custom-vocab2\"])" ; "custom schema" )] #[test_case( &serde_json::json!({}), "None" ; "no $id keyword" )] fn test_find(schema: &serde_json::Value, expected: &str) { let set = find(schema).expect("Invalid vocabulary"); assert_eq!(format!("{set:?}"), expected); } } referencing-0.37.3/tests/suite.rs000064400000000000000000000065221046102023000150530ustar 00000000000000use referencing::{Draft, Registry}; use referencing_testsuite::{suite, Test}; #[suite( path = "crates/jsonschema-referencing/tests/suite", drafts = [ "json-schema-draft-04", "json-schema-draft-06", "json-schema-draft-07", "json-schema-draft-2019-09", "json-schema-draft-2020-12", ], xfail = [ // `fluent-uri` does not normalize 80 port "json-schema-draft-04::rfc3986_normalization_on_insertion::test_5", "json-schema-draft-04::rfc3986_normalization_on_insertion::test_11", "json-schema-draft-04::rfc3986_normalization_on_retrieval::test_5", "json-schema-draft-04::rfc3986_normalization_on_retrieval::test_11", "json-schema-draft-06::rfc3986_normalization_on_insertion::test_5", "json-schema-draft-06::rfc3986_normalization_on_insertion::test_11", "json-schema-draft-06::rfc3986_normalization_on_retrieval::test_5", "json-schema-draft-06::rfc3986_normalization_on_retrieval::test_11", "json-schema-draft-07::rfc3986_normalization_on_insertion::test_5", "json-schema-draft-07::rfc3986_normalization_on_insertion::test_11", "json-schema-draft-07::rfc3986_normalization_on_retrieval::test_5", "json-schema-draft-07::rfc3986_normalization_on_retrieval::test_11", "json-schema-draft-2019-09::rfc3986_normalization_on_insertion::test_5", "json-schema-draft-2019-09::rfc3986_normalization_on_insertion::test_11", "json-schema-draft-2019-09::rfc3986_normalization_on_retrieval::test_5", "json-schema-draft-2019-09::rfc3986_normalization_on_retrieval::test_11", "json-schema-draft-2020-12::rfc3986_normalization_on_insertion::test_5", "json-schema-draft-2020-12::rfc3986_normalization_on_insertion::test_11", "json-schema-draft-2020-12::rfc3986_normalization_on_retrieval::test_5", "json-schema-draft-2020-12::rfc3986_normalization_on_retrieval::test_11", ] )] fn test_suite(draft: &'static str, test: Test) { let draft = match draft { "json-schema-draft-04" => Draft::Draft4, "json-schema-draft-06" => Draft::Draft6, "json-schema-draft-07" => Draft::Draft7, "json-schema-draft-2019-09" => Draft::Draft201909, "json-schema-draft-2020-12" => Draft::Draft202012, _ => panic!("Unknown draft"), }; let registry = Registry::try_from_resources( test.registry .into_iter() .map(|(uri, content)| (uri, draft.create_resource(content))), ) .expect("Invalid registry"); let resolver = registry .try_resolver(test.base_uri.unwrap_or_default()) .expect("Invalid base URI"); if test.error.is_some() { assert!(resolver.lookup(test.reference).is_err()); } else { let mut resolved = resolver.lookup(test.reference).expect("Invalid reference"); assert_eq!( resolved.contents(), &test.target.expect("Should be present") ); let mut then = test.then; while let Some(then_) = then { resolved = resolved .resolver() .lookup(then_.reference) .expect("Invalid reference"); assert_eq!( resolved.contents(), &then_.target.expect("Should be present") ); then = then_.then; } } }