use alloc::borrow::Cow;
use alloc::boxed::Box;
use alloc::collections::BTreeMap;
use alloc::string::ToString;
use alloc::sync::Arc;
use alloc::vec::Vec;
use core::any::{Any, TypeId, type_name};
use core::fmt;
use core::marker::PhantomData;
use crate::State;
use crate::Text;
use crate::de::enums::{
AdjacentlyTaggedSink, ArenaVariant, EnumKey, ExternallyTaggedSink, InternallyTaggedSink, Tag,
UntaggedTry, ValueVariant, VariantBuilder, VariantNames, Variants, no_matching_variant,
untagged_handle_with,
};
use crate::de::mapped::mapped;
use crate::de::{Deserialize, DeserializeOwned, OwnedSink, Sink, SinkHandle};
use crate::error::{Error, ErrorKind};
use crate::event::{Atom, ContainerShape};
use crate::ser::enums::{EntrySer, FieldsSer, TaggedNewtype};
use crate::ser::{
Describe, Emit, Serialize, SerializeHandle, SerializeRef, Variant, VariantKind, VariantRepr,
};
pub trait OpenEnum: Send + Sync + 'static {
#[doc(hidden)]
const INFO: &'static OpenEnumInfo;
#[doc(hidden)]
fn __private_variant(value: &Self) -> VariantValue<'_>;
}
pub trait OpenVariant<O: ?Sized + OpenEnum>: Serialize + DeserializeOwned + 'static {
#[doc(hidden)]
const ENTRY: &'static VariantEntry;
#[doc(hidden)]
fn __private_into_box(self) -> Box<O>;
}
type MakeVariant<O> =
for<'a, 'de> fn(&mut State, PhantomData<&'a &'de ()>) -> ArenaVariant<'a, 'de, Box<O>>;
fn make_variant<'a, 'de, O: ?Sized + OpenEnum, T: OpenVariant<O>>(
state: &mut State,
_: PhantomData<&'a &'de ()>,
) -> ArenaVariant<'a, 'de, Box<O>> {
ValueVariant::arena(T::__private_into_box, state)
}
type TryVariant<O> = for<'t, 'de> fn(&mut UntaggedTry<'t, 'de, Box<O>>);
fn try_variant<O: ?Sized + OpenEnum, T: OpenVariant<O>>(attempt: &mut UntaggedTry<'_, '_, Box<O>>) {
attempt.variant::<T>(T::__private_into_box);
}
const STYLES: usize = 8;
#[doc(hidden)]
pub struct VariantEntry {
pub styled: &'static [&'static str; STYLES],
pub rename: Option<Tag<'static>>,
pub aliases: &'static [Tag<'static>],
}
#[doc(hidden)]
#[derive(Clone, Copy)]
pub enum OpenRepr {
External,
Internal {
tag: EnumKey,
},
Adjacent {
tag: EnumKey,
content: EnumKey,
deny_unknown_fields: bool,
},
Untagged,
}
#[doc(hidden)]
pub struct OpenEnumInfo {
pub name: &'static str,
pub repr: OpenRepr,
pub rename_all: usize,
pub alias_all: &'static [usize],
}
#[doc(hidden)]
pub struct VariantValue<'a> {
entry: &'static VariantEntry,
content: SerializeRef<'a>,
}
impl<'a> VariantValue<'a> {
#[inline]
pub fn new<T: Serialize>(entry: &'static VariantEntry, value: &'a T) -> VariantValue<'a> {
VariantValue {
entry,
content: SerializeRef::new(value),
}
}
}
impl VariantEntry {
#[inline]
fn name(&'static self, info: &OpenEnumInfo) -> Tag<'static> {
match self.rename {
Some(name) => name,
None => Tag::Str(self.styled[info.rename_all]),
}
}
#[inline]
fn name_handle(&'static self, info: &OpenEnumInfo) -> SerializeHandle<'static> {
match self.rename {
Some(Tag::Str(ref name)) => SerializeHandle::to(name),
Some(ref name) => SerializeHandle::to(name),
None => SerializeHandle::to(&self.styled[info.rename_all]),
}
}
#[inline]
fn name_str(&'static self, info: &OpenEnumInfo) -> Option<&'static str> {
match self.name(info) {
Tag::Str(name) => Some(name),
_ => None,
}
}
fn tags(&'static self, info: &OpenEnumInfo) -> Vec<Tag<'static>> {
let name = self.name(info);
let mut tags = Vec::from([name]);
let mut push = |tag: Tag<'static>| {
if !tags.contains(&tag) {
tags.push(tag);
}
};
for &alias in self.aliases {
push(alias);
}
for &style in info.alias_all {
push(Tag::Str(self.styled[style]));
}
tags
}
fn display_name(&self, info: &OpenEnumInfo) -> Cow<'static, str> {
match self.rename {
Some(tag) => tag_display(tag),
None => Cow::Borrowed(self.styled[info.rename_all]),
}
}
}
fn tag_display(tag: Tag<'static>) -> Cow<'static, str> {
match tag {
Tag::Str(name) => Cow::Borrowed(name),
Tag::U64(value) => Cow::Owned(value.to_string()),
Tag::I64(value) => Cow::Owned(value.to_string()),
Tag::Bool(value) => Cow::Borrowed(if value { "true" } else { "false" }),
}
}
#[derive(Default)]
pub struct OpenEnums {
enums: BTreeMap<TypeId, Registered>,
}
struct Registered {
name: &'static str,
names: Vec<Cow<'static, str>>,
table: Box<dyn Any + Send + Sync>,
}
struct Table<O: ?Sized + 'static> {
variants: Vec<Registration<O>>,
tags: Vec<(Tag<'static>, usize)>,
}
struct Registration<O: ?Sized + 'static> {
ty: TypeId,
type_name: &'static str,
make: MakeVariant<O>,
try_untagged: TryVariant<O>,
}
impl OpenEnums {
pub fn new() -> OpenEnums {
OpenEnums::default()
}
pub fn register<O: ?Sized + OpenEnum, T: OpenVariant<O>>(
&mut self,
) -> Result<&mut OpenEnums, Error> {
let entry = T::ENTRY;
let info = O::INFO;
let ty = TypeId::of::<T>();
let registered = self
.enums
.entry(TypeId::of::<O>())
.or_insert_with(|| Registered {
name: info.name,
names: Vec::new(),
table: Box::new(Table::<O> {
variants: Vec::new(),
tags: Vec::new(),
}),
});
let table = registered
.table
.downcast_mut::<Table<O>>()
.expect("the table of an open enum has the type of the open enum");
if table.variants.iter().any(|variant| variant.ty == ty) {
return Ok(self);
}
let tags = match info.repr {
OpenRepr::Untagged => Vec::new(),
_ => entry.tags(info),
};
for tag in &tags {
if let Ok(index) = table.tags.binary_search_by(|(other, _)| other.cmp(tag)) {
return Err(Error::new(
ErrorKind::Configuration,
alloc::format!(
"duplicate variant `{}` of {}: `{}` and `{}`",
tag_display(*tag),
info.name,
table.variants[table.tags[index].1].type_name,
type_name::<T>(),
),
));
}
}
let variant = table.variants.len();
table.variants.push(Registration {
ty,
type_name: type_name::<T>(),
make: make_variant::<O, T>,
try_untagged: try_variant::<O, T>,
});
for tag in tags {
let index = table
.tags
.binary_search_by(|(other, _)| other.cmp(&tag))
.unwrap_err();
table.tags.insert(index, (tag, variant));
}
let name = entry.display_name(info);
let index = registered.names.binary_search(&name).unwrap_or_else(|x| x);
registered.names.insert(index, name);
Ok(self)
}
#[inline]
fn get<O: ?Sized + OpenEnum>(&self) -> Option<&Registered> {
self.enums.get(&TypeId::of::<O>())
}
#[inline]
fn lookup<O: ?Sized + OpenEnum>(&self, tag: Tag<'_>) -> Option<MakeVariant<O>> {
let table = self.get::<O>()?.table.downcast_ref::<Table<O>>()?;
let index = table
.tags
.binary_search_by(|(other, _)| other.cmp(&tag))
.ok()?;
Some(table.variants[table.tags[index].1].make)
}
#[inline]
fn untagged<O: ?Sized + OpenEnum>(&self, index: usize) -> Option<TryVariant<O>> {
let table = self.get::<O>()?.table.downcast_ref::<Table<O>>()?;
Some(table.variants.get(index)?.try_untagged)
}
}
impl fmt::Debug for OpenEnums {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_map()
.entries(
self.enums
.values()
.map(|registered| (registered.name, ®istered.names)),
)
.finish()
}
}
impl Serialize for Tag<'_> {
fn serialize<'a>(value: &'a Self, _state: &mut State) -> Result<Emit<'a>, Error> {
Ok(Emit::Atom(match *value {
Tag::Str(name) => Atom::Str(Text::borrowed(name)),
Tag::U64(value) => Atom::U64(value),
Tag::I64(value) => Atom::I64(value),
Tag::Bool(value) => Atom::Bool(value),
}))
}
}
fn variant_repr(repr: OpenRepr) -> VariantRepr<'static> {
match repr {
OpenRepr::External => VariantRepr::External,
OpenRepr::Internal { tag } => VariantRepr::Internal { tag: tag.name },
OpenRepr::Adjacent { tag, content, .. } => VariantRepr::Adjacent {
tag: tag.name,
content: content.name,
},
OpenRepr::Untagged => VariantRepr::Untagged,
}
}
pub fn serialize<'a, O: ?Sized + OpenEnum>(
value: &'a O,
state: &mut State,
) -> Result<Emit<'a>, Error> {
let VariantValue { entry, content } = O::__private_variant(value);
let info = O::INFO;
Ok(match info.repr {
OpenRepr::External => match entry.name_str(info) {
Some(name) => {
FieldsSer(Vec::from([(name, SerializeHandle::from(content))])).into_emit(state)
}
None => EntrySer::new(entry.name_handle(info), SerializeHandle::from(content))
.into_emit(state),
},
OpenRepr::Internal { tag } => {
TaggedNewtype::new(tag.name, entry.name_handle(info), content).into_emit(state)
}
OpenRepr::Adjacent {
tag,
content: content_key,
..
} => FieldsSer(Vec::from([
(tag.name, entry.name_handle(info)),
(content_key.name, SerializeHandle::from(content)),
]))
.into_emit(state),
OpenRepr::Untagged => Emit::Forward(SerializeHandle::from(content)),
})
}
pub fn describe<O: ?Sized + OpenEnum>(value: &O, d: &mut dyn Describe) {
let VariantValue { entry, content } = O::__private_variant(value);
let name = entry.display_name(O::INFO);
let repr = O::INFO.repr;
d.variant(&Variant::new(
O::INFO.name,
&name,
VariantKind::Newtype,
variant_repr(repr),
));
if let OpenRepr::Internal { .. } | OpenRepr::Untagged = repr {
content.describe(d);
}
}
pub fn container_shape<O: ?Sized + OpenEnum>() -> ContainerShape {
match O::INFO.repr {
OpenRepr::External => ContainerShape::with_len(1),
OpenRepr::Internal { .. } => ContainerShape::new(),
OpenRepr::Adjacent { .. } => ContainerShape::with_len(2),
OpenRepr::Untagged => ContainerShape::new(),
}
}
fn lookup<'a, 'de: 'a, O: ?Sized + OpenEnum>(
tag: Tag<'_>,
state: &mut State,
) -> Option<ArenaVariant<'a, 'de, Box<O>>> {
let make = match state.get::<OpenEnums>() {
Some(registry) if registry.get::<O>().is_some() => registry.lookup::<O>(tag)?,
_ => return Some(ArenaVariant::new(Unregistered::<O>::new(), state)),
};
Some(make(state, PhantomData))
}
fn untagged_variants<'de, O: ?Sized + OpenEnum>(
index: usize,
attempt: &mut UntaggedTry<'_, 'de, Box<O>>,
) -> bool {
let try_variant = match attempt.state().get::<OpenEnums>() {
Some(registry) => registry.untagged::<O>(index),
None => None,
};
match try_variant {
Some(try_variant) => {
try_variant(attempt);
true
}
None => false,
}
}
#[cold]
fn untagged_no_match<O: ?Sized + OpenEnum>(name: &str, state: &State) -> Error {
let registered = state
.get::<OpenEnums>()
.is_some_and(|registry| registry.get::<O>().is_some());
if !registered {
return Unregistered::<O>::new().error();
}
no_matching_variant(name, state)
}
fn names<O: ?Sized + OpenEnum>(state: &State) -> Vec<&str> {
match state
.get::<OpenEnums>()
.and_then(|registry| registry.get::<O>())
{
Some(registered) => registered.names.iter().map(|name| &**name).collect(),
None => Vec::new(),
}
}
fn no_unit<E>(_tag: Tag<'_>) -> Option<E> {
None
}
pub fn deserialize_box<'out, 'de, O: ?Sized + OpenEnum>(
out: &'out mut Option<Box<O>>,
state: &mut State,
) -> SinkHandle<'out, 'de> {
let name = O::INFO.name;
if let OpenRepr::Untagged = O::INFO.repr {
return untagged_handle_with(
out,
name,
untagged_variants::<O>,
untagged_no_match::<O>,
state,
);
}
let variants = Variants {
lookup: lookup::<O>,
other: None,
default: None,
names: VariantNames::Dynamic(names::<O>),
};
match O::INFO.repr {
OpenRepr::External => ExternallyTaggedSink::handle(out, name, variants, no_unit, state),
OpenRepr::Internal { tag } => InternallyTaggedSink::handle(out, tag, name, variants, state),
OpenRepr::Adjacent {
tag,
content,
deny_unknown_fields,
} => AdjacentlyTaggedSink::handle(
out,
tag,
content,
name,
variants,
deny_unknown_fields,
state,
),
OpenRepr::Untagged => unreachable!("untagged open enums are handled above"),
}
}
pub fn deserialize_arc<'out, 'de, O: ?Sized + OpenEnum>(
out: &'out mut Option<Arc<O>>,
state: &mut State,
) -> SinkHandle<'out, 'de>
where
Box<O>: Deserialize<'de>,
{
mapped(
out,
OwnedSink::<Box<O>>::deserialize(state),
|value| Ok(Arc::from(value)),
state,
)
}
struct Unregistered<O: ?Sized>(PhantomData<fn() -> Box<O>>);
impl<O: ?Sized + OpenEnum> Unregistered<O> {
fn new() -> Self {
Unregistered(PhantomData)
}
fn error(&self) -> Error {
Error::new(
ErrorKind::Configuration,
alloc::format!(
"no variants of {} are registered (register them in a deser::OpenEnums \
that is given in the context)",
O::INFO.name
),
)
}
}
impl<'de, O: ?Sized + OpenEnum> VariantBuilder<'de, Box<O>> for Unregistered<O> {
fn sink(&mut self) -> &mut dyn Sink<'de> {
self
}
fn build(&mut self) -> Option<Box<O>> {
None
}
}
impl<'de, O: ?Sized + OpenEnum> Sink<'de> for Unregistered<O> {
fn atom(&mut self, _atom: Atom, _state: &mut State) -> Result<(), Error> {
Err(self.error())
}
fn map(&mut self, _state: &mut State) -> Result<(), Error> {
Err(self.error())
}
fn seq(&mut self, _state: &mut State) -> Result<(), Error> {
Err(self.error())
}
fn finish(&mut self, _state: &mut State) -> Result<(), Error> {
Err(self.error())
}
}