use std::{borrow::Cow, error::Error, fmt, marker::PhantomData};
use schemars::{JsonSchema, Schema, SchemaGenerator, generate::SchemaSettings, json_schema};
use semver::{Version, VersionReq};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use serde_json::Value;
use crate::{AnyArray, AnyArrayView, AnyArrayViewMut, AnyCowArray};
pub trait Codec: 'static + Send + Sync + Clone {
type Error: 'static + Send + Sync + Error;
fn encode(&self, data: AnyCowArray) -> Result<AnyArray, Self::Error>;
fn decode(&self, encoded: AnyCowArray) -> Result<AnyArray, Self::Error>;
fn decode_into(
&self,
encoded: AnyArrayView,
decoded: AnyArrayViewMut,
) -> Result<(), Self::Error>;
}
pub trait StaticCodec: Codec {
const CODEC_ID: &'static str;
type Config<'de>: Serialize + Deserialize<'de> + JsonSchema;
fn from_config(config: Self::Config<'_>) -> Self;
fn get_config(&self) -> StaticCodecConfig<'_, Self>;
}
pub trait DynCodec: Codec {
type Type: DynCodecType;
fn ty(&self) -> Self::Type;
fn get_config<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error>;
}
pub trait DynCodecType: 'static + Send + Sync {
type Codec: DynCodec<Type = Self>;
fn codec_id(&self) -> &str;
fn codec_config_schema(&self) -> Schema;
fn codec_from_config<'de, D: Deserializer<'de>>(
&self,
config: D,
) -> Result<Self::Codec, D::Error>;
}
impl<T: StaticCodec> DynCodec for T {
type Type = StaticCodecType<Self>;
fn ty(&self) -> Self::Type {
StaticCodecType::of()
}
fn get_config<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
<T as StaticCodec>::get_config(self).serialize(serializer)
}
}
pub struct StaticCodecType<T: StaticCodec> {
_marker: PhantomData<T>,
}
impl<T: StaticCodec> StaticCodecType<T> {
#[must_use]
pub const fn of() -> Self {
Self {
_marker: PhantomData::<T>,
}
}
}
impl<T: StaticCodec> DynCodecType for StaticCodecType<T> {
type Codec = T;
fn codec_id(&self) -> &str {
T::CODEC_ID
}
fn codec_config_schema(&self) -> Schema {
let mut settings = SchemaSettings::draft2020_12();
settings.inline_subschemas = true;
settings
.into_generator()
.into_root_schema_for::<T::Config<'static>>()
}
fn codec_from_config<'de, D: Deserializer<'de>>(
&self,
config: D,
) -> Result<Self::Codec, D::Error> {
let config = T::Config::deserialize(config)?;
Ok(T::from_config(config))
}
}
#[derive(Serialize, Deserialize)]
#[serde(bound = "")]
pub struct StaticCodecConfig<'a, T: StaticCodec> {
#[serde(default)]
id: StaticCodecId<T>,
#[serde(flatten)]
#[serde(borrow)]
pub config: T::Config<'a>,
}
impl<'a, T: StaticCodec> StaticCodecConfig<'a, T> {
#[must_use]
pub const fn new(config: T::Config<'a>) -> Self {
Self {
id: StaticCodecId::of(),
config,
}
}
}
impl<'a, T: StaticCodec> From<&T::Config<'a>> for StaticCodecConfig<'a, T>
where
T::Config<'a>: Clone,
{
fn from(config: &T::Config<'a>) -> Self {
Self::new(config.clone())
}
}
struct StaticCodecId<T: StaticCodec>(PhantomData<T>);
impl<T: StaticCodec> StaticCodecId<T> {
#[must_use]
pub const fn of() -> Self {
Self(PhantomData::<T>)
}
}
impl<T: StaticCodec> Default for StaticCodecId<T> {
fn default() -> Self {
Self::of()
}
}
impl<T: StaticCodec> Serialize for StaticCodecId<T> {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
T::CODEC_ID.serialize(serializer)
}
}
impl<'de, T: StaticCodec> Deserialize<'de> for StaticCodecId<T> {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let id = Cow::<str>::deserialize(deserializer)?;
let id = &*id;
if id != T::CODEC_ID {
return Err(serde::de::Error::custom(format!(
"expected codec id {:?} but found {id:?}",
T::CODEC_ID,
)));
}
Ok(Self::of())
}
}
pub fn serialize_codec_config_with_id<T: Serialize, C: DynCodec, S: Serializer>(
config: &T,
codec: &C,
serializer: S,
) -> Result<S::Ok, S::Error> {
#[derive(Serialize)]
struct DynCodecConfigWithId<'a, T> {
id: &'a str,
#[serde(flatten)]
config: &'a T,
}
DynCodecConfigWithId {
id: codec.ty().codec_id(),
config,
}
.serialize(serializer)
}
pub fn codec_from_config_with_id<'de, T: DynCodecType, D: Deserializer<'de>>(
ty: &T,
config: D,
) -> Result<T::Codec, D::Error> {
let mut config = Value::deserialize(config)?;
if let Some(config) = config.as_object_mut() {
if let Some(id) = config.remove("id") {
let codec_id = ty.codec_id();
if !matches!(id, Value::String(ref id) if id == codec_id) {
return Err(serde::de::Error::custom(format!(
"expected codec id {codec_id:?} but found {id}"
)));
}
}
}
ty.codec_from_config(config)
.map_err(serde::de::Error::custom)
}
pub struct StaticCodecVersion<const MAJOR: u64, const MINOR: u64, const PATCH: u64>;
impl<const MAJOR: u64, const MINOR: u64, const PATCH: u64> StaticCodecVersion<MAJOR, MINOR, PATCH> {
#[must_use]
pub const fn version() -> Version {
Version::new(MAJOR, MINOR, PATCH)
}
}
#[expect(clippy::expl_impl_clone_on_copy)]
impl<const MAJOR: u64, const MINOR: u64, const PATCH: u64> Clone
for StaticCodecVersion<MAJOR, MINOR, PATCH>
{
fn clone(&self) -> Self {
*self
}
}
impl<const MAJOR: u64, const MINOR: u64, const PATCH: u64> Copy
for StaticCodecVersion<MAJOR, MINOR, PATCH>
{
}
impl<const MAJOR: u64, const MINOR: u64, const PATCH: u64> fmt::Debug
for StaticCodecVersion<MAJOR, MINOR, PATCH>
{
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
<semver::Version as fmt::Debug>::fmt(&Self::version(), fmt)
}
}
impl<const MAJOR: u64, const MINOR: u64, const PATCH: u64> fmt::Display
for StaticCodecVersion<MAJOR, MINOR, PATCH>
{
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
<semver::Version as fmt::Display>::fmt(&Self::version(), fmt)
}
}
impl<const MAJOR: u64, const MINOR: u64, const PATCH: u64> Default
for StaticCodecVersion<MAJOR, MINOR, PATCH>
{
fn default() -> Self {
Self
}
}
impl<const MAJOR: u64, const MINOR: u64, const PATCH: u64> Serialize
for StaticCodecVersion<MAJOR, MINOR, PATCH>
{
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
Self::version().serialize(serializer)
}
}
impl<'de, const MAJOR: u64, const MINOR: u64, const PATCH: u64> Deserialize<'de>
for StaticCodecVersion<MAJOR, MINOR, PATCH>
{
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let version = Version::deserialize(deserializer)?;
let requirement = VersionReq {
comparators: vec![semver::Comparator {
op: semver::Op::Caret,
major: version.major,
minor: Some(version.minor),
patch: Some(version.patch),
pre: version.pre,
}],
};
if !requirement.matches(&Self::version()) {
return Err(serde::de::Error::custom(format!(
"{Self} does not fulfil {requirement}"
)));
}
Ok(Self)
}
}
impl<const MAJOR: u64, const MINOR: u64, const PATCH: u64> JsonSchema
for StaticCodecVersion<MAJOR, MINOR, PATCH>
{
fn schema_name() -> Cow<'static, str> {
Cow::Borrowed("StaticCodecVersion")
}
fn schema_id() -> Cow<'static, str> {
Cow::Borrowed(concat!(module_path!(), "::", "StaticCodecVersion"))
}
fn json_schema(_gen: &mut SchemaGenerator) -> Schema {
json_schema!({
"type": "string",
"pattern": r"^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$",
"description": "A semver.org compliant semantic version number.",
})
}
}