Skip to content
Closed
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
2 changes: 1 addition & 1 deletion .clippy.toml
Original file line number Diff line number Diff line change
@@ -1 +1 @@
msrv = "1.57.0"
msrv = "1.75.0"
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
[workspace]
resolver = "2"
members = [
"annotation",
"cli",
Expand Down
41 changes: 18 additions & 23 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,4 @@
# Typeshare

| Crate | Status |
|----------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| typeshare | [![crates.io version](https://img.shields.io/crates/v/typeshare.svg)](https://crates.io/crates/typeshare) [![crate documentation](https://docs.rs/typeshare/badge.svg)](https://docs.rs/typeshare) ![MSRV](https://img.shields.io/badge/rustc-1.57+-blue.svg) [![crates.io downloads](https://img.shields.io/crates/d/typeshare.svg)](https://crates.io/crates/typeshare) |
| typeshare-core | [![crates.io version](https://img.shields.io/crates/v/typeshare-core.svg)](https://crates.io/crates/typeshare-core) [![crate documentation](https://docs.rs/typeshare-core/badge.svg)](https://docs.rs/typeshare-core) ![MSRV](https://img.shields.io/badge/rustc-1.57+-blue.svg) [![crates.io downloads](https://img.shields.io/crates/d/typeshare-core.svg)](https://crates.io/crates/typeshare-core) |
| typeshare-annotation | [![crates.io version](https://img.shields.io/crates/v/typeshare-annotation.svg)](https://crates.io/crates/typeshare-annotation) [![crate documentation](https://docs.rs/typeshare-annotation/badge.svg)](https://docs.rs/typeshare-annotation) ![MSRV](https://img.shields.io/badge/rustc-1.57+-blue.svg) [![crates.io downloads](https://img.shields.io/crates/d/typeshare-annotation.svg)](https://crates.io/crates/typeshare-annotation) |
| typeshare-cli | [![crates.io version](https://img.shields.io/crates/v/typeshare-cli.svg)](https://crates.io/crates/typeshare-cli) ![MSRV](https://img.shields.io/badge/rustc-1.57+-blue.svg) [![crates.io downloads](https://img.shields.io/crates/d/typeshare-cli.svg)](https://crates.io/crates/typeshare-cli) |

# Typeshare (Kindness fork)

_One tool to rule the types,_

Expand All @@ -16,17 +8,16 @@ _One tool to parse your Rust,_

_And in the darkness, compile them_ 💍


Do you like manually managing types that need to be passed through an FFI layer, so that your code doesn't archaically break at runtime? Be honest, nobody does. Typeshare is here to take that burden away from you! Leveraging the power of the `serde` library, Typeshare is a tool that converts your
Rust types into their equivalent forms in Swift, Go**, Kotlin, Scala and Typescript, keeping
Rust types into their equivalent forms in Swift, Go\*\*, Kotlin, Scala and Typescript, keeping
your cross-language codebase in sync. With automatic implementation for serialization and deserialization on both sides of the FFI, Typeshare does all the heavy lifting for you. It can even handle generics and convert effortlessly between standard libraries in different languages!

**A few caveats. See [here](#a-quick-refresher-on-supported-languages) for more details.

## Installation
\*\*A few caveats. See [here](#a-quick-refresher-on-supported-languages) for more details.

## Installation

To install the CLI (Command Line Interface):

```
cargo install typeshare-cli
```
Expand All @@ -40,11 +31,13 @@ typeshare = "1.0.0"
```

## Using Typeshare

We've put together a book that documents (almost) everything you can do.

📚[Read the Typeshare book here!](https://1password.github.io/typeshare)

To generate FFI definitions for a target language, run the `typeshare` command and specify the directory containing your rust code, the language you would like to generate for, and the file to which your generated definitions will be written:

```
typeshare ./my_rust_project --lang=kotlin --output-file=my_kotlin_definitions.kt
typeshare ./my_rust_project --lang=swift --output-file=my_swift_definitions.swift
Expand Down Expand Up @@ -73,18 +66,19 @@ enum MyEnum {
MyNumber(u32),
}
```

```typescript
// Generated Typescript definitions

export interface MyStruct {
my_name: string;
my_age: number;
my_name: string;
my_age: number;
}

export type MyEnum =
| { type: "MyVariant", content: boolean }
| { type: "MyOtherVariant", content: undefined }
| { type: "MyNumber", content: number };
export type MyEnum =
| { type: "MyVariant"; content: boolean }
| { type: "MyOtherVariant"; content: undefined }
| { type: "MyNumber"; content: number };
```

## Getting Help
Expand All @@ -97,13 +91,14 @@ Are you getting weird deserialization issues? Did our procedural macro throw a c
- Scala
- Swift
- Typescript
- Go**
- Go\*\*

If there is a language that you want Typeshare to generate definitions for, you can either:

1. Open an issue in this repository requesting your language of choice.
2. Implement support for that language and open a PR with your implementation. We would be eternally grateful! 🙏

** Right now, Go support is experimental. Enable the `go` feature when installing typeshare-cli if you want to use it.
\*\* Right now, Go support is experimental. Enable the `go` feature when installing typeshare-cli if you want to use it.

## Credits

Expand All @@ -113,7 +108,7 @@ Made with ❤️ and ☕ by the [1Password](https://1password.com/) team.

Does your team need a secure way to manage passwords and other credentials for your open source project? Head on over to our [other repository](https://github.com/1Password/1password-teams-open-source) to get a 1Password Teams account on us:

✨[1Password for Open Source Projects](https://github.com/1Password/1password-teams-open-source)
✨[1Password for Open Source Projects](https://github.com/1Password/1password-teams-open-source)

#### License

Expand Down
6 changes: 3 additions & 3 deletions cli/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "typeshare-cli"
version = "1.7.0"
rust-version = "1.57"
version = "1.7.0-kindness.1"
rust-version = "1.75"
edition = "2021"
description = "Command Line Tool for generating language files with typeshare"
license = "MIT OR Apache-2.0"
Expand All @@ -23,4 +23,4 @@ once_cell = "1"
rayon = "1.5"
serde = { version = "1.0", features = ["derive"] }
toml = "0.5"
typeshare-core = { path = "../core", version = "1.7.0" }
typeshare-core = { path = "../core", version = "1.7.0-kindness.1" }
2 changes: 1 addition & 1 deletion cli/rust-toolchain.toml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
[toolchain]
channel = "1.57"
channel = "1.75"
components = ["clippy"]
profile = "default"
3 changes: 2 additions & 1 deletion cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,7 @@ fn main() {
.filter_map(|dir_entry| dir_entry.path().to_str().map(String::from))
.collect();

let supports_flatten = lang.supports_flatten();
let mut generated_contents = vec![];
let parsed_data = glob_paths
.par_iter()
Expand All @@ -268,7 +269,7 @@ fn main() {
e = e
)
});
let parsed_data = typeshare_core::parser::parse(&data);
let parsed_data = typeshare_core::parser::parse(&data, supports_flatten);
if parsed_data.is_err() {
panic!("{}", parsed_data.err().unwrap());
}
Expand Down
4 changes: 2 additions & 2 deletions core/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "typeshare-core"
version = "1.7.0"
rust-version = "1.57"
version = "1.7.0-kindness.1"
rust-version = "1.75"
license = "MIT OR Apache-2.0"
edition = "2021"
description = "The code generator used by Typeshare's command line tool"
Expand Down
2 changes: 1 addition & 1 deletion core/rust-toolchain.toml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
[toolchain]
channel = "1.57"
channel = "1.75"
components = ["clippy"]
profile = "default"
4 changes: 4 additions & 0 deletions core/src/language/go.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,10 @@ impl Language for Go {

writeln!(w, "}}")
}

fn supports_flatten(&self) -> bool {
false
}
}

impl Go {
Expand Down
4 changes: 4 additions & 0 deletions core/src/language/kotlin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,10 @@ impl Language for Kotlin {

writeln!(w, "}}\n")
}

fn supports_flatten(&self) -> bool {
false
}
}

impl Kotlin {
Expand Down
3 changes: 3 additions & 0 deletions core/src/language/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -302,4 +302,7 @@ pub trait Language {

Ok(())
}

/// whether `#[serde(flatten)]` macro attribute is supported or not
fn supports_flatten(&self) -> bool;
}
4 changes: 4 additions & 0 deletions core/src/language/scala.rs
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,10 @@ impl Language for Scala {
self.write_enum_variants(w, e)?;
writeln!(w, "}}\n")
}

fn supports_flatten(&self) -> bool {
false
}
}

impl Scala {
Expand Down
4 changes: 4 additions & 0 deletions core/src/language/swift.rs
Original file line number Diff line number Diff line change
Expand Up @@ -533,6 +533,10 @@ impl Language for Swift {

writeln!(w, "}}")
}

fn supports_flatten(&self) -> bool {
false
}
}

impl Swift {
Expand Down
30 changes: 28 additions & 2 deletions core/src/language/typescript.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,13 +115,32 @@ impl Language for TypeScript {

fn write_struct(&mut self, w: &mut dyn Write, rs: &RustStruct) -> io::Result<()> {
self.write_comments(w, 0, &rs.comments)?;
let mut inheritance = "".to_string();
let mut count = 0;
for field in rs.fields.iter() {
if field.flattened {
let ts_ty = self
.format_type(&field.ty, &rs.generic_types)
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidInput, e))?;
if count >= 1 {
inheritance.push_str(", ");
}
inheritance.push_str(ts_ty.as_str());
count += 1;
}
}
writeln!(
w,
"export interface {}{} {{",
"export interface {}{}{} {{",
rs.id.renamed,
(!rs.generic_types.is_empty())
.then(|| format!("<{}>", rs.generic_types.join(", ")))
.unwrap_or_default()
.unwrap_or_default(),
if !inheritance.is_empty() {
format!(" extends {}", inheritance)
} else {
"".to_string()
}
)?;

rs.fields
Expand Down Expand Up @@ -165,6 +184,10 @@ impl Language for TypeScript {
}
}
}

fn supports_flatten(&self) -> bool {
true
}
}

impl TypeScript {
Expand Down Expand Up @@ -235,6 +258,9 @@ impl TypeScript {
field: &RustField,
generic_types: &[String],
) -> io::Result<()> {
if field.flattened {
return Ok(());
}
self.write_comments(w, 1, &field.comments)?;
let ts_ty: String = match field.type_override(SupportedLanguage::TypeScript) {
Some(type_override) => type_override.to_owned(),
Expand Down
2 changes: 1 addition & 1 deletion core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ pub fn process_input(
language: &mut dyn Language,
out: &mut dyn Write,
) -> Result<(), ProcessInputError> {
let parsed_data = parser::parse(input)?;
let parsed_data = parser::parse(input, language.supports_flatten())?;
language.generate_types(out, &parsed_data)?;
Ok(())
}
20 changes: 14 additions & 6 deletions core/src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ pub enum ParseError {
}

/// Parse the given Rust source string into `ParsedData`.
pub fn parse(input: &str) -> Result<ParsedData, ParseError> {
pub fn parse(input: &str, supports_flatten: bool) -> Result<ParsedData, ParseError> {
let mut parsed_data = ParsedData::default();

// We will only produce output for files that contain the `#[typeshare]`
Expand All @@ -96,7 +96,7 @@ pub fn parse(input: &str) -> Result<ParsedData, ParseError> {
for item in flatten_items(source.items.iter()) {
match item {
syn::Item::Struct(s) if has_typeshare_annotation(&s.attrs) => {
parsed_data.push_rust_thing(parse_struct(s)?);
parsed_data.push_rust_thing(parse_struct(s, supports_flatten)?);
}
syn::Item::Enum(e) if has_typeshare_annotation(&e.attrs) => {
parsed_data.push_rust_thing(parse_enum(e)?);
Expand Down Expand Up @@ -133,7 +133,7 @@ fn flatten_items<'a>(
///
/// This function can currently return something other than a struct, which is a
/// hack.
fn parse_struct(s: &ItemStruct) -> Result<RustItem, ParseError> {
fn parse_struct(s: &ItemStruct, supports_flatten: bool) -> Result<RustItem, ParseError> {
let serde_rename_all = serde_rename_all(&s.attrs);

let generic_types = s
Expand All @@ -158,6 +158,7 @@ fn parse_struct(s: &ItemStruct) -> Result<RustItem, ParseError> {
}));
}

let mut flattened: bool = false;
Ok(match &s.fields {
// Structs
Fields::Named(f) => {
Expand All @@ -172,9 +173,14 @@ fn parse_struct(s: &ItemStruct) -> Result<RustItem, ParseError> {
RustType::try_from(&f.ty)?
};

if serde_flatten(&f.attrs) {
return Err(ParseError::SerdeFlattenNotAllowed);
}
flattened = if serde_flatten(&f.attrs) {
if !supports_flatten {
return Err(ParseError::SerdeFlattenNotAllowed);
}
true
} else {
false
};

let has_default = serde_default(&f.attrs);
let decorators = get_field_decorators(&f.attrs);
Expand All @@ -185,6 +191,7 @@ fn parse_struct(s: &ItemStruct) -> Result<RustItem, ParseError> {
comments: parse_comment_attrs(&f.attrs),
has_default,
decorators,
flattened,
})
})
.collect::<Result<_, ParseError>>()?;
Expand Down Expand Up @@ -382,6 +389,7 @@ fn parse_enum_variant(
comments: parse_comment_attrs(&f.attrs),
has_default,
decorators,
flattened: false,
})
})
.collect::<Result<Vec<_>, ParseError>>()?,
Expand Down
3 changes: 3 additions & 0 deletions core/src/rust_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,9 @@ pub struct RustField {
/// Language-specific decorators assigned to a given field.
/// The keys are language names (e.g. SupportedLanguage::TypeScript), the values are field decorators (e.g. readonly)
pub decorators: HashMap<SupportedLanguage, BTreeSet<FieldDecorator>>,
/// Whether the field should be flattened or not,
/// as per `#[serde(flatten)]` definition.
pub flattened: bool,
}

/// A single decorator on a field in Rust code.
Expand Down
2 changes: 1 addition & 1 deletion core/tests/snapshot_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ fn check(
)?;

let mut typeshare_output: Vec<u8> = Vec::new();
let parsed_data = typeshare_core::parser::parse(&rust_input)?;
let parsed_data = typeshare_core::parser::parse(&rust_input, lang.supports_flatten())?;
lang.generate_types(&mut typeshare_output, &parsed_data)?;

let typeshare_output = String::from_utf8(typeshare_output)?;
Expand Down
2 changes: 1 addition & 1 deletion rust-toolchain.toml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
[toolchain]
channel = "1.57"
channel = "1.75"
components = ["clippy"]
profile = "default"