diff --git a/src/to_typescript/structs.rs b/src/to_typescript/structs.rs index fb4b2c6..081bd65 100644 --- a/src/to_typescript/structs.rs +++ b/src/to_typescript/structs.rs @@ -12,11 +12,26 @@ impl super::ToTypescript for syn::ItemStruct { let comments = utils::get_comments(self.clone().attrs); state.write_comments(&comments, 0); - state.types.push_str(&format!( - "{export}interface {interface_name}{generics} {{\n", - interface_name = self.ident, - generics = utils::extract_struct_generics(self.generics.clone()) - )); + let intersections = get_intersections(&self.fields); + + match intersections { + Some(intersections) => { + state.types.push_str(&format!( + "{export}type {struct_name}{generics} = {intersections} & {{\n", + export = export, + struct_name = self.ident, + generics = utils::extract_struct_generics(self.generics.clone()), + intersections = intersections + )); + } + None => { + state.types.push_str(&format!( + "{export}interface {interface_name}{generics} {{\n", + interface_name = self.ident, + generics = utils::extract_struct_generics(self.generics.clone()) + )); + } + } process_fields(self.fields, state, 2, casing); state.types.push('}'); @@ -33,6 +48,12 @@ pub fn process_fields( let space = utils::build_indentation(indentation_amount); let case = case.into(); for field in fields { + // Check if the field has the serde flatten attribute, if so, skip it + let has_flatten_attr = utils::get_attribute_arg("serde", "flatten", &field.attrs).is_some(); + if has_flatten_attr { + continue; + } + let comments = utils::get_comments(field.attrs); state.write_comments(&comments, 2); @@ -55,3 +76,21 @@ pub fn process_fields( )); } } + +fn get_intersections(fields: &syn::Fields) -> Option { + let mut types = Vec::new(); + + for field in fields { + let has_flatten_attr = utils::get_attribute_arg("serde", "flatten", &field.attrs).is_some(); + let field_type = convert_type(&field.ty); + if has_flatten_attr { + types.push(field_type.ts_type); + } + } + + if types.is_empty() { + return None; + } + + Some(types.join(" & ")) +} diff --git a/test/struct/rust.rs b/test/struct/rust.rs index dddb6ba..74ad206 100644 --- a/test/struct/rust.rs +++ b/test/struct/rust.rs @@ -11,6 +11,8 @@ struct Book { /// Reviews of the book /// by users. user_reviews: Option>, + #[serde(flatten)] + book_type: BookType, } #[tsync] @@ -43,7 +45,6 @@ struct PaginationResult { total_items: number, } - #[tsync] #[derive(Serialize)] #[serde(rename_all = "camelCase")] @@ -51,4 +52,31 @@ struct PaginationResult { struct PaginationResultCamel { items: Vec, total_items: number, -} \ No newline at end of file +} + +#[tsync] +#[derive(Serialize)] +/// Struct with flattened field. +struct Author { + name: String, + #[serde(flatten)] + name: AuthorName, +} + +#[tsync] +#[derive(Serialize)] +struct AuthorName { + alias: Option, + first_name: String, + last_name: String, +} + +#[tsync] +#[derive(Serialize)] +#[serde(tag = "type")] +enum BookType { + #[serde(rename = "fiction")] + Fiction { genre: String }, + #[serde(rename = "non-fiction")] + NonFiction { subject: String }, +} diff --git a/test/struct/typescript.d.ts b/test/struct/typescript.d.ts index 5d70aba..95d3139 100644 --- a/test/struct/typescript.d.ts +++ b/test/struct/typescript.d.ts @@ -1,7 +1,7 @@ /* This file is generated and managed by tsync */ /** Doc comments are preserved too! */ -interface Book { +type Book = BookType & { /** Name of the book. */ name: string; /** Chapters of the book. */ @@ -47,3 +47,27 @@ interface PaginationResultCamel { items: Array; totalItems: number; } + +/** Struct with flattened field. */ +type Author = AuthorName & { + name: string; +} + +interface AuthorName { + alias?: string; + first_name: string; + last_name: string; +} + +type BookType = + | BookType__Fiction + | BookType__NonFiction; + +type BookType__Fiction = { + type: "Fiction"; + genre: string; +}; +type BookType__NonFiction = { + type: "NonFiction"; + subject: string; +}; diff --git a/test/struct/typescript.ts b/test/struct/typescript.ts index 0093618..66edb8c 100644 --- a/test/struct/typescript.ts +++ b/test/struct/typescript.ts @@ -1,7 +1,7 @@ /* This file is generated and managed by tsync */ /** Doc comments are preserved too! */ -export interface Book { +export type Book = BookType & { /** Name of the book. */ name: string; /** Chapters of the book. */ @@ -47,3 +47,27 @@ export interface PaginationResultCamel { items: Array; totalItems: number; } + +/** Struct with flattened field. */ +export type Author = AuthorName & { + name: string; +} + +export interface AuthorName { + alias?: string; + first_name: string; + last_name: string; +} + +export type BookType = + | BookType__Fiction + | BookType__NonFiction; + +type BookType__Fiction = { + type: "Fiction"; + genre: string; +}; +type BookType__NonFiction = { + type: "NonFiction"; + subject: string; +};