use alloc::collections::BTreeMap;
use alloc::format;
use alloc::string::String;
use foundation_errstacks::{ErrorTrace, IntoErrorTrace};
use serde_json::Value;
use crate::resolver_trait::{JsonResolver, ResolveError};
fn build_builtin_map() -> BTreeMap<String, Value> {
let mut map = BTreeMap::new();
let pairs: &[(&str, &str)] = &[
(
"http://json-schema.org/draft-04/schema#",
include_str!("../artefacts/draft-04-schema.json"),
),
(
"http://json-schema.org/draft-04/hyper-schema#",
include_str!("../artefacts/draft-04-hyper-schema.json"),
),
(
"http://json-schema.org/draft-06/schema#",
include_str!("../artefacts/draft-06-schema.json"),
),
(
"http://json-schema.org/draft-06/hyper-schema#",
include_str!("../artefacts/draft-06-hyper-schema.json"),
),
(
"http://json-schema.org/draft-07/schema#",
include_str!("../artefacts/draft-07-schema.json"),
),
(
"http://json-schema.org/draft-07/hyper-schema#",
include_str!("../artefacts/draft-07-hyper-schema.json"),
),
(
"https://json-schema.org/draft/2019-09/schema",
include_str!("../artefacts/draft-2019-09-schema.json"),
),
(
"https://json-schema.org/draft/2019-09/meta/core",
include_str!("../artefacts/draft-2019-09-meta-core.json"),
),
(
"https://json-schema.org/draft/2019-09/meta/applicator",
include_str!("../artefacts/draft-2019-09-meta-applicator.json"),
),
(
"https://json-schema.org/draft/2019-09/meta/validation",
include_str!("../artefacts/draft-2019-09-meta-validation.json"),
),
(
"https://json-schema.org/draft/2019-09/meta/meta-data",
include_str!("../artefacts/draft-2019-09-meta-meta-data.json"),
),
(
"https://json-schema.org/draft/2019-09/meta/format",
include_str!("../artefacts/draft-2019-09-meta-format.json"),
),
(
"https://json-schema.org/draft/2019-09/meta/content",
include_str!("../artefacts/draft-2019-09-meta-content.json"),
),
(
"https://json-schema.org/draft/2020-12/schema",
include_str!("../artefacts/draft-2020-12-schema.json"),
),
(
"https://json-schema.org/draft/2020-12/meta/core",
include_str!("../artefacts/draft-2020-12-meta-core.json"),
),
(
"https://json-schema.org/draft/2020-12/meta/applicator",
include_str!("../artefacts/draft-2020-12-meta-applicator.json"),
),
(
"https://json-schema.org/draft/2020-12/meta/validation",
include_str!("../artefacts/draft-2020-12-meta-validation.json"),
),
(
"https://json-schema.org/draft/2020-12/meta/meta-data",
include_str!("../artefacts/draft-2020-12-meta-meta-data.json"),
),
(
"https://json-schema.org/draft/2020-12/meta/format-annotation",
include_str!("../artefacts/draft-2020-12-meta-format-annotation.json"),
),
(
"https://json-schema.org/draft/2020-12/meta/content",
include_str!("../artefacts/draft-2020-12-meta-content.json"),
),
];
for (uri, json_str) in pairs {
if let Ok(val) = serde_json::from_str::<Value>(json_str) {
let norm = crate::referencing::uri::normalize(uri);
map.insert(norm, val);
}
}
map
}
#[derive(Clone)]
pub struct InMemoryFetcher {
schemas: BTreeMap<String, Value>,
}
impl InMemoryFetcher {
#[must_use]
pub fn builtin() -> Self {
Self {
schemas: build_builtin_map(),
}
}
#[must_use]
pub fn empty() -> Self {
Self {
schemas: BTreeMap::new(),
}
}
pub fn insert(&mut self, uri: impl Into<String>, schema: Value) -> &mut Self {
use crate::referencing::uri;
let norm = uri::normalize(&uri.into());
self.schemas.insert(norm, schema);
self
}
#[must_use]
pub fn schemas(&self) -> &BTreeMap<String, Value> {
&self.schemas
}
#[must_use]
pub fn len(&self) -> usize {
self.schemas.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.schemas.is_empty()
}
}
impl Default for InMemoryFetcher {
fn default() -> Self {
Self::builtin()
}
}
impl JsonResolver for InMemoryFetcher {
fn resolve(&self, uri: &str) -> Result<Value, ErrorTrace<ResolveError>> {
use crate::referencing::uri;
let norm = uri::normalize(uri);
self.schemas.get(&norm).cloned().ok_or_else(|| {
ResolveError::new(uri)
.into_error_trace()
.attach(format!("not found in in-memory fetcher: {uri}"))
})
}
}
impl core::fmt::Debug for InMemoryFetcher {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("InMemoryFetcher")
.field("schema_count", &self.schemas.len())
.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_builtin_has_all_draft_schemas() {
let fetcher = InMemoryFetcher::builtin();
assert!(fetcher.len() >= 20);
}
#[test]
fn test_resolve_draft4_schema() {
let fetcher = InMemoryFetcher::builtin();
let result = fetcher.resolve("http://json-schema.org/draft-04/schema#");
assert!(result.is_ok());
let schema = result.unwrap();
assert!(schema.get("id").is_some() || schema.get("$id").is_some());
}
#[test]
fn test_resolve_draft7_schema() {
let fetcher = InMemoryFetcher::builtin();
let result = fetcher.resolve("http://json-schema.org/draft-07/schema#");
assert!(result.is_ok());
}
#[test]
fn test_resolve_draft202012_schema() {
let fetcher = InMemoryFetcher::builtin();
let result = fetcher.resolve("https://json-schema.org/draft/2020-12/schema");
assert!(result.is_ok());
}
#[test]
fn test_resolve_draft201909_meta() {
let fetcher = InMemoryFetcher::builtin();
let result = fetcher.resolve("https://json-schema.org/draft/2019-09/meta/applicator");
assert!(result.is_ok());
}
#[test]
fn test_resolve_missing_uri() {
let fetcher = InMemoryFetcher::builtin();
let result = fetcher.resolve("https://example.com/nonexistent");
assert!(result.is_err());
}
#[test]
fn test_empty_fetcher() {
let fetcher = InMemoryFetcher::empty();
assert!(fetcher.is_empty());
assert_eq!(fetcher.len(), 0);
}
#[test]
fn test_insert_and_resolve() {
let mut fetcher = InMemoryFetcher::empty();
fetcher.insert(
"https://example.com/test",
serde_json::json!({"type": "string"}),
);
let result = fetcher.resolve("https://example.com/test");
assert!(result.is_ok());
assert_eq!(result.unwrap(), serde_json::json!({"type": "string"}));
}
#[test]
fn test_clone_resolver() {
let fetcher = InMemoryFetcher::builtin();
let cloned = fetcher.clone();
assert_eq!(fetcher.len(), cloned.len());
}
}