autapi 0.0.1

Automatic OpenAPI specification from function signatures
Documentation
use std::{
    borrow::Cow,
    collections::{HashSet, LinkedList},
    hash::BuildHasher,
    hash::Hash,
    iter,
    marker::PhantomData,
};

use serde::de::Visitor;

use crate::{
    Registry,
    adapters::SerdeAdapter,
    openapi::{MaybeRef, Schema, Type},
    private::size_hint,
    schema::{SchemaDeserialize, SchemaSerialize, ToSchema},
};

macro_rules! impl_to_schema_and_serialize {
    ($ty:ident < T $(,)? $($gen:ident),* >) => {

        impl<T, $($gen),*> ToSchema for $ty<T, $($gen),*>
        where
            T: ToSchema
        {
            type Original = Vec<T::Original>;

            const REQUIRED: bool = true;
            const ALWAYS_INLINED: bool = true;

            fn name() -> Cow<'static, str> {
                format!("Array_{}", T::name()).into()
            }
            fn schema(registry: &mut Registry) -> MaybeRef<Schema> {
                MaybeRef::T(Schema::default()
                    .with_schema_type(Type::Array)
                    .with_items(T::schema_ref(registry))
                )
            }
        }

        impl<T, $($gen),*> SchemaSerialize for $ty<T, $($gen),*>
        where
            Self: ToSchema,
            T: SchemaSerialize
        {
            fn schema_serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
            where
                S: serde::Serializer,
            {
                serializer.collect_seq(
                    self.iter().map(SerdeAdapter)
                        .filter(|val| !val.skip_serializing())
                )
            }

            fn is_present(&self) -> bool {
                true
            }
        }

    };
}

macro_rules! impl_deserialize {
    ($ty:ident<$($gen:ident $(: $($bound:ident)&+)?),*> $(, $reserve:expr)?) => {
        impl<$($gen),*> SchemaDeserialize for $ty<$($gen),*>
        where
            Self: ToSchema,
            T: SchemaDeserialize,
            $($($($gen: $bound,)*)?)*
        {
            fn schema_deserialize<'de, D>(deserializer: D) -> Result<Self, D::Error>
            where
                D: serde::Deserializer<'de>,
            {
                struct Vis<$($gen),*>(PhantomData<($($gen,)*)>);
                impl<'de, $($gen),*> Visitor<'de> for Vis<$($gen),*>
                where
                    T: SchemaDeserialize,
                    $($($($gen: $bound,)*)?)*
                {
                    type Value = $ty<$($gen),*>;

                    fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
                        formatter.write_str("an array")
                    }

                    fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
                    where
                        A: serde::de::SeqAccess<'de>,
                    {
                        let mut result = <$ty<$($gen),*> as Default>::default();
                        $(
                            $reserve(&mut result, size_hint::<T>(seq.size_hint()));
                        )?
                        while let Some(value) = seq.next_element::<SerdeAdapter<T>>()? {
                            result.extend(iter::once(value.0));
                        }
                        Ok(result)
                    }
                }
                deserializer.deserialize_seq(Vis(PhantomData))
            }

            fn deserialize_missing() -> Option<Self> {
                None
            }
        }
    };
}

type Slice<T> = [T];
impl_to_schema_and_serialize!(Slice<T>);

impl_to_schema_and_serialize!(Vec<T>);
impl_deserialize!(Vec<T>, Vec::reserve);

impl_to_schema_and_serialize!(LinkedList<T>);
impl_deserialize!(LinkedList<T>);

impl_to_schema_and_serialize!(HashSet<T, H>);
impl_deserialize!(HashSet<T: Eq & Hash, H: BuildHasher & Default>, HashSet::reserve);

#[cfg(test)]
mod tests {
    use insta::{assert_debug_snapshot, assert_json_snapshot};
    use serde_json::json;

    use super::*;

    #[test]
    pub fn array_schema() {
        assert_json_snapshot!(Vec::<bool>::schema(&mut Registry::default()));
        assert_json_snapshot!(LinkedList::<String>::schema(&mut Registry::default()));
        assert_json_snapshot!(HashSet::<i64>::schema(&mut Registry::default()));

        assert_json_snapshot!(SerdeAdapter(vec![1, 2, 3]));
        assert_debug_snapshot!(serde_json::from_value::<SerdeAdapter<LinkedList<String>>>(
            json!(["this", "is", "a", "linked", "list"])
        ))
    }
}