Repository: Aleph-Alpha/ts-rs (Rust)

Issue:
bug: `#[serde(flatten)]` is not working propertly on fields of type `HashMap`
**Describe the bug**
HashMaps should be flattenable

**To Reproduce**
```rs
#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema, TS)]
pub struct ThirdPartyWidgetSettings {
    /// enable or disable the widget
    pub enabled: bool,
    #[serde(flatten)]
    pub rest: HashMap<String, serde_json::Value>,
}
```

**Expected behavior**
```ts
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { JsonValue } from './serde_json/JsonValue.ts';

export type ThirdPartyWidgetSettings = {
  /**
   * enable or disable the widget
   */
  enabled: boolean;
} & ({ [key in string]?: JsonValue });
```

**Screenshots**
```txt
test handlers::events::export_bindings_seeleneventpayload ... FAILED

failures:

---- handlers::commands::export_bindings_seelencommandreturn stdout ----

thread 'handlers::commands::export_bindings_seelencommandreturn' panicked at C:\Users\dlmqc\.cargo\git\checkouts\ts-rs-4e1afb5ed402ab6a\30ba9d1\ts-rs\src\lib.rs:944:9:
{ [key in string]?: string } cannot be flattened
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

---- state::settings::export_bindings_settings stdout ----

thread 'state::settings::export_bindings_settings' panicked at C:\Users\dlmqc\.cargo\git\checkouts\ts-rs-4e1afb5ed402ab6a\30ba9d1\ts-rs\src\lib.rs:944:9:   
{ [key in string]?: string } cannot be flattened

---- handlers::commands::export_bindings_seelencommandargument stdout ----

thread 'handlers::commands::export_bindings_seelencommandargument' panicked at C:\Users\dlmqc\.cargo\git\checkouts\ts-rs-4e1afb5ed402ab6a\30ba9d1\ts-rs\src\lib.rs:944:9:
{ [key in string]?: string } cannot be flattened

---- handlers::events::export_bindings_seeleneventpayload stdout ----

thread 'handlers::events::export_bindings_seeleneventpayload' panicked at C:\Users\dlmqc\.cargo\git\checkouts\ts-rs-4e1afb5ed402ab6a\30ba9d1\ts-rs\src\lib.rs:944:9:
{ [key in string]?: string } cannot be flattened


failures:
    handlers::commands::export_bindings_seelencommandargument
    handlers::commands::export_bindings_seelencommandreturn
    handlers::events::export_bindings_seeleneventpayload
    state::settings::export_bindings_settings
```

**Version**
latest

**Additional context**
As a workaround I'm using a enum as wrapper in meantime this is fixed
```rs
use schemars::JsonSchema;

/// This struct is intented to work with ts-rs flattening
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, TS)]
#[serde(untagged)]
pub enum Flatenable<T> {
    Inner(T),
}

impl<T: Default> Default for Flatenable<T> {
    fn default() -> Self {
        Flatenable::Inner(T::default())
    }
}

impl<T> std::ops::Deref for Flatenable<T> {
    type Target = T;

    fn deref(&self) -> &Self::Target {
        match self {
            Flatenable::Inner(inner) => inner,
        }
    }
}

impl<T> std::ops::DerefMut for Flatenable<T> {
    fn deref_mut(&mut self) -> &mut Self::Target {
        match self {
            Flatenable::Inner(inner) => inner,
        }
    }
}
```
Now this works fine:
```rs
#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema, TS)]
pub struct ThirdPartyWidgetSettings {
    /// enable or disable the widget
    pub enabled: bool,
    #[serde(flatten)]
    pub rest: Flatenable<HashMap<String, serde_json::Value>>,
}
```



Discussion:
It looks like our `impl TS for HashMap` doesn't have an implementation for `inline_flattened`, I'll make a PR to fix it



Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c00a8079..475472b6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -18,6 +18,7 @@
 ### Fixes
 - Fix `#[ts(optional)]` error when using a type alias for `Option` or fully qualifying it as `core::option::Option` ([#366](https://github.com/Aleph-Alpha/ts-rs/pull/366))
 - Fix missing import statements when using `#[ts(as = "...")]` at the top level of a struct/enum ([#385](https://github.com/Aleph-Alpha/ts-rs/pull/385))
+- Fix missing `inline_flattened` implementation for `HashMap`
 
 # 10.1.0
 ### Features
diff --git a/ts-rs/src/lib.rs b/ts-rs/src/lib.rs
index f0e4af2d..754100c2 100644
--- a/ts-rs/src/lib.rs
+++ b/ts-rs/src/lib.rs
@@ -1002,7 +1002,11 @@ impl<K: TS, V: TS, H> TS for HashMap<K, V, H> {
     }
 
     fn inline_flattened() -> String {
-        panic!("{} cannot be flattened", <Self as crate::TS>::name())
+        format!(
+            "({{ [key in {}]?: {} }})",
+            <K as crate::TS>::inline(),
+            <V as crate::TS>::inline()
+        )
     }
 }
 


Test patch:
diff --git a/ts-rs/tests/integration/flatten.rs b/ts-rs/tests/integration/flatten.rs
index e881678a..01b8c66f 100644
--- a/ts-rs/tests/integration/flatten.rs
+++ b/ts-rs/tests/integration/flatten.rs
@@ -1,5 +1,7 @@
 #![allow(dead_code)]
 
+use std::collections::HashMap;
+
 use ts_rs::TS;
 
 #[derive(TS)]
@@ -7,6 +9,8 @@ use ts_rs::TS;
 struct A {
     a: i32,
     b: i32,
+    #[ts(flatten)]
+    c: HashMap<String, i32>,
 }
 
 #[derive(TS)]
@@ -29,6 +33,6 @@ struct C {
 fn test_def() {
     assert_eq!(
         C::inline(),
-        "{ b: { c: number, a: number, b: number, }, d: number, }"
+        "{ b: { c: number, a: number, b: number, } & ({ [key in string]?: number }), d: number, }"
     );
 }