use super::options::CompilationOptions;
use crate::{
compilation::DEFAULT_SCOPE,
paths::{InstancePath, JSONPointer, PathChunk},
resolver::Resolver,
schemas,
};
use serde_json::Value;
use std::{borrow::Cow, sync::Arc};
use url::{ParseError, Url};
static DEFAULT_SCHEME: &str = "json-schema";
#[derive(Debug, Clone)]
pub(crate) struct CompilationContext<'a> {
base_uri: BaseUri<'a>,
pub(crate) config: Arc<CompilationOptions>,
pub(crate) resolver: Arc<Resolver>,
pub(crate) schema_path: InstancePath<'a>,
}
#[derive(Debug, Clone)]
pub(crate) enum BaseUri<'a> {
Known(Cow<'a, Url>),
Unknown,
}
impl<'a> BaseUri<'a> {
pub(crate) fn with_new_scope(&self, new_scope: &str) -> Result<Self, ParseError> {
let options = match self {
BaseUri::Known(u) => Url::options().base_url(Some(u)),
BaseUri::Unknown => Url::options().base_url(Some(&DEFAULT_SCOPE)),
};
Ok(options.parse(new_scope)?.into())
}
}
impl<'a> From<Option<Url>> for BaseUri<'a> {
fn from(u: Option<Url>) -> Self {
u.map_or(BaseUri::Unknown, Into::into)
}
}
impl<'a> From<&'a Url> for BaseUri<'a> {
fn from(u: &'a Url) -> Self {
if u.scheme() == DEFAULT_SCHEME {
BaseUri::Unknown
} else {
BaseUri::Known(Cow::Borrowed(u))
}
}
}
impl<'a> From<Url> for BaseUri<'a> {
fn from(u: Url) -> Self {
if u.scheme() == DEFAULT_SCHEME {
BaseUri::Unknown
} else {
BaseUri::Known(Cow::Owned(u))
}
}
}
impl<'a> From<&'a BaseUri<'a>> for Cow<'a, Url> {
fn from(uri: &BaseUri<'a>) -> Self {
match uri {
BaseUri::Unknown => Cow::Borrowed(&DEFAULT_SCOPE),
BaseUri::Known(u) => u.clone(),
}
}
}
impl<'a> CompilationContext<'a> {
pub(crate) const fn new(
scope: BaseUri<'a>,
config: Arc<CompilationOptions>,
resolver: Arc<Resolver>,
) -> Self {
CompilationContext {
base_uri: scope,
config,
resolver,
schema_path: InstancePath::new(),
}
}
#[allow(clippy::doc_markdown)]
#[inline]
pub(crate) fn push(&'a self, schema: &Value) -> Result<Self, ParseError> {
if let Some(id) = schemas::id_of(self.config.draft(), schema) {
Ok(CompilationContext {
base_uri: self.base_uri.with_new_scope(id)?,
config: Arc::clone(&self.config),
resolver: Arc::clone(&self.resolver),
schema_path: self.schema_path.clone(),
})
} else {
Ok(CompilationContext {
base_uri: self.base_uri.clone(),
config: Arc::clone(&self.config),
resolver: Arc::clone(&self.resolver),
schema_path: self.schema_path.clone(),
})
}
}
#[inline]
pub(crate) fn with_path(&'a self, chunk: impl Into<PathChunk>) -> Self {
let schema_path = self.schema_path.push(chunk);
CompilationContext {
base_uri: self.base_uri.clone(),
config: Arc::clone(&self.config),
resolver: Arc::clone(&self.resolver),
schema_path,
}
}
#[inline]
pub(crate) fn into_pointer(self) -> JSONPointer {
self.schema_path.into()
}
#[inline]
pub(crate) fn as_pointer_with(&self, chunk: impl Into<PathChunk>) -> JSONPointer {
self.schema_path.push(chunk).into()
}
pub(crate) fn build_url(&self, reference: &str) -> Result<Url, ParseError> {
let cowbase: Cow<Url> = (&self.base_uri).into();
Url::options()
.base_url(Some(cowbase.as_ref()))
.parse(reference)
}
pub(crate) fn base_uri(&self) -> Option<Url> {
match &self.base_uri {
BaseUri::Known(u) => Some(u.as_ref().clone()),
BaseUri::Unknown => None,
}
}
}