diff --git a/macros/src/type/attr/enum.rs b/macros/src/type/attr/enum.rs index 295d09d..f73ad0c 100644 --- a/macros/src/type/attr/enum.rs +++ b/macros/src/type/attr/enum.rs @@ -18,6 +18,8 @@ pub struct EnumAttr { pub tag: Option, pub content: Option, pub untagged: bool, + // This property is not covered by sem-ver and *should* not be used. + pub unstable_skip_bigint_checks: bool, } impl_parse! { @@ -25,6 +27,7 @@ impl_parse! { // "tag" was already passed in the container so we don't need to do anything here "content" => out.content = out.content.take().or(Some(attr.parse_string()?)), "untagged" => out.untagged = attr.parse_bool().unwrap_or(true), + "unstable_skip_bigint_checks" => out.unstable_skip_bigint_checks = attr.parse_bool().unwrap_or(true), } } diff --git a/macros/src/type/enum.rs b/macros/src/type/enum.rs index 7bed54d..815152b 100644 --- a/macros/src/type/enum.rs +++ b/macros/src/type/enum.rs @@ -240,8 +240,10 @@ pub fn parse_enum( ), }; + let skip_bigint_checs = enum_attrs.unstable_skip_bigint_checks; + Ok(( - quote!(#crate_ref::DataType::Enum(#crate_ref::internal::construct::r#enum(#name.into(), #repr, vec![#(#definition_generics),*], vec![#(#variant_types),*]))), + quote!(#crate_ref::DataType::Enum(#crate_ref::internal::construct::r#enum(#name.into(), #repr, #skip_bigint_checs, vec![#(#definition_generics),*], vec![#(#variant_types),*]))), quote!({ let generics = vec![#(#reference_generics),*]; #crate_ref::reference::reference::(opts, #crate_ref::internal::construct::data_type_reference( diff --git a/src/datatype/enum.rs b/src/datatype/enum.rs index b668280..ce2c80f 100644 --- a/src/datatype/enum.rs +++ b/src/datatype/enum.rs @@ -13,6 +13,7 @@ use crate::{ #[derive(Debug, Clone, PartialEq)] pub struct EnumType { pub(crate) name: Cow<'static, str>, + pub(crate) skip_bigint_checks: bool, pub(crate) repr: EnumRepr, pub(crate) generics: Vec, pub(crate) variants: Vec<(Cow<'static, str>, EnumVariant)>, diff --git a/src/datatype/mod.rs b/src/datatype/mod.rs index 3e36c11..0165fe4 100644 --- a/src/datatype/mod.rs +++ b/src/datatype/mod.rs @@ -162,6 +162,7 @@ impl + 'static> From> for DataType { DataType::Enum(EnumType { name: "Vec".into(), repr: EnumRepr::Untagged, + skip_bigint_checks: false, variants: t .into_iter() .map(|t| { diff --git a/src/internal.rs b/src/internal.rs index e5f5c2d..6093eea 100644 --- a/src/internal.rs +++ b/src/internal.rs @@ -68,12 +68,14 @@ pub mod construct { pub const fn r#enum( name: Cow<'static, str>, repr: EnumRepr, + skip_bigint_checks: bool, generics: Vec, variants: Vec<(Cow<'static, str>, EnumVariant)>, ) -> EnumType { EnumType { name, repr, + skip_bigint_checks, generics, variants, } diff --git a/src/lang/ts/mod.rs b/src/lang/ts/mod.rs index 471e887..cd0f395 100644 --- a/src/lang/ts/mod.rs +++ b/src/lang/ts/mod.rs @@ -246,11 +246,19 @@ pub(crate) fn datatype_inner(ctx: ExportContext, typ: &DataType, type_map: &Type item, type_map, )?, - DataType::Enum(item) => enum_datatype( - ctx.with(PathItem::Variant(item.name.clone())), - item, - type_map, - )?, + DataType::Enum(item) => { + let mut ctx = ctx.clone(); + let cfg = ctx.cfg.clone().bigint(BigIntExportBehavior::Number); + if item.skip_bigint_checks { + ctx.cfg = &cfg; + } + + enum_datatype( + ctx.with(PathItem::Variant(item.name.clone())), + item, + type_map, + )? + } DataType::Tuple(tuple) => tuple_datatype(ctx, tuple, type_map)?, DataType::Result(result) => { let mut variants = vec![ diff --git a/src/type/impls.rs b/src/type/impls.rs index 6165014..08105e4 100644 --- a/src/type/impls.rs +++ b/src/type/impls.rs @@ -279,21 +279,28 @@ const _: () = { #[cfg(feature = "serde_json")] const _: () = { - impl_for_map!(serde_json::Map as "Map"); - impl Flatten for serde_json::Map {} + use serde_json::{Map, Number, Value}; - impl Type for serde_json::Value { - fn inline(_: DefOpts, _: &[DataType]) -> DataType { - DataType::Any - } + impl_for_map!(Map as "Map"); + impl Flatten for Map {} + + #[derive(Type)] + #[specta(rename = "JsonValue", untagged, remote = Value, crate = crate, export = false)] + pub enum JsonValue { + Null, + Bool(bool), + Number(Number), + String(String), + Array(Vec), + Object(Map), } - // TODO: Using remote impl - impl Type for serde_json::Number { + impl Type for Number { fn inline(_: DefOpts, _: &[DataType]) -> DataType { DataType::Enum(EnumType { name: "Number".into(), repr: EnumRepr::Untagged, + skip_bigint_checks: true, variants: vec![ ( "f64".into(), @@ -355,21 +362,33 @@ const _: () = { #[cfg(feature = "serde_yaml")] const _: () = { - impl Type for serde_yaml::Value { - fn inline(_: DefOpts, _: &[DataType]) -> DataType { - DataType::Any - } + use serde_yaml::{value::TaggedValue, Mapping, Number, Sequence, Value}; + + #[derive(Type)] + #[specta(rename = "YamlValue", untagged, remote = Value, crate = crate, export = false)] + pub enum YamlValue { + Null, + Bool(bool), + Number(Number), + String(String), + Sequence(Sequence), + Mapping(Mapping), + Tagged(Box), } impl Type for serde_yaml::Mapping { fn inline(_: DefOpts, _: &[DataType]) -> DataType { - DataType::Any + // We don't type this more accurately because `serde_json` doesn't allow non-string map keys so neither does Specta + DataType::Unknown } } impl Type for serde_yaml::value::TaggedValue { fn inline(_: DefOpts, _: &[DataType]) -> DataType { - DataType::Any + DataType::Map(Box::new(( + DataType::Primitive(PrimitiveType::String), + DataType::Unknown, + ))) } } @@ -378,6 +397,7 @@ const _: () = { DataType::Enum(EnumType { name: "Number".into(), repr: EnumRepr::Untagged, + skip_bigint_checks: true, variants: vec![ ( "f64".into(), @@ -439,49 +459,29 @@ const _: () = { #[cfg(feature = "toml")] const _: () = { + use toml::{value::Array, value::Datetime, value::Table, Value}; + impl_for_map!(toml::map::Map as "Map"); impl Flatten for toml::map::Map {} - impl Type for toml::Value { - fn inline(_: DefOpts, _: &[DataType]) -> DataType { - DataType::Any - } - } - - #[derive(Type)] - #[specta(remote = toml::value::Date, crate = crate, export = false)] - #[allow(dead_code)] - struct Date { - year: u16, - month: u8, - day: u8, - } - #[derive(Type)] - #[specta(remote = toml::value::Time, crate = crate, export = false)] - #[allow(dead_code)] - struct Time { - hour: u8, - minute: u8, - second: u8, - nanosecond: u32, - } - - #[derive(Type)] - #[specta(remote = toml::value::Datetime, crate = crate, export = false)] - #[allow(dead_code)] - struct Datetime { - pub date: Option, - pub time: Option, - pub offset: Option, + #[specta(rename = "TomlValue", untagged, remote = Value, crate = crate, export = false, unstable_skip_bigint_checks)] + pub enum TomlValue { + String(String), + Integer(i64), + Float(f64), + Boolean(bool), + Datetime(Datetime), + Array(Array), + Table(Table), } #[derive(Type)] - #[specta(remote = toml::value::Offset, crate = crate, export = false)] + #[specta(rename = "Datetime", remote = Datetime, crate = crate, export = false)] #[allow(dead_code)] - pub enum Offset { - Z, - Custom { minutes: i16 }, + struct DatetimeDef { + #[specta(rename = "$__toml_private_datetime")] + pub v: String, } }; @@ -670,6 +670,7 @@ impl Type for either::Either { DataType::Enum(EnumType { name: "Either".into(), repr: EnumRepr::Untagged, + skip_bigint_checks: false, variants: vec![ ( "Left".into(), diff --git a/tests/macro/compile_error.stderr b/tests/macro/compile_error.stderr index ae76151..c84438a 100644 --- a/tests/macro/compile_error.stderr +++ b/tests/macro/compile_error.stderr @@ -69,10 +69,10 @@ error[E0277]: the trait bound `UnitExternal: specta::Flatten` is not satisfied FlattenExternal toml_datetime::datetime::Datetime FlattenUntagged - toml_datetime::datetime::Date - toml_datetime::datetime::Time FlattenInternal - toml_datetime::datetime::Offset + Box + serde_json::map::Map + HashMap and $N others note: required by a bound in `_::::inline::validate_flatten` --> tests/macro/compile_error.rs:29:10 @@ -92,10 +92,10 @@ error[E0277]: the trait bound `UnnamedMultiExternal: specta::Flatten` is not sat FlattenExternal toml_datetime::datetime::Datetime FlattenUntagged - toml_datetime::datetime::Date - toml_datetime::datetime::Time FlattenInternal - toml_datetime::datetime::Offset + Box + serde_json::map::Map + HashMap and $N others note: required by a bound in `_::::inline::validate_flatten` --> tests/macro/compile_error.rs:29:10 @@ -115,10 +115,10 @@ error[E0277]: the trait bound `UnnamedUntagged: specta::Flatten` is not satisfie FlattenExternal toml_datetime::datetime::Datetime FlattenUntagged - toml_datetime::datetime::Date - toml_datetime::datetime::Time FlattenInternal - toml_datetime::datetime::Offset + Box + serde_json::map::Map + HashMap and $N others note: required by a bound in `_::::inline::validate_flatten` --> tests/macro/compile_error.rs:49:10 @@ -138,10 +138,10 @@ error[E0277]: the trait bound `UnnamedMultiUntagged: specta::Flatten` is not sat FlattenExternal toml_datetime::datetime::Datetime FlattenUntagged - toml_datetime::datetime::Date - toml_datetime::datetime::Time FlattenInternal - toml_datetime::datetime::Offset + Box + serde_json::map::Map + HashMap and $N others note: required by a bound in `_::::inline::validate_flatten` --> tests/macro/compile_error.rs:49:10 @@ -161,10 +161,10 @@ error[E0277]: the trait bound `UnnamedInternal: specta::Flatten` is not satisfie FlattenExternal toml_datetime::datetime::Datetime FlattenUntagged - toml_datetime::datetime::Date - toml_datetime::datetime::Time FlattenInternal - toml_datetime::datetime::Offset + Box + serde_json::map::Map + HashMap and $N others note: required by a bound in `_::::inline::validate_flatten` --> tests/macro/compile_error.rs:67:10 diff --git a/tests/serde/mod.rs b/tests/serde/mod.rs index 469bfec..dd9224b 100644 --- a/tests/serde/mod.rs +++ b/tests/serde/mod.rs @@ -3,5 +3,8 @@ mod empty_enum; mod empty_struct; mod externally_tagged; mod internally_tagged; +mod serde_json; +mod serde_yaml; mod skip; +mod toml; mod untagged; diff --git a/tests/serde/serde_json.rs b/tests/serde/serde_json.rs new file mode 100644 index 0000000..7b96168 --- /dev/null +++ b/tests/serde/serde_json.rs @@ -0,0 +1,12 @@ +#[cfg(feature = "serde_json")] +#[test] +fn serde_json() { + use crate::ts::assert_ts; + + assert_ts!( + serde_json::Value, + "null | boolean | number | string | JsonValue[] | { [key in string]: JsonValue }" + ); + assert_ts!(serde_json::Map, "{ [key in string]: null }"); + assert_ts!(serde_json::Number, "number"); +} diff --git a/tests/serde/serde_yaml.rs b/tests/serde/serde_yaml.rs new file mode 100644 index 0000000..20f437a --- /dev/null +++ b/tests/serde/serde_yaml.rs @@ -0,0 +1,16 @@ +#[cfg(feature = "serde_yaml")] +#[test] +fn serde_yaml() { + use crate::ts::assert_ts; + + assert_ts!( + serde_yaml::Value, + "null | boolean | number | string | YamlValue[] | unknown | { [key in string]: unknown }" + ); + assert_ts!(serde_yaml::Mapping, "unknown"); + assert_ts!( + serde_yaml::value::TaggedValue, + "{ [key in string]: unknown }" + ); + assert_ts!(serde_yaml::Number, "number"); +} diff --git a/tests/serde/toml.rs b/tests/serde/toml.rs new file mode 100644 index 0000000..45aaa59 --- /dev/null +++ b/tests/serde/toml.rs @@ -0,0 +1,15 @@ +#[cfg(feature = "toml")] +#[test] +fn toml() { + use crate::ts::assert_ts; + + assert_ts!( + toml::Value, + "string | number | boolean | Datetime | TomlValue[] | { [key in string]: TomlValue }" + ); + assert_ts!(toml::map::Map, "{ [key in string]: null }"); + assert_ts!( + toml::value::Datetime, + "{ $__toml_private_datetime: string }" + ); +}