use crate::{deserialize, CodecError, Node};
use serde::de::DeserializeOwned;
use serde::ser::SerializeMap;
use serde::{Deserialize, Serialize};
use serde_json::{Map, Value as Json};
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
pub struct KanonakNode {
#[serde(rename = "$id", skip_serializing_if = "Option::is_none", default)]
pub id: Option<String>,
#[serde(rename = "$type", skip_serializing_if = "Option::is_none", default)]
pub type_uri: Option<String>,
#[serde(rename = "$types", skip_serializing_if = "Option::is_none", default)]
pub types: Option<Vec<String>>,
#[serde(rename = "$name", skip_serializing_if = "Option::is_none", default)]
pub name: Option<String>,
#[serde(
rename = "$contentHash",
skip_serializing_if = "Option::is_none",
default
)]
pub content_hash: Option<String>,
#[serde(rename = "$version", skip_serializing_if = "Option::is_none", default)]
pub version: Option<String>,
#[serde(flatten)]
pub extra: Map<String, Json>,
}
pub trait KanonakResource {
fn kanonak_node(&self) -> &KanonakNode;
fn kanonak_node_mut(&mut self) -> &mut KanonakNode;
}
#[derive(Debug, Clone, PartialEq)]
pub enum Ref<T> {
Reference(String),
Embedded(T),
}
impl<T> Ref<T> {
pub fn to(uri: impl Into<String>) -> Self {
Ref::Reference(uri.into())
}
pub fn to_resource(target: &impl KanonakResource) -> Result<Self, CodecError> {
match target.kanonak_node().id.as_deref() {
Some(id) if !id.is_empty() => Ok(Ref::Reference(id.to_string())),
_ => Err(CodecError::Malformed(
"Ref::to_resource requires a resource with a non-empty envelope id — \
to carry the value inline instead, use Ref::embed."
.into(),
)),
}
}
pub fn embed(value: T) -> Self {
Ref::Embedded(value)
}
pub fn embed_named(mut value: T, name: impl Into<String>) -> Self
where
T: KanonakResource,
{
value.kanonak_node_mut().name = Some(name.into());
Ref::Embedded(value)
}
pub fn is_reference(&self) -> bool {
matches!(self, Ref::Reference(_))
}
}
impl<T: Serialize> Serialize for Ref<T> {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
match self {
Ref::Reference(uri) => {
let mut map = serializer.serialize_map(Some(1))?;
map.serialize_entry("$ref", uri)?;
map.end()
}
Ref::Embedded(value) => value.serialize(serializer),
}
}
}
impl<'de, T: DeserializeOwned> Deserialize<'de> for Ref<T> {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let value = Json::deserialize(deserializer)?;
if let Some(uri) = value.get("$ref").and_then(|r| r.as_str()) {
return Ok(Ref::Reference(uri.to_string()));
}
serde_json::from_value(value)
.map(Ref::Embedded)
.map_err(serde::de::Error::custom)
}
}
pub fn to_node<T: Serialize>(typed: &T, schema: &Json) -> Result<Node, CodecError> {
let wire = serde_json::to_value(typed)
.map_err(|e| CodecError::Malformed(format!("typed value failed to serialize: {}", e)))?;
match wire.as_object() {
Some(map) => deserialize(map, schema),
None => Err(CodecError::Malformed(
"a typed instance must serialize to a JSON object (the wire node form)".into(),
)),
}
}