diff --git a/.clippy.toml b/.clippy.toml index 23b32c17..13067250 100644 --- a/.clippy.toml +++ b/.clippy.toml @@ -1 +1 @@ -msrv = "1.57.0" +msrv = "1.75.0" diff --git a/Cargo.lock b/Cargo.lock index ec4d558b..20764eb3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -601,7 +601,7 @@ dependencies = [ [[package]] name = "typeshare-cli" -version = "1.7.0" +version = "1.7.0-kindness.1" dependencies = [ "clap", "clap_complete_command", @@ -615,7 +615,7 @@ dependencies = [ [[package]] name = "typeshare-core" -version = "1.7.0" +version = "1.7.0-kindness.1" dependencies = [ "anyhow", "expect-test", diff --git a/Cargo.toml b/Cargo.toml index daad6d5b..bacdd4b8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,4 +1,5 @@ [workspace] +resolver = "2" members = [ "annotation", "cli", diff --git a/README.md b/README.md index 70152e97..8369294b 100644 --- a/README.md +++ b/README.md @@ -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,_ @@ -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 ``` @@ -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 @@ -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 @@ -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 @@ -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 diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 1b41b11a..8ed3e875 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -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" @@ -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" } diff --git a/cli/rust-toolchain.toml b/cli/rust-toolchain.toml index b94410bd..7644487e 100644 --- a/cli/rust-toolchain.toml +++ b/cli/rust-toolchain.toml @@ -1,4 +1,4 @@ [toolchain] -channel = "1.57" +channel = "1.75" components = ["clippy"] profile = "default" diff --git a/cli/src/main.rs b/cli/src/main.rs index 5a57c8e3..0231e109 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -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() @@ -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()); } diff --git a/core/Cargo.toml b/core/Cargo.toml index cfdfb8b7..0aaeed50 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -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" diff --git a/core/rust-toolchain.toml b/core/rust-toolchain.toml index 5b8f508d..c4c3a326 100644 --- a/core/rust-toolchain.toml +++ b/core/rust-toolchain.toml @@ -1,4 +1,4 @@ [toolchain] -channel = "1.57" +channel = "1.75" components = ["clippy"] profile = "default" \ No newline at end of file diff --git a/core/src/language/go.rs b/core/src/language/go.rs index 8c43d940..eba2cf6a 100644 --- a/core/src/language/go.rs +++ b/core/src/language/go.rs @@ -159,6 +159,10 @@ impl Language for Go { writeln!(w, "}}") } + + fn supports_flatten(&self) -> bool { + false + } } impl Go { diff --git a/core/src/language/kotlin.rs b/core/src/language/kotlin.rs index cd0e22b2..04ce20b6 100644 --- a/core/src/language/kotlin.rs +++ b/core/src/language/kotlin.rs @@ -187,6 +187,10 @@ impl Language for Kotlin { writeln!(w, "}}\n") } + + fn supports_flatten(&self) -> bool { + false + } } impl Kotlin { diff --git a/core/src/language/mod.rs b/core/src/language/mod.rs index c96ba5ac..b874fb1e 100644 --- a/core/src/language/mod.rs +++ b/core/src/language/mod.rs @@ -302,4 +302,7 @@ pub trait Language { Ok(()) } + + /// whether `#[serde(flatten)]` macro attribute is supported or not + fn supports_flatten(&self) -> bool; } diff --git a/core/src/language/scala.rs b/core/src/language/scala.rs index c086bd4b..452bda1c 100644 --- a/core/src/language/scala.rs +++ b/core/src/language/scala.rs @@ -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 { diff --git a/core/src/language/swift.rs b/core/src/language/swift.rs index 06c23c89..403b3c0c 100644 --- a/core/src/language/swift.rs +++ b/core/src/language/swift.rs @@ -533,6 +533,10 @@ impl Language for Swift { writeln!(w, "}}") } + + fn supports_flatten(&self) -> bool { + false + } } impl Swift { diff --git a/core/src/language/typescript.rs b/core/src/language/typescript.rs index a5560ef1..36f4f39b 100644 --- a/core/src/language/typescript.rs +++ b/core/src/language/typescript.rs @@ -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 @@ -165,6 +184,10 @@ impl Language for TypeScript { } } } + + fn supports_flatten(&self) -> bool { + true + } } impl TypeScript { @@ -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(), diff --git a/core/src/lib.rs b/core/src/lib.rs index 65a8c8c3..4a57d661 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -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(()) } diff --git a/core/src/parser.rs b/core/src/parser.rs index efc7b505..910dd9b3 100644 --- a/core/src/parser.rs +++ b/core/src/parser.rs @@ -80,7 +80,7 @@ pub enum ParseError { } /// Parse the given Rust source string into `ParsedData`. -pub fn parse(input: &str) -> Result { +pub fn parse(input: &str, supports_flatten: bool) -> Result { let mut parsed_data = ParsedData::default(); // We will only produce output for files that contain the `#[typeshare]` @@ -96,7 +96,7 @@ pub fn parse(input: &str) -> Result { 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)?); @@ -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 { +fn parse_struct(s: &ItemStruct, supports_flatten: bool) -> Result { let serde_rename_all = serde_rename_all(&s.attrs); let generic_types = s @@ -158,6 +158,7 @@ fn parse_struct(s: &ItemStruct) -> Result { })); } + let mut flattened: bool = false; Ok(match &s.fields { // Structs Fields::Named(f) => { @@ -172,9 +173,14 @@ fn parse_struct(s: &ItemStruct) -> Result { 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); @@ -185,6 +191,7 @@ fn parse_struct(s: &ItemStruct) -> Result { comments: parse_comment_attrs(&f.attrs), has_default, decorators, + flattened, }) }) .collect::>()?; @@ -382,6 +389,7 @@ fn parse_enum_variant( comments: parse_comment_attrs(&f.attrs), has_default, decorators, + flattened: false, }) }) .collect::, ParseError>>()?, diff --git a/core/src/rust_types.rs b/core/src/rust_types.rs index 84dfe87b..26e7e32d 100644 --- a/core/src/rust_types.rs +++ b/core/src/rust_types.rs @@ -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>, + /// 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. diff --git a/core/tests/snapshot_tests.rs b/core/tests/snapshot_tests.rs index 62c50c1e..8b3a6668 100644 --- a/core/tests/snapshot_tests.rs +++ b/core/tests/snapshot_tests.rs @@ -67,7 +67,7 @@ fn check( )?; let mut typeshare_output: Vec = 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)?; diff --git a/rust-toolchain.toml b/rust-toolchain.toml index b94410bd..7644487e 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,4 +1,4 @@ [toolchain] -channel = "1.57" +channel = "1.75" components = ["clippy"] profile = "default"