Skip to content

Commit

Permalink
Fix #166 - serde_json::Value shouldn't be any (#176)
Browse files Browse the repository at this point in the history
* wip

* cringe `skip_bigint_checks` options

* fix up yaml and toml types + `unstable_skip_bigint_checks`
  • Loading branch information
oscartbeaumont authored Nov 23, 2023
1 parent 5ba64f1 commit 27d52f9
Show file tree
Hide file tree
Showing 12 changed files with 134 additions and 70 deletions.
3 changes: 3 additions & 0 deletions macros/src/type/attr/enum.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,16 @@ pub struct EnumAttr {
pub tag: Option<String>,
pub content: Option<String>,
pub untagged: bool,
// This property is not covered by sem-ver and *should* not be used.
pub unstable_skip_bigint_checks: bool,
}

impl_parse! {
EnumAttr(attr, out) {
// "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),
}
}

Expand Down
4 changes: 3 additions & 1 deletion macros/src/type/enum.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<Self>(opts, #crate_ref::internal::construct::data_type_reference(
Expand Down
1 change: 1 addition & 0 deletions src/datatype/enum.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<GenericType>,
pub(crate) variants: Vec<(Cow<'static, str>, EnumVariant)>,
Expand Down
1 change: 1 addition & 0 deletions src/datatype/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ impl<T: Into<DataType> + 'static> From<Vec<T>> for DataType {
DataType::Enum(EnumType {
name: "Vec".into(),
repr: EnumRepr::Untagged,
skip_bigint_checks: false,
variants: t
.into_iter()
.map(|t| {
Expand Down
2 changes: 2 additions & 0 deletions src/internal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,12 +68,14 @@ pub mod construct {
pub const fn r#enum(
name: Cow<'static, str>,
repr: EnumRepr,
skip_bigint_checks: bool,
generics: Vec<GenericType>,
variants: Vec<(Cow<'static, str>, EnumVariant)>,
) -> EnumType {
EnumType {
name,
repr,
skip_bigint_checks,
generics,
variants,
}
Expand Down
18 changes: 13 additions & 5 deletions src/lang/ts/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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![
Expand Down
99 changes: 50 additions & 49 deletions src/type/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -279,21 +279,28 @@ const _: () = {

#[cfg(feature = "serde_json")]
const _: () = {
impl_for_map!(serde_json::Map<K, V> as "Map");
impl<K: Type, V: Type> Flatten for serde_json::Map<K, V> {}
use serde_json::{Map, Number, Value};

impl Type for serde_json::Value {
fn inline(_: DefOpts, _: &[DataType]) -> DataType {
DataType::Any
}
impl_for_map!(Map<K, V> as "Map");
impl<K: Type, V: Type> Flatten for Map<K, V> {}

#[derive(Type)]
#[specta(rename = "JsonValue", untagged, remote = Value, crate = crate, export = false)]
pub enum JsonValue {
Null,
Bool(bool),
Number(Number),
String(String),
Array(Vec<Value>),
Object(Map<String, Value>),
}

// 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(),
Expand Down Expand Up @@ -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<TaggedValue>),
}

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,
)))
}
}

Expand All @@ -378,6 +397,7 @@ const _: () = {
DataType::Enum(EnumType {
name: "Number".into(),
repr: EnumRepr::Untagged,
skip_bigint_checks: true,
variants: vec![
(
"f64".into(),
Expand Down Expand Up @@ -439,49 +459,29 @@ const _: () = {

#[cfg(feature = "toml")]
const _: () = {
use toml::{value::Array, value::Datetime, value::Table, Value};

impl_for_map!(toml::map::Map<K, V> as "Map");
impl<K: Type, V: Type> Flatten for toml::map::Map<K, V> {}

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<toml::value::Date>,
pub time: Option<toml::value::Time>,
pub offset: Option<toml::value::Offset>,
#[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,
}
};

Expand Down Expand Up @@ -670,6 +670,7 @@ impl<L: Type, R: Type> Type for either::Either<L, R> {
DataType::Enum(EnumType {
name: "Either".into(),
repr: EnumRepr::Untagged,
skip_bigint_checks: false,
variants: vec![
(
"Left".into(),
Expand Down
30 changes: 15 additions & 15 deletions tests/macro/compile_error.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -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<T>
serde_json::map::Map<K, V>
HashMap<K, V>
and $N others
note: required by a bound in `_::<impl specta::Type for FlattenExternal>::inline::validate_flatten`
--> tests/macro/compile_error.rs:29:10
Expand All @@ -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<T>
serde_json::map::Map<K, V>
HashMap<K, V>
and $N others
note: required by a bound in `_::<impl specta::Type for FlattenExternal>::inline::validate_flatten`
--> tests/macro/compile_error.rs:29:10
Expand All @@ -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<T>
serde_json::map::Map<K, V>
HashMap<K, V>
and $N others
note: required by a bound in `_::<impl specta::Type for FlattenUntagged>::inline::validate_flatten`
--> tests/macro/compile_error.rs:49:10
Expand All @@ -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<T>
serde_json::map::Map<K, V>
HashMap<K, V>
and $N others
note: required by a bound in `_::<impl specta::Type for FlattenUntagged>::inline::validate_flatten`
--> tests/macro/compile_error.rs:49:10
Expand All @@ -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<T>
serde_json::map::Map<K, V>
HashMap<K, V>
and $N others
note: required by a bound in `_::<impl specta::Type for FlattenInternal>::inline::validate_flatten`
--> tests/macro/compile_error.rs:67:10
Expand Down
3 changes: 3 additions & 0 deletions tests/serde/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
12 changes: 12 additions & 0 deletions tests/serde/serde_json.rs
Original file line number Diff line number Diff line change
@@ -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<String, ()>, "{ [key in string]: null }");
assert_ts!(serde_json::Number, "number");
}
16 changes: 16 additions & 0 deletions tests/serde/serde_yaml.rs
Original file line number Diff line number Diff line change
@@ -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");
}
15 changes: 15 additions & 0 deletions tests/serde/toml.rs
Original file line number Diff line number Diff line change
@@ -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<String, ()>, "{ [key in string]: null }");
assert_ts!(
toml::value::Datetime,
"{ $__toml_private_datetime: string }"
);
}

0 comments on commit 27d52f9

Please sign in to comment.