Skip to content

Commit

Permalink
Merge pull request #301 from Raicuparta/patch-1
Browse files Browse the repository at this point in the history
Wrap maps in Partial<> in generated TypeScript types
  • Loading branch information
oscartbeaumont authored Dec 20, 2024
2 parents 65a5f27 + 31a76fc commit 373a42f
Show file tree
Hide file tree
Showing 13 changed files with 35 additions and 34 deletions.
2 changes: 1 addition & 1 deletion examples/examples/basic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,6 @@ fn main() {
println!("{ts_str}");
assert_eq!(
ts_str,
r#"export type Something = { a: { [key in MyEnum]: number } }"#.to_string()
r#"export type Something = { a: Partial<{ [key in MyEnum]: number }>"#.to_string()
);
}
7 changes: 4 additions & 3 deletions specta-typescript/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -291,8 +291,9 @@ pub(crate) fn datatype_inner(
}
}
DataType::Map(def) => {
// We use this instead of `Record<K, V>` to avoid issues with circular references.
s.push_str("{ [key in ");
// We use `{ [key in K]: V }` instead of `Record<K, V>` to avoid issues with circular references.
// Wrapped in Partial<> because otherwise TypeScript would enforce exhaustiveness.
s.push_str("Partial<{ [key in ");
datatype_inner(
ctx.clone(),
&FunctionResultVariant::Value(def.key_ty().clone()),
Expand All @@ -306,7 +307,7 @@ pub(crate) fn datatype_inner(
type_map,
s,
)?;
s.push_str(" }");
s.push_str(" }>");
}
// We use `T[]` instead of `Array<T>` to avoid issues with circular references.
DataType::List(def) => {
Expand Down
6 changes: 3 additions & 3 deletions tests/tests/advanced_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,11 @@ fn test_type_aliases() {
assert_ts!(FullGeneric<u8, bool>, "{ a: number; b: boolean }");
assert_ts!(Another<bool>, "{ a: number; b: boolean }");

assert_ts!(MapA<u32>, "{ [key in string]: number }");
assert_ts!(MapB<u32>, "{ [key in number]: string }");
assert_ts!(MapA<u32>, "Partial<{ [key in string]: number }>");
assert_ts!(MapB<u32>, "Partial<{ [key in number]: string }>");
assert_ts!(
MapC<u32>,
"{ [key in string]: { field: Demo<number, boolean> } }"
"Partial<{ [key in string]: { field: Demo<number, boolean> } }>"
);

assert_ts!(Struct<u32>, "{ field: Demo<number, boolean> }");
Expand Down
2 changes: 1 addition & 1 deletion tests/tests/flatten_and_inline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ pub enum K {
fn serde() {
assert_ts!(
B,
"({ a: string }) & ({ [key in string]: string }) & ({ a: string })"
"({ a: string }) & (Partial<{ [key in string]: string }>) & ({ a: string })"
);
assert_ts!(C, "({ a: string }) & { b: { a: string } }");
assert_ts!(D, "({ a: string }) & { b: { a: string }; type: \"D\" }");
Expand Down
22 changes: 11 additions & 11 deletions tests/tests/map_keys.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,28 +75,28 @@ pub struct InvalidMaybeValidKeyNested(HashMap<MaybeValidKey<MaybeValidKey<()>>,

#[test]
fn map_keys() {
assert_ts!(HashMap<String, ()>, "{ [key in string]: null }");
assert_ts_export!(Regular, "export type Regular = { [key in string]: null }");
assert_ts!(HashMap<Infallible, ()>, "{ [key in never]: null }");
assert_ts!(HashMap<Any, ()>, "{ [key in any]: null }");
assert_ts!(HashMap<TransparentStruct, ()>, "{ [key in string]: null }");
assert_ts!(HashMap<UnitVariants, ()>, "{ [key in \"A\" | \"B\" | \"C\"]: null }");
assert_ts!(HashMap<UntaggedVariants, ()>, "{ [key in string | number]: null }");
assert_ts!(HashMap<String, ()>, "Partial<{ [key in string]: null }>");
assert_ts_export!(Regular, "export type Regular = Partial<{ [key in string]: null }>");
assert_ts!(HashMap<Infallible, ()>, "Partial<{ [key in never]: null }>");
assert_ts!(HashMap<Any, ()>, "Partial<{ [key in any]: null }>");
assert_ts!(HashMap<TransparentStruct, ()>, "Partial<{ [key in string]: null }>");
assert_ts!(HashMap<UnitVariants, ()>, "Partial<{ [key in \"A\" | \"B\" | \"C\"]: null }>");
assert_ts!(HashMap<UntaggedVariants, ()>, "Partial<{ [key in string | number]: null }>");
assert_ts!(
ValidMaybeValidKey,
"{ [key in MaybeValidKey<string>]: null }"
"Partial<{ [key in MaybeValidKey<string>]: null }>"
);
assert_ts_export!(
ValidMaybeValidKey,
"export type ValidMaybeValidKey = { [key in MaybeValidKey<string>]: null }"
"export type ValidMaybeValidKey = Partial<{ [key in MaybeValidKey<string>]: null }>"
);
assert_ts!(
ValidMaybeValidKeyNested,
"{ [key in MaybeValidKey<MaybeValidKey<string>>]: null }"
"Partial<{ [key in MaybeValidKey<MaybeValidKey<string>>]: null }>"
);
assert_ts_export!(
ValidMaybeValidKeyNested,
"export type ValidMaybeValidKeyNested = { [key in MaybeValidKey<MaybeValidKey<string>>]: null }"
"export type ValidMaybeValidKeyNested = Partial<{ [key in MaybeValidKey<MaybeValidKey<string>>]: null }>"
);

assert_ts!(error; HashMap<() /* `null` */, ()>, SerdeError::InvalidMapKey);
Expand Down
4 changes: 2 additions & 2 deletions tests/tests/recursive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,11 @@ fn test_recursive_types() {

assert_ts!(
RecursiveMapValue,
"{ demo: { [key in string]: RecursiveMapValue } }"
"{ demo: Partial<{ [key in string]: RecursiveMapValue }> }"
);
assert_ts_export!(
RecursiveMapValue,
"export type RecursiveMapValue = { demo: { [key in string]: RecursiveMapValue } }"
"export type RecursiveMapValue = { demo: Partial<{ [key in string]: RecursiveMapValue }> }"
);
}

Expand Down
2 changes: 1 addition & 1 deletion tests/tests/serde/internally_tagged.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ fn internally_tagged() {
assert_ts!(error; A, SerdeError::InvalidInternallyTaggedEnum);
assert_ts!(error; B, SerdeError::InvalidInternallyTaggedEnum);
assert_ts!(error; C, SerdeError::InvalidInternallyTaggedEnum);
assert_ts!(D, "({ type: \"A\" } & { [key in string]: string })");
assert_ts!(D, "({ type: \"A\" } & Partial<{ [key in string]: string }>)");
assert_ts!(E, "({ type: \"A\" })");
assert_ts!(F, "({ type: \"A\" } & FInner)");
assert_ts!(error; G, SerdeError::InvalidInternallyTaggedEnum);
Expand Down
4 changes: 2 additions & 2 deletions tests/tests/serde/serde_json.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ fn serde_json() {

assert_ts!(
serde_json::Value,
"null | boolean | number | string | JsonValue[] | { [key in string]: JsonValue }"
"null | boolean | number | string | JsonValue[] | Partial<{ [key in string]: JsonValue }>"
);
assert_ts!(serde_json::Map<String, ()>, "{ [key in string]: null }");
assert_ts!(serde_json::Map<String, ()>, "Partial<{ [key in string]: null }>");
assert_ts!(serde_json::Number, "number");
}
4 changes: 2 additions & 2 deletions tests/tests/serde/serde_yaml.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@ fn serde_yaml() {

assert_ts!(
serde_yaml::Value,
"null | boolean | number | string | YamlValue[] | unknown | { [key in string]: unknown }"
"null | boolean | number | string | YamlValue[] | unknown | Partial<{ [key in string]: unknown }>"
);
assert_ts!(serde_yaml::Mapping, "unknown");
assert_ts!(
serde_yaml::value::TaggedValue,
"{ [key in string]: unknown }"
"Partial<{ [key in string]: unknown }>"
);
assert_ts!(serde_yaml::Number, "number");
}
4 changes: 2 additions & 2 deletions tests/tests/serde/toml.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ fn toml() {

assert_ts!(
toml::Value,
"string | number | boolean | Datetime | TomlValue[] | { [key in string]: TomlValue }"
"string | number | boolean | Datetime | TomlValue[] | Partial<{ [key in string]: TomlValue }>"
);
assert_ts!(toml::map::Map<String, ()>, "{ [key in string]: null }");
assert_ts!(toml::map::Map<String, ()>, "Partial<{ [key in string]: null }>");
assert_ts!(
toml::value::Datetime,
"{ $__toml_private_datetime: string }"
Expand Down
8 changes: 4 additions & 4 deletions tests/tests/ts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ fn typescript_types() {
);

assert_ts!(OverridenStruct, "{ overriden_field: string }");
assert_ts!(HasGenericAlias, r#"{ [key in number]: string }"#);
assert_ts!(HasGenericAlias, r#"Partial<{ [key in number]: string }>"#);

assert_ts!(SkipVariant, "{ A: string }");
assert_ts!(SkipVariant2, r#"{ tag: "A"; data: string }"#);
Expand Down Expand Up @@ -191,7 +191,7 @@ fn typescript_types() {
);

// https://github.com/oscartbeaumont/specta/issues/65
assert_ts!(HashMap<BasicEnum, ()>, r#"{ [key in "A" | "B"]: null }"#);
assert_ts!(HashMap<BasicEnum, ()>, r#"Partial<{ [key in "A" | "B"]: null }>"#);

// https://github.com/oscartbeaumont/specta/issues/60
assert_ts!(Option<Option<Option<Option<i32>>>>, r#"number | null"#);
Expand Down Expand Up @@ -226,10 +226,10 @@ fn typescript_types() {
Ok(r#"{ secs: string; nanos: number }"#.into())
);

assert_ts!(HashMap<BasicEnum, i32>, r#"{ [key in "A" | "B"]: number }"#);
assert_ts!(HashMap<BasicEnum, i32>, r#"Partial<{ [key in "A" | "B"]: number }>"#);
assert_ts_export!(
EnumReferenceRecordKey,
"export type EnumReferenceRecordKey = { a: { [key in BasicEnum]: number } }"
"export type EnumReferenceRecordKey = { a: Partial<{ [key in BasicEnum]: number }> }"
);

assert_ts!(
Expand Down
2 changes: 1 addition & 1 deletion tests/tests/ts_rs/generics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ fn test() {

assert_ts_export!(
Container1,
"export type Container1 = { foo: Generic1<number>; bar: Generic1<number>[]; baz: { [key in string]: Generic1<string> } }"
"export type Container1 = { foo: Generic1<number>; bar: Generic1<number>[]; baz: Partial<{ [key in string]: Generic1<string> }> }"
);
}

Expand Down
2 changes: 1 addition & 1 deletion tests/tests/ts_rs/indexmap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,6 @@ fn indexmap() {

assert_ts!(
Indexes,
"{ map: { [key in string]: string }; indexset: string[] }"
"{ map: Partial<{ [key in string]: string }>; indexset: string[] }"
);
}

0 comments on commit 373a42f

Please sign in to comment.