#[cfg(feature = "schema")]
use schemars::JsonSchema;
use serde::de::{self, MapAccess, Visitor};
use serde::ser::SerializeMap as _;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use serde_json::{Map as JsonMap, Value as JsonValue};
#[cfg(feature = "schema")]
use super::types::legal::{Brief, Hearing, LegalCase, Regulation, Statute, Treaty};
#[cfg(feature = "schema")]
use super::types::specialized::{
AudioVisualWork, Classic, Dataset, Event, Patent, Software, Standard,
};
#[cfg(feature = "schema")]
use super::types::structural::{
Collection, CollectionComponent, Monograph, Serial, SerialComponent,
};
use super::{
ClassExtension, InputReference, ReferenceClass, SupplementaryIdentifiers, UnknownClassData,
};
fn duplicate_field_error<E: de::Error>(field: &str) -> E {
de::Error::custom(format_args!("duplicate field `{field}`"))
}
impl<'de> Deserialize<'de> for InputReference {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct ReferenceVisitor;
impl<'de> Visitor<'de> for ReferenceVisitor {
type Value = InputReference;
fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter.write_str("a flat reference object with a `class` discriminator")
}
fn visit_map<M>(self, mut map: M) -> Result<Self::Value, M::Error>
where
M: MapAccess<'de>,
{
let mut class = None;
let mut identifiers = None;
let mut body = JsonMap::new();
while let Some(key) = map.next_key::<String>()? {
if key == "class" {
if class.is_some() {
return Err(duplicate_field_error::<M::Error>("class"));
}
class = Some(map.next_value::<String>()?);
} else if key == "identifiers" {
if identifiers.is_some() {
return Err(duplicate_field_error::<M::Error>("identifiers"));
}
identifiers = Some(map.next_value::<SupplementaryIdentifiers>()?);
} else {
let value = map.next_value::<JsonValue>()?;
if body.insert(key.clone(), value).is_some() {
return Err(duplicate_field_error::<M::Error>(&key));
}
}
}
let class = class.ok_or_else(|| de::Error::missing_field("class"))?;
let mut reference =
deserialize_reference_body(&class, body).map_err(de::Error::custom)?;
reference.identifiers = identifiers.unwrap_or_default();
Ok(reference)
}
}
deserializer.deserialize_map(ReferenceVisitor)
}
}
#[derive(Serialize)]
struct FlatClassProxy<'a, T: Serialize + ?Sized> {
class: &'a str,
#[serde(skip_serializing_if = "SupplementaryIdentifiers::is_empty")]
identifiers: &'a SupplementaryIdentifiers,
#[serde(flatten)]
inner: &'a T,
}
fn serialize_known<S, T>(
class: &str,
identifiers: &SupplementaryIdentifiers,
inner: &T,
serializer: S,
) -> Result<S::Ok, S::Error>
where
S: Serializer,
T: Serialize + ?Sized,
{
FlatClassProxy {
class,
identifiers,
inner,
}
.serialize(serializer)
}
fn serialize_unknown<S>(
data: &UnknownClassData,
identifiers: &SupplementaryIdentifiers,
serializer: S,
) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let extra = usize::from(!identifiers.is_empty());
let mut out = serializer.serialize_map(Some(data.fields.len() + 1 + extra))?;
out.serialize_entry("class", &data.class)?;
if !identifiers.is_empty() {
out.serialize_entry("identifiers", identifiers)?;
}
for (key, value) in &data.fields {
out.serialize_entry(key, value)?;
}
out.end()
}
impl Serialize for InputReference {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let class = self.extension.class_name();
let identifiers = &self.identifiers;
match &self.extension {
ClassExtension::Monograph(inner) => {
serialize_known(class, identifiers, inner.as_ref(), serializer)
}
ClassExtension::CollectionComponent(inner) => {
serialize_known(class, identifiers, inner.as_ref(), serializer)
}
ClassExtension::SerialComponent(inner) => {
serialize_known(class, identifiers, inner.as_ref(), serializer)
}
ClassExtension::Collection(inner) => {
serialize_known(class, identifiers, inner.as_ref(), serializer)
}
ClassExtension::Serial(inner) => {
serialize_known(class, identifiers, inner.as_ref(), serializer)
}
ClassExtension::LegalCase(inner) => {
serialize_known(class, identifiers, inner.as_ref(), serializer)
}
ClassExtension::Statute(inner) => {
serialize_known(class, identifiers, inner.as_ref(), serializer)
}
ClassExtension::Treaty(inner) => {
serialize_known(class, identifiers, inner.as_ref(), serializer)
}
ClassExtension::Hearing(inner) => {
serialize_known(class, identifiers, inner.as_ref(), serializer)
}
ClassExtension::Regulation(inner) => {
serialize_known(class, identifiers, inner.as_ref(), serializer)
}
ClassExtension::Brief(inner) => {
serialize_known(class, identifiers, inner.as_ref(), serializer)
}
ClassExtension::Classic(inner) => {
serialize_known(class, identifiers, inner.as_ref(), serializer)
}
ClassExtension::Patent(inner) => {
serialize_known(class, identifiers, inner.as_ref(), serializer)
}
ClassExtension::Dataset(inner) => {
serialize_known(class, identifiers, inner.as_ref(), serializer)
}
ClassExtension::Standard(inner) => {
serialize_known(class, identifiers, inner.as_ref(), serializer)
}
ClassExtension::Software(inner) => {
serialize_known(class, identifiers, inner.as_ref(), serializer)
}
ClassExtension::Event(inner) => {
serialize_known(class, identifiers, inner.as_ref(), serializer)
}
ClassExtension::AudioVisual(inner) => {
serialize_known(class, identifiers, inner.as_ref(), serializer)
}
ClassExtension::Unknown(data) => serialize_unknown(data, identifiers, serializer),
}
}
}
#[cfg(feature = "schema")]
impl JsonSchema for InputReference {
fn schema_name() -> std::borrow::Cow<'static, str> {
"InputReference".into()
}
fn json_schema(generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
let variants = [
reference_schema_branch::<Monograph>(generator, ReferenceClass::Monograph.name()),
reference_schema_branch::<CollectionComponent>(
generator,
ReferenceClass::CollectionComponent.name(),
),
reference_schema_branch::<SerialComponent>(
generator,
ReferenceClass::SerialComponent.name(),
),
reference_schema_branch::<Collection>(generator, ReferenceClass::Collection.name()),
reference_schema_branch::<Serial>(generator, ReferenceClass::Serial.name()),
reference_schema_branch::<LegalCase>(generator, ReferenceClass::LegalCase.name()),
reference_schema_branch::<Statute>(generator, ReferenceClass::Statute.name()),
reference_schema_branch::<Treaty>(generator, ReferenceClass::Treaty.name()),
reference_schema_branch::<Hearing>(generator, ReferenceClass::Hearing.name()),
reference_schema_branch::<Regulation>(generator, ReferenceClass::Regulation.name()),
reference_schema_branch::<Brief>(generator, ReferenceClass::Brief.name()),
reference_schema_branch::<Classic>(generator, ReferenceClass::Classic.name()),
reference_schema_branch::<Patent>(generator, ReferenceClass::Patent.name()),
reference_schema_branch::<Dataset>(generator, ReferenceClass::Dataset.name()),
reference_schema_branch::<Standard>(generator, ReferenceClass::Standard.name()),
reference_schema_branch::<Software>(generator, ReferenceClass::Software.name()),
reference_schema_branch::<Event>(generator, ReferenceClass::Event.name()),
reference_schema_branch::<AudioVisualWork>(
generator,
ReferenceClass::AudioVisual.name(),
),
];
schemars::json_schema!({
"oneOf": variants,
"unevaluatedProperties": false
})
}
}
#[cfg(feature = "schema")]
fn reference_schema_branch<T: JsonSchema>(
generator: &mut schemars::SchemaGenerator,
class: &str,
) -> JsonValue {
let mut schema = T::json_schema(generator);
let object = schema.ensure_object();
if !object.get("properties").is_some_and(JsonValue::is_object) {
object.insert("properties".to_string(), JsonValue::Object(JsonMap::new()));
}
let Some(properties) = object
.get_mut("properties")
.and_then(JsonValue::as_object_mut)
else {
return schema.to_value();
};
properties.insert(
"class".to_string(),
serde_json::json!({
"type": "string",
"const": class
}),
);
properties.insert(
"identifiers".to_string(),
generator
.subschema_for::<SupplementaryIdentifiers>()
.to_value(),
);
if !object.get("required").is_some_and(JsonValue::is_array) {
object.insert("required".to_string(), JsonValue::Array(Vec::new()));
}
let Some(required) = object.get_mut("required").and_then(JsonValue::as_array_mut) else {
return schema.to_value();
};
if !required.iter().any(|value| value.as_str() == Some("class")) {
required.push(JsonValue::String("class".to_string()));
}
schema.to_value()
}
fn deserialize_reference_body(
class: &str,
body: JsonMap<String, JsonValue>,
) -> Result<InputReference, serde_json::Error> {
let value = JsonValue::Object(body);
match ReferenceClass::from_known_name(class) {
Some(ReferenceClass::Monograph) => {
InputReference::from_known(ClassExtension::Monograph, value)
}
Some(ReferenceClass::CollectionComponent) => {
InputReference::from_known(ClassExtension::CollectionComponent, value)
}
Some(ReferenceClass::SerialComponent) => {
InputReference::from_known(ClassExtension::SerialComponent, value)
}
Some(ReferenceClass::Collection) => {
InputReference::from_known(ClassExtension::Collection, value)
}
Some(ReferenceClass::Serial) => InputReference::from_known(ClassExtension::Serial, value),
Some(ReferenceClass::LegalCase) => {
InputReference::from_known(ClassExtension::LegalCase, value)
}
Some(ReferenceClass::Statute) => InputReference::from_known(ClassExtension::Statute, value),
Some(ReferenceClass::Treaty) => InputReference::from_known(ClassExtension::Treaty, value),
Some(ReferenceClass::Hearing) => InputReference::from_known(ClassExtension::Hearing, value),
Some(ReferenceClass::Regulation) => {
InputReference::from_known(ClassExtension::Regulation, value)
}
Some(ReferenceClass::Brief) => InputReference::from_known(ClassExtension::Brief, value),
Some(ReferenceClass::Classic) => InputReference::from_known(ClassExtension::Classic, value),
Some(ReferenceClass::Patent) => InputReference::from_known(ClassExtension::Patent, value),
Some(ReferenceClass::Dataset) => InputReference::from_known(ClassExtension::Dataset, value),
Some(ReferenceClass::Standard) => {
InputReference::from_known(ClassExtension::Standard, value)
}
Some(ReferenceClass::Software) => {
InputReference::from_known(ClassExtension::Software, value)
}
Some(ReferenceClass::Event) => InputReference::from_known(ClassExtension::Event, value),
Some(ReferenceClass::AudioVisual) => {
InputReference::from_known(ClassExtension::AudioVisual, value)
}
Some(ReferenceClass::Unknown(_)) | None => {
let fields = if let JsonValue::Object(fields) = value {
fields
} else {
JsonMap::new()
};
Ok(InputReference::Unknown(Box::new(UnknownClassData {
class: class.to_string(),
fields,
})))
}
}
}