use std::sync::Arc;
use referencing::{Draft, Registry};
use serde_json::Value;
use crate::{
canonical::{
context::CanonicalizationContext,
ir::{RawJson, Schema, SchemaKind},
parse,
schema::CanonicalSchema,
CanonicalizationError, DefinitionMap,
},
compiler::{
formats_are_assertions_by_default, normalize_base_uri, resolve_base_uri, validate_schema,
},
options::{PatternEngineOptions, PatternOptions},
};
#[must_use]
pub fn options() -> CanonicalizeOptions<'static> {
CanonicalizeOptions::default()
}
#[derive(Default)]
pub struct CanonicalizeOptions<'r> {
registry: Option<&'r Registry<'r>>,
pattern_options: PatternEngineOptions,
draft: Option<Draft>,
validate_formats: Option<bool>,
}
impl<'r> CanonicalizeOptions<'r> {
#[must_use]
pub fn with_registry(mut self, registry: &'r Registry<'r>) -> Self {
self.registry = Some(registry);
self
}
#[must_use]
pub fn with_draft(mut self, draft: Draft) -> Self {
self.draft = Some(draft);
self
}
#[must_use]
pub fn should_validate_formats(mut self, enabled: bool) -> Self {
self.validate_formats = Some(enabled);
self
}
#[must_use]
#[allow(clippy::needless_pass_by_value)]
pub fn with_pattern_options<E>(mut self, options: PatternOptions<E>) -> Self {
self.pattern_options = options.inner;
self
}
pub fn canonicalize(self, value: &Value) -> Result<CanonicalSchema, CanonicalizationError> {
build(
value,
self.draft,
self.registry,
self.validate_formats,
self.pattern_options,
)
}
}
fn build(
value: &Value,
draft: Option<Draft>,
registry: Option<&Registry<'_>>,
validate_formats: Option<bool>,
pattern_options: PatternEngineOptions,
) -> Result<CanonicalSchema, CanonicalizationError> {
match value {
Value::Bool(_) | Value::Object(_) => {}
other @ (Value::Null | Value::Number(_) | Value::String(_) | Value::Array(_)) => {
return Err(CanonicalizationError::InvalidSchemaType(other.to_string()))
}
}
let draft = detect_draft(value, draft, registry)?;
if draft == Draft::Unknown {
return Ok(CanonicalSchema::new(
Schema::new(SchemaKind::Raw(RawJson::new(value.clone()))),
draft,
pattern_options,
validate_formats.unwrap_or(false),
Arc::new(DefinitionMap::new()),
));
}
let validate_formats =
validate_formats.unwrap_or_else(|| formats_are_assertions_by_default(draft));
validate_schema(draft, value)?;
let resource = draft.create_resource_ref(value);
let base_uri = resolve_base_uri(None, resource.id())?;
let registry = match registry {
Some(registry) => registry
.add(base_uri.as_str(), resource)?
.draft(draft)
.prepare()?,
None => Registry::new()
.add(base_uri.as_str(), resource)?
.draft(draft)
.prepare()?,
};
let base_uri = normalize_base_uri(®istry, &base_uri);
let resolver = registry.resolver(base_uri);
let context = CanonicalizationContext::new(draft, pattern_options, validate_formats);
let (inner, definitions) = match parse::parse(value, &context, &resolver)? {
Some(parsed) => (parsed.root, Arc::new(parsed.definitions)),
None => (
Schema::new(SchemaKind::Raw(RawJson::new(value.clone()))),
Arc::new(DefinitionMap::new()),
),
};
Ok(CanonicalSchema::new(
inner,
draft,
pattern_options,
validate_formats,
definitions,
))
}
fn detect_draft<'r>(
value: &Value,
draft: Option<Draft>,
registry: Option<&'r Registry<'r>>,
) -> Result<Draft, CanonicalizationError> {
let mut options = crate::options();
if let Some(draft) = draft {
options = options.with_draft(draft);
}
if let Some(registry) = registry {
options = options.with_registry(registry);
}
options
.draft_for(value)
.map_err(CanonicalizationError::from)
}