use serde_json::Value;
use std::collections::{BTreeMap, HashMap};
use std::marker::PhantomData;
use crate::error::{IssueCode, PathSegment, SizeOrigin, VldError};
use crate::schema::VldSchema;
pub trait RecordKey {
type Output;
fn parse_key(&self, key: &str) -> Result<Self::Output, VldError>;
}
#[derive(Clone, Copy, Default)]
pub struct AnyKey;
impl RecordKey for AnyKey {
type Output = String;
fn parse_key(&self, key: &str) -> Result<String, VldError> {
Ok(key.to_owned())
}
}
#[derive(Clone)]
pub struct KeySchema<K>(pub K);
impl<K: VldSchema> RecordKey for KeySchema<K> {
type Output = K::Output;
fn parse_key(&self, key: &str) -> Result<K::Output, VldError> {
self.0.parse_value(&Value::String(key.to_owned()))
}
}
pub struct ZRecord<V: VldSchema, K = AnyKey, M = HashMap<String, <V as VldSchema>::Output>> {
key_schema: K,
value_schema: V,
min_keys: Option<usize>,
max_keys: Option<usize>,
_map: PhantomData<fn() -> M>,
}
pub type ZOrderedRecord<V, K = AnyKey> =
ZRecord<V, K, BTreeMap<<K as RecordKey>::Output, <V as VldSchema>::Output>>;
impl<V: VldSchema> ZRecord<V> {
pub fn new(value_schema: V) -> Self {
Self::with_keys(AnyKey, value_schema)
}
}
impl<V: VldSchema, K, M> ZRecord<V, K, M> {
pub(crate) fn with_keys(key_schema: K, value_schema: V) -> Self {
Self {
key_schema,
value_schema,
min_keys: None,
max_keys: None,
_map: PhantomData,
}
}
pub fn min_keys(mut self, n: usize) -> Self {
self.min_keys = Some(n);
self
}
pub fn max_keys(mut self, n: usize) -> Self {
self.max_keys = Some(n);
self
}
pub fn ordered(self) -> ZOrderedRecord<V, K>
where
K: RecordKey,
K::Output: Ord,
{
ZRecord {
key_schema: self.key_schema,
value_schema: self.value_schema,
min_keys: self.min_keys,
max_keys: self.max_keys,
_map: PhantomData,
}
}
}
impl<V, K, M> VldSchema for ZRecord<V, K, M>
where
V: VldSchema,
K: RecordKey,
M: Default + Extend<(K::Output, V::Output)>,
{
type Output = M;
fn parse_value(&self, value: &Value) -> Result<M, VldError> {
let obj = value
.as_object()
.ok_or_else(|| crate::primitives::type_error(&None, "object", value))?;
let mut errors = VldError::new();
let mut failed = false;
if let Some(min) = self.min_keys {
if obj.len() < min {
errors.push_code(IssueCode::TooSmall {
origin: SizeOrigin::Record,
minimum: min.into(),
inclusive: true,
});
}
}
if let Some(max) = self.max_keys {
if obj.len() > max {
errors.push_code(IssueCode::TooBig {
origin: SizeOrigin::Record,
maximum: max.into(),
inclusive: true,
});
}
}
let mut result = M::default();
for (key, val) in obj {
let k = self.key_schema.parse_key(key);
let v = self.value_schema.parse_value(val);
match (k, v) {
(Ok(k), Ok(v)) => result.extend(std::iter::once((k, v))),
(k, v) => {
failed = true;
for e in [k.err(), v.err()].into_iter().flatten() {
errors = errors.merge(e.with_prefix(PathSegment::Field(key.clone())));
}
}
}
}
if errors.is_empty() && !failed {
Ok(result)
} else {
Err(errors)
}
}
}
#[cfg(feature = "openapi")]
impl crate::openapi::JsonSchema for AnyKey {
fn json_schema(&self) -> Value {
serde_json::json!({})
}
}
#[cfg(feature = "openapi")]
impl<K: crate::openapi::JsonSchema> crate::openapi::JsonSchema for KeySchema<K> {
fn json_schema(&self) -> Value {
self.0.json_schema()
}
}
#[cfg(feature = "openapi")]
impl<V, K, M> crate::openapi::JsonSchema for ZRecord<V, K, M>
where
V: VldSchema + crate::openapi::JsonSchema,
K: crate::openapi::JsonSchema,
{
fn json_schema(&self) -> Value {
let mut schema = serde_json::json!({
"type": "object",
"additionalProperties": self.value_schema.json_schema(),
});
let keys = self.key_schema.json_schema();
if keys.as_object().is_some_and(|o| !o.is_empty()) {
schema["propertyNames"] = keys;
}
crate::primitives::length_schema(
&mut schema,
("minProperties", "maxProperties"),
self.min_keys,
self.max_keys,
);
schema
}
}
#[cfg(feature = "openapi")]
impl<V, K, M> crate::openapi::CollectNestedSchemas for ZRecord<V, K, M>
where
V: VldSchema + crate::openapi::CollectNestedSchemas,
{
fn collect_nested_schemas(&self, out: &mut Vec<crate::schema::NestedSchemaEntry>) {
self.value_schema.collect_nested_schemas(out);
}
}