rsonschema 0.1.16

A fast, simple, user-friendly JSON Schema validator for Rust
Documentation
use crate::{Schemas, Validable, ValidationReport, error, schema};

use either::Either;
use percent_encoding::percent_decode_str;
use serde::Deserialize;
use serde_json::{Value, from_reader};
use std::fs;

fn get_remote_schema(ref_: &str) -> Option<Value> {
    if let Ok(mut response) = ureq::get(ref_).call()
        && let Ok(schema) = response.body_mut().read_json()
    {
        Some(schema)
    } else {
        None
    }
}

fn get_local_schema(ref_: &str) -> Option<Value> {
    if let Ok(file) = fs::File::open(ref_)
        && let Ok(schema) = from_reader(file)
    {
        Some(schema)
    } else {
        None
    }
}

fn get_schema_from_pointer<'a>(
    mut schema: &'a Value,
    mut parent_id: Option<schema::common::id::Id>,
    pointer: Option<&str>,
) -> Either<(&'a Value, Option<schema::common::id::Id>), error::ValidationError> {
    if let Some(pointer) = pointer {
        for key in pointer.split("/") {
            let key = percent_decode_str(key)
                .decode_utf8_lossy()
                .replace("~0", "~")
                .replace("~1", "/");
            let result = schema::common::id::extract_from_schema(schema, parent_id);
            match result {
                Either::Left(id) => {
                    parent_id = id;
                }
                Either::Right(error) => {
                    return Either::Right(error);
                }
            }
            let result = get_schema_from_key(schema, &key, pointer);
            match result {
                Either::Left(inner) => {
                    schema = inner;
                }
                Either::Right(error) => {
                    return Either::Right(error);
                }
            }
        }
    }
    Either::Left((schema, parent_id))
}

fn get_schema_from_key<'a>(
    schema: &'a Value,
    key: &str,
    ref_: &str,
) -> Either<&'a Value, error::ValidationError> {
    match schema {
        Value::Object(map) => {
            if let Some(inner) = map.get(key) {
                return Either::Left(inner);
            }
        }
        Value::Array(arr) => {
            if let Ok(index) = key.parse::<usize>()
                && let Some(inner) = arr.get(index)
            {
                return Either::Left(inner);
            }
        }
        _ => {}
    }
    let error = get_ref_error(ref_);
    Either::Right(error)
}

fn parse_schema(schema: &Value) -> Either<Box<dyn Validable>, error::ValidationError> {
    let is_root = ["$schema", "$vocabulary"]
        .iter()
        .any(|key| schema.get(key).is_some());
    let result: Result<Box<dyn Validable>, _> = if is_root {
        schema::root::RootSchema::deserialize(schema)
            .map(|root_schema| Box::new(root_schema) as Box<dyn Validable>)
    } else {
        schema::inner::InnerSchema::deserialize(schema)
            .map(|inner_schema| Box::new(inner_schema) as Box<dyn Validable>)
    };
    let result = result.map_err(|err| error::ValidationError {
        type_: error::type_::ValidationErrorType::from_de_error(err, schema.clone()),
        ..Default::default()
    });
    match result {
        Ok(schema) => Either::Left(schema),
        Err(error) => Either::Right(error),
    }
}

pub(crate) fn resolve_ref(ref_: &str) -> Option<Value> {
    match get_remote_schema(ref_) {
        Some(schema) => Some(schema),
        None => get_local_schema(ref_),
    }
}

pub(crate) fn get_schema(
    schema: &Value,
    parent_id: Option<schema::common::id::Id>,
    pointer: Option<&str>,
) -> Either<(Box<dyn Validable>, Option<schema::common::id::Id>), error::ValidationError> {
    get_schema_from_pointer(schema, parent_id, pointer).left_and_then(|(schema, parent_id)| {
        parse_schema(schema).map_left(|schema| (schema, parent_id))
    })
}

pub(crate) fn validate_ref(
    ref_: &str,
    instance: &Value,
    state: &mut schema::common::state::State,
    relative_schemas: &Schemas,
    parent_id: Option<&schema::common::id::Id>,
) -> ValidationReport {
    let (id, pointer) = get_id_and_pointer(ref_);
    let id = clean_id(id);
    let is_relative = id.is_empty();
    let (schema, id) = if is_relative {
        let id = schema::common::id::Id::Relative(String::new());
        let schema = relative_schemas.get(&id);
        (schema.cloned(), id)
    } else {
        let id = schema::common::id::Id::new(parent_id, id);
        let schema = state.get_schema_from_id(&id, relative_schemas);
        (schema, id)
    };
    if let Some(schema) = schema
        && let Either::Left((validable_schema, parent_id)) = get_schema(&schema, Some(id), pointer)
    {
        let parent_id = parent_id.as_ref();
        if is_relative {
            validable_schema.validate(instance, state, relative_schemas, parent_id)
        } else {
            let mut relative_schemas = validable_schema.get_schemas(None, false);
            relative_schemas.insert(schema::common::id::Id::Relative(String::new()), schema);
            validable_schema.validate(instance, state, &relative_schemas, parent_id)
        }
    } else {
        let error = get_ref_error(ref_);
        ValidationReport {
            errors: Some(vec![error]),
            ..Default::default()
        }
    }
}

fn get_ref_error(ref_: &str) -> error::ValidationError {
    error::ValidationError {
        type_: error::type_::ValidationErrorType::Ref {
            ref_: ref_.to_string(),
        },
        ..Default::default()
    }
}

fn get_id_and_pointer(ref_: &str) -> (&str, Option<&str>) {
    match ref_.split_once("#/") {
        Some((id, pointer)) => (id, Some(pointer)),
        None => (ref_, None),
    }
}

fn clean_id(id: &str) -> &str {
    if ["#", ""].contains(&id) { "" } else { id }
}