Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions core/data/tests/can_handle_serde_transparent/input.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#[typeshare]
#[serde(transparent)]
pub struct Players {
players: Vec<Player>,
}

#[typeshare]
pub struct Player {
name: String,
}

#[typeshare]
#[serde(transparent)]
pub struct Pool {
cards: Cards,
#[serde(skip)]
queue: Vec<Card>,
}

#[typeshare]
pub struct Cards(Vec<Card>);

#[typeshare]
pub struct Card {
name: String,
}
16 changes: 16 additions & 0 deletions core/data/tests/can_handle_serde_transparent/output.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package proto

import "encoding/json"

type Card struct {
Name string `json:"name"`
}
type Cards []Card

type Player struct {
Name string `json:"name"`
}
type Players []Player

type Pool Cards

21 changes: 21 additions & 0 deletions core/data/tests/can_handle_serde_transparent/output.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.agilebits.onepassword

import kotlinx.serialization.Serializable
import kotlinx.serialization.SerialName

@Serializable
data class Card (
val name: String
)

typealias Cards = List<Card>

@Serializable
data class Player (
val name: String
)

typealias Players = List<Player>

typealias Pool = Cards

18 changes: 18 additions & 0 deletions core/data/tests/can_handle_serde_transparent/output.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from __future__ import annotations

from pydantic import BaseModel
from typing import List


class Card(BaseModel):
name: str

Cards = List[Card]

class Player(BaseModel):
name: str

Players = List[Player]

Pool = Cards

22 changes: 22 additions & 0 deletions core/data/tests/can_handle_serde_transparent/output.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.agilebits

package object onepassword {

type Cards = Vector[Card]

type Players = Vector[Player]

type Pool = Cards

}
package onepassword {

case class Card (
name: String
)

case class Player (
name: String
)

}
23 changes: 23 additions & 0 deletions core/data/tests/can_handle_serde_transparent/output.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import Foundation

public struct Card: Codable {
public let name: String

public init(name: String) {
self.name = name
}
}

public typealias Cards = [Card]

public struct Player: Codable {
public let name: String

public init(name: String) {
self.name = name
}
}

public typealias Players = [Player]

public typealias Pool = Cards
14 changes: 14 additions & 0 deletions core/data/tests/can_handle_serde_transparent/output.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
export interface Card {
name: string;
}

export type Cards = Card[];

export interface Player {
name: string;
}

export type Players = Player[];

export type Pool = Cards;

31 changes: 31 additions & 0 deletions core/src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,33 @@ pub(crate) fn parse_struct(
Ok(match &s.fields {
// Structs
Fields::Named(f) => {
// A struct with `#[serde(transparent)]` serializes as the value of
// its single non-skipped field, so generate a type alias to that
// field's type instead of a wrapper struct.
if serde_transparent(&s.attrs) {
let mut transparent_fields = f
.named
.iter()
.filter(|field| !is_skipped(&field.attrs, target_os));
if let (Some(field), None) = (transparent_fields.next(), transparent_fields.next())
{
let ty = if let Some(ty) = get_field_type_override(&field.attrs) {
ty.parse()?
} else {
RustType::try_from(&field.ty)?
};

return Ok(RustItem::Alias(RustTypeAlias {
id: get_ident(Some(&s.ident), &s.attrs, &None),
r#type: ty,
comments: parse_comment_attrs(&s.attrs),
generic_types,
decorators: get_decorators(&s.attrs),
is_redacted: is_redacted(&s.attrs),
}));
}
}

let fields = f
.named
.iter()
Expand Down Expand Up @@ -759,6 +786,10 @@ fn serde_flatten(attrs: &[syn::Attribute]) -> bool {
serde_attr(attrs, "flatten")
}

fn serde_transparent(attrs: &[syn::Attribute]) -> bool {
serde_attr(attrs, "transparent")
}

/// Checks the struct or enum for decorators like `#[typeshare(typescript(readonly)]`
/// Takes a slice of `syn::Attribute`, returns a `HashMap<language, BTreeSet<decorator>>`, where `language` is `SupportedLanguage`
/// and `decorator` is `FieldDecorator`. Field decorators are ordered in a `BTreeSet` for consistent code generation.
Expand Down
1 change: 1 addition & 0 deletions core/tests/snapshot_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -716,4 +716,5 @@ tests! {
];
no_mangle: [swift, kotlin, scala, typescript, go];
cfg_if_attribute_typeshare: [swift, kotlin, typescript];
can_handle_serde_transparent: [kotlin, swift, typescript, scala, go, python];
}
17 changes: 17 additions & 0 deletions docs/src/usage/annotations.md
Original file line number Diff line number Diff line change
Expand Up @@ -166,3 +166,20 @@ export interface MyStruct {
c: number;
}
```

### Transparent Structs

A struct annotated with `#[serde(transparent)]` serializes as the value of its
single non-skipped field. Typeshare mirrors this by generating a type alias to
that field's type instead of a wrapper struct. For example, this Rust type
```rust
#[typeshare]
#[serde(transparent)]
pub struct Players {
players: Vec<Player>,
}
```
becomes the following Typescript definition.
```typescript
export type Players = Player[];
```