diff --git a/core/data/tests/can_override_types/input.rs b/core/data/tests/can_override_types/input.rs new file mode 100644 index 00000000..994cc9aa --- /dev/null +++ b/core/data/tests/can_override_types/input.rs @@ -0,0 +1,29 @@ +#[typeshare] +#[serde(rename_all = "camelCase")] +struct OverrideStruct { + // These annotations are intentionally inconsistent across languages + #[typeshare( + swift(type = "Int"), + typescript(readonly, type = "any | undefined"), + kotlin(type = "Int"), go(type = "uint"), + scala(type = "Short") + )] + field_to_override: String, +} + +#[typeshare] +#[serde(tag = "type", content = "content")] +enum OverrideEnum { + UnitVariant, + TupleVariant(String), + #[serde(rename_all = "camelCase")] + AnonymousStructVariant { + #[typeshare( + swift(type = "Int"), + typescript(readonly, type = "any | undefined"), + kotlin(type = "Int"), go(type = "uint"), + scala(type = "Short") + )] + field_to_override: String + } +} \ No newline at end of file diff --git a/core/data/tests/can_override_types/output.go b/core/data/tests/can_override_types/output.go new file mode 100644 index 00000000..b8038c60 --- /dev/null +++ b/core/data/tests/can_override_types/output.go @@ -0,0 +1,87 @@ +package proto + +import "encoding/json" + +type OverrideStruct struct { + FieldToOverride uint `json:"fieldToOverride"` +} +// Generated type representing the anonymous struct variant `AnonymousStructVariant` of the `OverrideEnum` Rust enum +type OverrideEnumAnonymousStructVariantInner struct { + FieldToOverride uint `json:"fieldToOverride"` +} +type OverrideEnumTypes string +const ( + OverrideEnumTypeVariantUnitVariant OverrideEnumTypes = "UnitVariant" + OverrideEnumTypeVariantTupleVariant OverrideEnumTypes = "TupleVariant" + OverrideEnumTypeVariantAnonymousStructVariant OverrideEnumTypes = "AnonymousStructVariant" +) +type OverrideEnum struct{ + Type OverrideEnumTypes `json:"type"` + content interface{} +} + +func (o *OverrideEnum) UnmarshalJSON(data []byte) error { + var enum struct { + Tag OverrideEnumTypes `json:"type"` + Content json.RawMessage `json:"content"` + } + if err := json.Unmarshal(data, &enum); err != nil { + return err + } + + o.Type = enum.Tag + switch o.Type { + case OverrideEnumTypeVariantUnitVariant: + return nil + case OverrideEnumTypeVariantTupleVariant: + var res string + o.content = &res + case OverrideEnumTypeVariantAnonymousStructVariant: + var res OverrideEnumAnonymousStructVariantInner + o.content = &res + + } + if err := json.Unmarshal(enum.Content, &o.content); err != nil { + return err + } + + return nil +} + +func (o OverrideEnum) MarshalJSON() ([]byte, error) { + var enum struct { + Tag OverrideEnumTypes `json:"type"` + Content interface{} `json:"content,omitempty"` + } + enum.Tag = o.Type + enum.Content = o.content + return json.Marshal(enum) +} + +func (o OverrideEnum) TupleVariant() string { + res, _ := o.content.(*string) + return *res +} +func (o OverrideEnum) AnonymousStructVariant() *OverrideEnumAnonymousStructVariantInner { + res, _ := o.content.(*OverrideEnumAnonymousStructVariantInner) + return res +} + +func NewOverrideEnumTypeVariantUnitVariant() OverrideEnum { + return OverrideEnum{ + Type: OverrideEnumTypeVariantUnitVariant, + } +} +func NewOverrideEnumTypeVariantTupleVariant(content string) OverrideEnum { + return OverrideEnum{ + Type: OverrideEnumTypeVariantTupleVariant, + content: &content, + } +} +func NewOverrideEnumTypeVariantAnonymousStructVariant(content *OverrideEnumAnonymousStructVariantInner) OverrideEnum { + return OverrideEnum{ + Type: OverrideEnumTypeVariantAnonymousStructVariant, + content: content, + } +} + diff --git a/core/data/tests/can_override_types/output.kt b/core/data/tests/can_override_types/output.kt new file mode 100644 index 00000000..5413f774 --- /dev/null +++ b/core/data/tests/can_override_types/output.kt @@ -0,0 +1,31 @@ +@file:NoLiveLiterals + +package com.agilebits.onepassword + +import androidx.compose.runtime.NoLiveLiterals +import kotlinx.serialization.* + +@Serializable +data class OverrideStruct ( + val fieldToOverride: Int +) + +/// Generated type representing the anonymous struct variant `AnonymousStructVariant` of the `OverrideEnum` Rust enum +@Serializable +data class OverrideEnumAnonymousStructVariantInner ( + val fieldToOverride: Int +) + +@Serializable +sealed class OverrideEnum { + @Serializable + @SerialName("UnitVariant") + object UnitVariant: OverrideEnum() + @Serializable + @SerialName("TupleVariant") + data class TupleVariant(val content: String): OverrideEnum() + @Serializable + @SerialName("AnonymousStructVariant") + data class AnonymousStructVariant(val content: OverrideEnumAnonymousStructVariantInner): OverrideEnum() +} + diff --git a/core/data/tests/can_override_types/output.scala b/core/data/tests/can_override_types/output.scala new file mode 100644 index 00000000..db22948e --- /dev/null +++ b/core/data/tests/can_override_types/output.scala @@ -0,0 +1,29 @@ +package com.agilebits + +package onepassword { + +case class OverrideStruct ( + fieldToOverride: Short +) + +// Generated type representing the anonymous struct variant `AnonymousStructVariant` of the `OverrideEnum` Rust enum +case class OverrideEnumAnonymousStructVariantInner ( + fieldToOverride: Short +) + +sealed trait OverrideEnum { + def serialName: String +} +object OverrideEnum { + case object UnitVariant extends OverrideEnum { + val serialName: String = "UnitVariant" + } + case class TupleVariant(content: String) extends OverrideEnum { + val serialName: String = "TupleVariant" + } + case class AnonymousStructVariant(content: OverrideEnumAnonymousStructVariantInner) extends OverrideEnum { + val serialName: String = "AnonymousStructVariant" + } +} + +} diff --git a/core/data/tests/can_override_types/output.swift b/core/data/tests/can_override_types/output.swift new file mode 100644 index 00000000..f64abc6c --- /dev/null +++ b/core/data/tests/can_override_types/output.swift @@ -0,0 +1,70 @@ +import Foundation + +public struct OverrideStruct: Codable { + public let fieldToOverride: Int + + public init(fieldToOverride: Int) { + self.fieldToOverride = fieldToOverride + } +} + + +/// Generated type representing the anonymous struct variant `AnonymousStructVariant` of the `OverrideEnum` Rust enum +public struct OverrideEnumAnonymousStructVariantInner: Codable { + public let fieldToOverride: Int + + public init(fieldToOverride: Int) { + self.fieldToOverride = fieldToOverride + } +} +public enum OverrideEnum: Codable { + case unitVariant + case tupleVariant(String) + case anonymousStructVariant(OverrideEnumAnonymousStructVariantInner) + + enum CodingKeys: String, CodingKey, Codable { + case unitVariant = "UnitVariant", + tupleVariant = "TupleVariant", + anonymousStructVariant = "AnonymousStructVariant" + } + + private enum ContainerCodingKeys: String, CodingKey { + case type, content + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: ContainerCodingKeys.self) + if let type = try? container.decode(CodingKeys.self, forKey: .type) { + switch type { + case .unitVariant: + self = .unitVariant + return + case .tupleVariant: + if let content = try? container.decode(String.self, forKey: .content) { + self = .tupleVariant(content) + return + } + case .anonymousStructVariant: + if let content = try? container.decode(OverrideEnumAnonymousStructVariantInner.self, forKey: .content) { + self = .anonymousStructVariant(content) + return + } + } + } + throw DecodingError.typeMismatch(OverrideEnum.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for OverrideEnum")) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: ContainerCodingKeys.self) + switch self { + case .unitVariant: + try container.encode(CodingKeys.unitVariant, forKey: .type) + case .tupleVariant(let content): + try container.encode(CodingKeys.tupleVariant, forKey: .type) + try container.encode(content, forKey: .content) + case .anonymousStructVariant(let content): + try container.encode(CodingKeys.anonymousStructVariant, forKey: .type) + try container.encode(content, forKey: .content) + } + } +} diff --git a/core/data/tests/can_override_types/output.ts b/core/data/tests/can_override_types/output.ts new file mode 100644 index 00000000..790db2fe --- /dev/null +++ b/core/data/tests/can_override_types/output.ts @@ -0,0 +1,11 @@ +export interface OverrideStruct { + readonly fieldToOverride: any | undefined; +} + +export type OverrideEnum = + | { type: "UnitVariant", content?: undefined } + | { type: "TupleVariant", content: string } + | { type: "AnonymousStructVariant", content: { + readonly fieldToOverride: any | undefined; +}}; + diff --git a/core/src/language/go.rs b/core/src/language/go.rs index 9cd3a9d2..2ecf0554 100644 --- a/core/src/language/go.rs +++ b/core/src/language/go.rs @@ -1,5 +1,6 @@ use std::io::Write; +use crate::language::SupportedLanguage; use crate::parser::ParsedData; use crate::rename::RenameExt; use crate::rust_types::{RustItem, RustTypeFormatError, SpecialRustType}; @@ -401,9 +402,14 @@ func ({short_name} {full_name}) MarshalJSON() ([]byte, error) {{ } write_comments(w, 1, &field.comments)?; - let type_name = self - .format_type(&field.ty, generic_types) - .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?; + + let type_name = match field.type_override(SupportedLanguage::Go) { + Some(type_override) => type_override.to_owned(), + None => self + .format_type(&field.ty, generic_types) + .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?, + }; + let go_type = self.acronyms_to_uppercase(&type_name); let is_optional = field.ty.is_optional() || field.has_default; let formatted_renamed_id = format!("{:?}", &field.id.renamed); diff --git a/core/src/language/kotlin.rs b/core/src/language/kotlin.rs index fe9703d6..ac38e6a3 100644 --- a/core/src/language/kotlin.rs +++ b/core/src/language/kotlin.rs @@ -1,4 +1,5 @@ use super::Language; +use crate::language::SupportedLanguage; use crate::rust_types::{RustTypeFormatError, SpecialRustType}; use crate::{ parser::remove_dash_from_identifier, @@ -313,9 +314,13 @@ impl Kotlin { if requires_serial_name { writeln!(w, "\t@SerialName({:?})", &f.id.renamed)?; } - let ty = self - .format_type(&f.ty, generic_types) - .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?; + let ty = match f.type_override(SupportedLanguage::Kotlin) { + Some(type_override) => type_override.to_owned(), + None => self + .format_type(&f.ty, generic_types) + .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?, + }; + write!( w, "\tval {}: {}{}", diff --git a/core/src/language/scala.rs b/core/src/language/scala.rs index be7ce99d..9ce0d52b 100644 --- a/core/src/language/scala.rs +++ b/core/src/language/scala.rs @@ -1,4 +1,5 @@ use super::Language; +use crate::language::SupportedLanguage; use crate::parser::ParsedData; use crate::rust_types::{RustType, RustTypeFormatError, SpecialRustType}; use crate::{ @@ -342,9 +343,14 @@ impl Scala { generic_types: &[String], ) -> std::io::Result<()> { self.write_comments(w, 1, &f.comments)?; - let ty = self - .format_type(&f.ty, generic_types) - .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?; + + let ty = match f.type_override(SupportedLanguage::Scala) { + Some(type_override) => type_override.to_owned(), + None => self + .format_type(&f.ty, generic_types) + .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?, + }; + write!( w, "\t{}: {}{}", diff --git a/core/src/language/swift.rs b/core/src/language/swift.rs index fdf52828..a1d86bc1 100644 --- a/core/src/language/swift.rs +++ b/core/src/language/swift.rs @@ -9,6 +9,7 @@ use itertools::Itertools; use joinery::JoinableIterator; use lazy_format::lazy_format; use std::collections::BTreeSet; +use std::io; use std::{ collections::HashMap, io::Write, @@ -205,7 +206,7 @@ impl Language for Swift { }) } - fn begin_file(&mut self, w: &mut dyn Write) -> std::io::Result<()> { + fn begin_file(&mut self, w: &mut dyn Write) -> io::Result<()> { if !self.no_version_header { writeln!(w, "/*")?; writeln!(w, " Generated by typeshare {}", env!("CARGO_PKG_VERSION"))?; @@ -216,7 +217,7 @@ impl Language for Swift { Ok(()) } - fn end_file(&mut self, w: &mut dyn Write) -> std::io::Result<()> { + fn end_file(&mut self, w: &mut dyn Write) -> io::Result<()> { if self.should_emit_codable_void.load(Ordering::SeqCst) { writeln!(w)?; writeln!( @@ -237,7 +238,7 @@ impl Language for Swift { Ok(()) } - fn write_type_alias(&mut self, w: &mut dyn Write, ty: &RustTypeAlias) -> std::io::Result<()> { + fn write_type_alias(&mut self, w: &mut dyn Write, ty: &RustTypeAlias) -> io::Result<()> { writeln!(w)?; self.write_comments(w, 0, &ty.comments)?; @@ -258,7 +259,7 @@ impl Language for Swift { Ok(()) } - fn write_struct(&mut self, w: &mut dyn Write, rs: &RustStruct) -> std::io::Result<()> { + fn write_struct(&mut self, w: &mut dyn Write, rs: &RustStruct) -> io::Result<()> { let mut coding_keys = vec![]; let mut should_write_coding_keys = false; @@ -329,9 +330,13 @@ impl Language for Swift { ))); } - let case_type = self - .format_type(&f.ty, rs.generic_types.as_slice()) - .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?; + let case_type: String = match f.type_override(SupportedLanguage::Swift) { + Some(type_override) => type_override.to_owned(), + None => self + .format_type(&f.ty, rs.generic_types.as_slice()) + .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?, + }; + writeln!( w, "\tpublic let {}: {}{}", @@ -360,9 +365,13 @@ impl Language for Swift { let mut init_params: Vec = Vec::new(); for f in &rs.fields { - let swift_ty = self - .format_type(&f.ty, rs.generic_types.as_slice()) - .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?; + let swift_ty = match f.type_override(SupportedLanguage::Swift) { + Some(type_override) => type_override.to_owned(), + None => self + .format_type(&f.ty, rs.generic_types.as_slice()) + .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?, + }; + init_params.push(format!( "{}: {}{}", remove_dash_from_identifier(&f.id.renamed), @@ -391,7 +400,7 @@ impl Language for Swift { Ok(()) } - fn write_enum(&mut self, w: &mut dyn Write, e: &RustEnum) -> std::io::Result<()> { + fn write_enum(&mut self, w: &mut dyn Write, e: &RustEnum) -> io::Result<()> { /// Determines the decorators needed for an enum given an array of decorators /// that should always be present fn determine_decorators(always_present: &[String], e: &RustEnum) -> Vec { @@ -528,7 +537,7 @@ impl Swift { w: &mut dyn Write, e: &RustEnum, make_anonymous_struct_name: impl Fn(&str) -> String, - ) -> std::io::Result { + ) -> io::Result { let mut decoding_cases = Vec::new(); let mut encoding_cases = Vec::new(); let mut coding_keys = Vec::new(); @@ -613,7 +622,7 @@ impl Swift { let content_optional = ty.is_optional(); let case_type = self .format_type(ty, e.shared().generic_types.as_slice()) - .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?; + .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; write!(w, "({})", swift_keyword_aware_rename(&case_type))?; if content_optional { @@ -719,12 +728,7 @@ impl Swift { }) } - fn write_comment( - &mut self, - w: &mut dyn Write, - indent: usize, - comment: &str, - ) -> std::io::Result<()> { + fn write_comment(&mut self, w: &mut dyn Write, indent: usize, comment: &str) -> io::Result<()> { writeln!(w, "{}/// {}", "\t".repeat(indent), comment.trim_end())?; Ok(()) } @@ -734,7 +738,7 @@ impl Swift { w: &mut dyn Write, indent: usize, comments: &[String], - ) -> std::io::Result<()> { + ) -> io::Result<()> { comments .iter() .try_for_each(|c| self.write_comment(w, indent, c)) diff --git a/core/src/language/typescript.rs b/core/src/language/typescript.rs index ff624c13..cffea1f7 100644 --- a/core/src/language/typescript.rs +++ b/core/src/language/typescript.rs @@ -5,6 +5,7 @@ use crate::{ language::{Language, SupportedLanguage}, rust_types::{RustEnum, RustEnumVariant, RustField, RustStruct, RustTypeAlias}, }; +use std::io; use std::{collections::HashMap, io::Write}; /// All information needed to generate Typescript type-code @@ -74,7 +75,7 @@ impl Language for TypeScript { } } - fn begin_file(&mut self, w: &mut dyn Write) -> std::io::Result<()> { + fn begin_file(&mut self, w: &mut dyn Write) -> io::Result<()> { if !self.no_version_header { writeln!(w, "/*")?; writeln!(w, " Generated by typeshare {}", env!("CARGO_PKG_VERSION"))?; @@ -84,12 +85,12 @@ impl Language for TypeScript { Ok(()) } - fn write_type_alias(&mut self, w: &mut dyn Write, ty: &RustTypeAlias) -> std::io::Result<()> { + fn write_type_alias(&mut self, w: &mut dyn Write, ty: &RustTypeAlias) -> io::Result<()> { self.write_comments(w, 0, &ty.comments)?; let r#type = self .format_type(&ty.r#type, ty.generic_types.as_slice()) - .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?; + .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; writeln!( w, @@ -108,7 +109,7 @@ impl Language for TypeScript { Ok(()) } - fn write_struct(&mut self, w: &mut dyn Write, rs: &RustStruct) -> std::io::Result<()> { + fn write_struct(&mut self, w: &mut dyn Write, rs: &RustStruct) -> io::Result<()> { self.write_comments(w, 0, &rs.comments)?; writeln!( w, @@ -126,7 +127,7 @@ impl Language for TypeScript { writeln!(w, "}}\n") } - fn write_enum(&mut self, w: &mut dyn Write, e: &RustEnum) -> std::io::Result<()> { + fn write_enum(&mut self, w: &mut dyn Write, e: &RustEnum) -> io::Result<()> { self.write_comments(w, 0, &e.shared().comments)?; let generic_parameters = (!e.shared().generic_types.is_empty()) @@ -163,7 +164,7 @@ impl Language for TypeScript { } impl TypeScript { - fn write_enum_variants(&mut self, w: &mut dyn Write, e: &RustEnum) -> std::io::Result<()> { + fn write_enum_variants(&mut self, w: &mut dyn Write, e: &RustEnum) -> io::Result<()> { match e { // Write all the unit variants out (there can only be unit variants in // this case) @@ -194,7 +195,7 @@ impl TypeScript { RustEnumVariant::Tuple { ty, shared } => { let r#type = self .format_type(ty, e.shared().generic_types.as_slice()) - .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?; + .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; write!( w, "\t| {{ {}: {:?}, {}{}: {} }}", @@ -229,11 +230,15 @@ impl TypeScript { w: &mut dyn Write, field: &RustField, generic_types: &[String], - ) -> std::io::Result<()> { + ) -> io::Result<()> { self.write_comments(w, 1, &field.comments)?; - let ts_ty = self - .format_type(&field.ty, generic_types) - .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?; + let ts_ty: String = match field.type_override(SupportedLanguage::TypeScript) { + Some(type_override) => type_override.to_owned(), + None => self + .format_type(&field.ty, generic_types) + .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?, + }; + let optional = field.ty.is_optional() || field.has_default; let double_optional = field.ty.is_double_optional(); let is_readonly = field @@ -259,7 +264,7 @@ impl TypeScript { w: &mut dyn Write, indent: usize, comments: &[String], - ) -> std::io::Result<()> { + ) -> io::Result<()> { // Only attempt to write a comment if there are some, otherwise we're Ok() if !comments.is_empty() { let comment: String = { diff --git a/core/src/rust_types.rs b/core/src/rust_types.rs index f72c10fc..e14d13cb 100644 --- a/core/src/rust_types.rs +++ b/core/src/rust_types.rs @@ -346,6 +346,19 @@ impl RustType { } } +impl RustField { + /// Returns an type override, if it exists, on this field for a given language. + pub fn type_override(&self, language: SupportedLanguage) -> Option<&str> { + self.decorators + .get(&language)? + .iter() + .find_map(|fd| match fd { + FieldDecorator::NameValue(name, ty) if name == "type" => Some(ty.as_str()), + _ => None, + }) + } +} + #[derive(Debug, Error)] #[allow(missing_docs)] pub enum RustTypeFormatError { diff --git a/core/tests/snapshot_tests.rs b/core/tests/snapshot_tests.rs index beb80d62..b8e1e068 100644 --- a/core/tests/snapshot_tests.rs +++ b/core/tests/snapshot_tests.rs @@ -441,6 +441,7 @@ tests! { typescript, go ]; + can_override_types: [swift, kotlin, scala, typescript, go]; /// Structs can_generate_simple_struct_with_a_comment: [kotlin, swift, typescript, scala, go];