diff --git a/Cargo.lock b/Cargo.lock index 8b9fcd69..db193256 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -601,7 +601,7 @@ dependencies = [ [[package]] name = "typeshare-cli" -version = "1.0.0" +version = "1.0.1" dependencies = [ "clap", "clap_complete_command", diff --git a/cli/src/main.rs b/cli/src/main.rs index e8263839..62b96c47 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -170,9 +170,11 @@ fn main() { package: config.kotlin.package, module_name: config.kotlin.module_name, type_mappings: config.kotlin.type_mappings, + ..Default::default() }), Some("typescript") => Box::new(TypeScript { type_mappings: config.typescript.type_mappings, + ..Default::default() }), #[cfg(feature = "go")] Some("go") => Box::new(Go { diff --git a/core/src/language/go.rs b/core/src/language/go.rs index 161e9f68..b6517dfd 100644 --- a/core/src/language/go.rs +++ b/core/src/language/go.rs @@ -72,6 +72,7 @@ impl Language for Go { self.format_type(rtype1, generic_types)?, self.format_type(rtype2, generic_types)? ), + SpecialRustType::DateTime => "Time".into(), SpecialRustType::Unit => "struct{}".into(), SpecialRustType::String => "string".into(), SpecialRustType::I8 @@ -100,7 +101,13 @@ impl Language for Go { )?; writeln!(w, "package {}", self.package)?; writeln!(w)?; - writeln!(w, "import \"encoding/json\"")?; + writeln!( + w, + r#"import ( + "encoding/json" + "time" +)"# + )?; writeln!(w)?; Ok(()) } diff --git a/core/src/language/kotlin.rs b/core/src/language/kotlin.rs index f40e507b..a921ece9 100644 --- a/core/src/language/kotlin.rs +++ b/core/src/language/kotlin.rs @@ -8,6 +8,7 @@ use crate::{ use itertools::Itertools; use joinery::JoinableIterator; use lazy_format::lazy_format; +use std::sync::atomic::{AtomicBool, Ordering}; use std::{collections::HashMap, io::Write}; /// All information needed for Kotlin type-code @@ -19,6 +20,8 @@ pub struct Kotlin { pub module_name: String, /// Conversions from Rust type names to Kotlin type names. pub type_mappings: HashMap, + /// Whether we've outputted a Date (requires custom serializer) + pub has_date: AtomicBool, } impl Language for Kotlin { @@ -45,6 +48,10 @@ impl Language for Kotlin { self.format_type(rtype2, generic_types)? ) } + SpecialRustType::DateTime => { + self.has_date.store(true, Ordering::SeqCst); + "java.time.Instant".into() + } SpecialRustType::Unit => "Unit".into(), SpecialRustType::String => "String".into(), // https://kotlinlang.org/docs/basic-types.html#integer-types @@ -74,13 +81,31 @@ impl Language for Kotlin { writeln!(w, "package {}", self.package)?; writeln!(w)?; writeln!(w, "import androidx.compose.runtime.NoLiveLiterals")?; - writeln!(w, "import kotlinx.serialization.*")?; writeln!(w)?; } Ok(()) } + fn end_file(&self, w: &mut dyn Write) -> std::io::Result<()> { + if self.has_date.load(Ordering::SeqCst) { + writeln!(w, "import kotlinx.serialization.*")?; + writeln!(w)?; + writeln!( + w, + r#"object JavaInstantSerializer : KSerializer {{ + override val descriptor = PrimitiveDescriptor("Instant", PrimitiveKind.STRING) + override fun serialize(encoder: Encoder, value: java.time.Instant) = encoder.encodeString(value) + override fun deserialize(decoder: Decoder): java.time.Instant = java.time.Instant.parse(decoder.decodeString()) +}} +"# + )?; + Ok(()) + } else { + Ok(()) + } + } + fn write_type_alias(&self, w: &mut dyn Write, ty: &RustTypeAlias) -> std::io::Result<()> { self.write_comments(w, 0, &ty.comments)?; diff --git a/core/src/language/swift.rs b/core/src/language/swift.rs index bab9082e..f4e3e7c6 100644 --- a/core/src/language/swift.rs +++ b/core/src/language/swift.rs @@ -130,6 +130,7 @@ impl Language for Swift { self.format_type(rtype1, generic_types)?, self.format_type(rtype2, generic_types)? ), + SpecialRustType::DateTime => "Date".into(), SpecialRustType::Unit => { self.should_emit_codable_void.store(true, Ordering::SeqCst); "CodableVoid".into() diff --git a/core/src/language/typescript.rs b/core/src/language/typescript.rs index c6f43804..2a92ccba 100644 --- a/core/src/language/typescript.rs +++ b/core/src/language/typescript.rs @@ -3,6 +3,7 @@ use crate::{ language::Language, rust_types::{RustEnum, RustEnumVariant, RustField, RustStruct, RustTypeAlias}, }; +use std::sync::atomic::{AtomicBool, Ordering}; use std::{collections::HashMap, io::Write}; /// All information needed to generate Typescript type-code @@ -10,6 +11,7 @@ use std::{collections::HashMap, io::Write}; pub struct TypeScript { /// Mappings from Rust type names to Typescript type names pub type_mappings: HashMap, + pub has_date: AtomicBool, } impl Language for TypeScript { @@ -38,6 +40,10 @@ impl Language for TypeScript { }, self.format_type(rtype2, generic_types)? )), + SpecialRustType::DateTime => { + self.has_date.store(true, Ordering::SeqCst); + Ok("Date".into()) + } SpecialRustType::Unit => Ok("undefined".into()), SpecialRustType::String => Ok("string".into()), SpecialRustType::I8 @@ -68,6 +74,15 @@ impl Language for TypeScript { Ok(()) } + fn end_file(&self, w: &mut dyn Write) -> std::io::Result<()> { + if self.has_date.load(Ordering::SeqCst) { + writeln!(w, "export function TypeshareDateReviver(key, value): Date {{ return new Date(value); }}")?; + Ok(()) + } else { + Ok(()) + } + } + fn write_type_alias(&self, w: &mut dyn Write, ty: &RustTypeAlias) -> std::io::Result<()> { self.write_comments(w, 0, &ty.comments)?; diff --git a/core/src/rust_types.rs b/core/src/rust_types.rs index 0fc0b3c3..d5a70f6b 100644 --- a/core/src/rust_types.rs +++ b/core/src/rust_types.rs @@ -112,6 +112,8 @@ pub enum SpecialRustType { HashMap(Box, Box), /// Represents `Option` from the standard library Option(Box), + /// Represents chrono::DateTime from chrono + DateTime, /// Represents `()` Unit, /// Represents `String` from the standard library @@ -213,6 +215,7 @@ impl TryFrom<&syn::Type> for RustType { params.next().unwrap().into(), )) } + "DateTime" => RustType::Special(SpecialRustType::DateTime), "str" | "String" => RustType::Special(SpecialRustType::String), // Since we do not need to box types in other languages, we treat this type // as its inner type. @@ -311,7 +314,8 @@ impl SpecialRustType { match &self { Self::Vec(rty) | Self::Option(rty) => rty.contains_type(ty), Self::HashMap(rty1, rty2) => rty1.contains_type(ty) || rty2.contains_type(ty), - Self::Unit + Self::DateTime + | Self::Unit | Self::String | Self::I8 | Self::I16 @@ -340,6 +344,7 @@ impl SpecialRustType { Self::Vec(_) => "Vec", Self::Option(_) => "Option", Self::HashMap(_, _) => "HashMap", + Self::DateTime => "DateTime", Self::String => "String", Self::Bool => "bool", Self::I8 => "i8", @@ -365,7 +370,8 @@ impl SpecialRustType { Self::HashMap(rtype1, rtype2) => { Box::new([rtype1.as_ref(), rtype2.as_ref()].into_iter()) } - Self::Unit + Self::DateTime + | Self::Unit | Self::String | Self::I8 | Self::I16