From 51313e336fa5b345a6b965336b9f612b541dc02a Mon Sep 17 00:00:00 2001 From: Daniel Martinez Date: Thu, 8 Feb 2024 11:00:02 +1100 Subject: [PATCH 1/6] Support CSharp Create basic language implementation. Start adding tests. Add snapshot tests Update slice type to map to IEnumerable instead of Array. Drop support for Unit type and return an error. Support anonymous structs Update slice of user type test snapshot Support namespace option Support serialization with json attributes Remove EnumLabel and use EnumMember attribute instead. Update all test snapshots according to the new changes. C# without naming convention option Add required attribute For newtonsoft json to require a non optional property, the property has to have the required attribute. Otherwise, it allows it to be null and a default value is provided. --- cli/src/config.rs | 11 +- cli/src/main.rs | 10 + cli/src/parse.rs | 1 + .../anonymous_struct_with_rename/output.cs | 38 ++ .../can_apply_prefix_correctly/output.cs | 29 ++ .../can_generate_algebraic_enum/output.cs | 48 ++ .../output.cs | 17 + .../can_generate_bare_string_enum/output.cs | 18 + .../output.cs | 20 + .../tests/can_generate_generic_enum/output.cs | 69 +++ .../can_generate_readonly_fields/output.cs | 11 + .../tests/can_generate_simple_enum/output.cs | 22 + .../output.cs | 20 + .../can_generate_slice_of_user_type/output.cs | 11 + .../output.cs | 12 + .../tests/can_generate_unit_structs/output.cs | 10 + .../can_handle_anonymous_struct/output.cs | 64 +++ .../output.cs | 15 + .../tests/can_handle_serde_rename/output.cs | 20 + .../can_handle_serde_rename_all/output.cs | 23 + .../output.cs | 20 + core/data/tests/can_override_types/input.rs | 18 +- core/data/tests/can_override_types/output.cs | 28 ++ .../output.cs | 23 + .../output.cs | 25 + core/data/tests/generate_types/output.cs | 23 + .../output.cs | 10 + core/data/tests/kebab_case_rename/output.cs | 14 + .../tests/recursive_enum_decorator/output.cs | 41 ++ .../tests/resolves_qualified_type/output.cs | 16 + .../serialize_anonymous_field_as/output.cs | 18 + core/data/tests/serialize_field_as/output.cs | 13 + core/data/tests/smart_pointers/output.cs | 56 +++ .../output.cs | 24 + core/data/tests/test_generate_char/output.cs | 11 + core/data/tests/test_i54_u53_type/output.cs | 12 + .../tests/test_serde_default_struct/output.cs | 11 + core/data/tests/test_serde_iso8601/output.cs | 11 + core/data/tests/test_serde_url/output.cs | 11 + .../output.cs | 20 + .../output.cs | 10 + .../tests/use_correct_integer_types/output.cs | 17 + core/src/language/csharp.rs | 442 ++++++++++++++++++ core/src/language/mod.rs | 7 +- core/src/rust_types.rs | 4 + core/tests/snapshot_tests.rs | 112 +++-- 46 files changed, 1423 insertions(+), 43 deletions(-) create mode 100644 core/data/tests/anonymous_struct_with_rename/output.cs create mode 100644 core/data/tests/can_apply_prefix_correctly/output.cs create mode 100644 core/data/tests/can_generate_algebraic_enum/output.cs create mode 100644 core/data/tests/can_generate_algebraic_enum_with_skipped_variants/output.cs create mode 100644 core/data/tests/can_generate_bare_string_enum/output.cs create mode 100644 core/data/tests/can_generate_empty_algebraic_enum/output.cs create mode 100644 core/data/tests/can_generate_generic_enum/output.cs create mode 100644 core/data/tests/can_generate_readonly_fields/output.cs create mode 100644 core/data/tests/can_generate_simple_enum/output.cs create mode 100644 core/data/tests/can_generate_simple_struct_with_a_comment/output.cs create mode 100644 core/data/tests/can_generate_slice_of_user_type/output.cs create mode 100644 core/data/tests/can_generate_struct_with_skipped_fields/output.cs create mode 100644 core/data/tests/can_generate_unit_structs/output.cs create mode 100644 core/data/tests/can_handle_anonymous_struct/output.cs create mode 100644 core/data/tests/can_handle_quote_in_serde_rename/output.cs create mode 100644 core/data/tests/can_handle_serde_rename/output.cs create mode 100644 core/data/tests/can_handle_serde_rename_all/output.cs create mode 100644 core/data/tests/can_handle_serde_rename_on_top_level/output.cs create mode 100644 core/data/tests/can_override_types/output.cs create mode 100644 core/data/tests/can_recognize_types_inside_modules/output.cs create mode 100644 core/data/tests/enum_is_properly_named_with_serde_overrides/output.cs create mode 100644 core/data/tests/generate_types/output.cs create mode 100644 core/data/tests/generates_empty_structs_and_initializers/output.cs create mode 100644 core/data/tests/kebab_case_rename/output.cs create mode 100644 core/data/tests/recursive_enum_decorator/output.cs create mode 100644 core/data/tests/resolves_qualified_type/output.cs create mode 100644 core/data/tests/serialize_anonymous_field_as/output.cs create mode 100644 core/data/tests/serialize_field_as/output.cs create mode 100644 core/data/tests/smart_pointers/output.cs create mode 100644 core/data/tests/test_algebraic_enum_case_name_support/output.cs create mode 100644 core/data/tests/test_generate_char/output.cs create mode 100644 core/data/tests/test_i54_u53_type/output.cs create mode 100644 core/data/tests/test_serde_default_struct/output.cs create mode 100644 core/data/tests/test_serde_iso8601/output.cs create mode 100644 core/data/tests/test_serde_url/output.cs create mode 100644 core/data/tests/test_simple_enum_case_name_support/output.cs create mode 100644 core/data/tests/use_correct_decoded_variable_name/output.cs create mode 100644 core/data/tests/use_correct_integer_types/output.cs create mode 100644 core/src/language/csharp.rs diff --git a/cli/src/config.rs b/cli/src/config.rs index 7529d556..53ac69ac 100644 --- a/cli/src/config.rs +++ b/cli/src/config.rs @@ -62,7 +62,15 @@ pub struct GoParams { pub type_mappings: HashMap, } -/// The parameters that are used to configure the behaviour of typeshare +#[derive(Default, Serialize, Deserialize, PartialEq, Eq, Debug)] +#[serde(default)] +pub struct CSharpParams { + pub type_mappings: HashMap, + pub namespace: String, + pub without_csharp_naming_convention: bool, +} + +/// The paramters that are used to configure the behaviour of typeshare /// from the configuration file `typeshare.toml` #[derive(Serialize, Deserialize, Default, Debug, PartialEq)] #[serde(default)] @@ -77,6 +85,7 @@ pub(crate) struct Config { pub go: GoParams, #[serde(skip)] pub target_os: Vec, + pub csharp: CSharpParams, } pub(crate) fn store_config(config: &Config, file_path: Option<&Path>) -> anyhow::Result<()> { diff --git a/cli/src/main.rs b/cli/src/main.rs index 7a702b4e..ca087418 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -212,6 +212,12 @@ fn language( type_mappings: config.typescript.type_mappings, ..Default::default() }), + SupportedLanguage::CSharp => Box::new(CSharp { + namespace: config.csharp.namespace, + type_mappings: config.csharp.type_mappings, + without_csharp_naming_convention: config.csharp.without_csharp_naming_convention, + ..Default::default() + }), #[cfg(feature = "go")] SupportedLanguage::Go => Box::new(Go { package: config.go.package, @@ -262,6 +268,10 @@ fn override_configuration(mut config: Config, options: &Args) -> anyhow::Result< config.scala.module_name = scala_module_name.to_string(); } + if let Some(csharp_namespace) = options.value_of(ARG_CSHARP_NAMESPACE) { + config.csharp.namespace = csharp_namespace.to_string(); + } + #[cfg(feature = "go")] { if let Some(go_package) = options.go_package.as_ref() { diff --git a/cli/src/parse.rs b/cli/src/parse.rs index 2ae38fe2..64c48d58 100644 --- a/cli/src/parse.rs +++ b/cli/src/parse.rs @@ -59,6 +59,7 @@ fn output_file_name(language_type: SupportedLanguage, crate_name: &CrateName) -> SupportedLanguage::Swift => pascal_case(), SupportedLanguage::TypeScript => snake_case(), SupportedLanguage::Python => snake_case(), + SupportedLanguage::CSharp => pascal_case(), } } diff --git a/core/data/tests/anonymous_struct_with_rename/output.cs b/core/data/tests/anonymous_struct_with_rename/output.cs new file mode 100644 index 00000000..d4e2f2da --- /dev/null +++ b/core/data/tests/anonymous_struct_with_rename/output.cs @@ -0,0 +1,38 @@ +#nullable enable + +using System.Reflection; +using JsonSubTypes; +using Newtonsoft.Json; +using System.Runtime.Serialization; + +/** Generated type representing the anonymous struct variant `List` of the `AnonymousStructWithRename` Rust enum */ +public class AnonymousStructWithRenameListInner { + public IEnumerable list { get; set; } +} + +/** Generated type representing the anonymous struct variant `LongFieldNames` of the `AnonymousStructWithRename` Rust enum */ +public class AnonymousStructWithRenameLongFieldNamesInner { + public string some_long_field_name { get; set; } + public bool and { get; set; } + public IEnumerable but_one_more { get; set; } +} + +/** Generated type representing the anonymous struct variant `KebabCase` of the `AnonymousStructWithRename` Rust enum */ +public class AnonymousStructWithRenameKebabCaseInner { + public IEnumerable another-list { get; set; } + public string camelCaseStringField { get; set; } + public bool something-else { get; set; } +} + +[JsonConverter(typeof(JsonSubtypes), "type")] +[JsonSubtypes.KnownSubType(typeof(List), "List")] +[JsonSubtypes.KnownSubType(typeof(LongFieldNames), "LongFieldNames")] +[JsonSubtypes.KnownSubType(typeof(KebabCase), "KebabCase")] +public abstract record AnonymousStructWithRename +{ + public record list(AnonymousStructWithRenameListInner Content): AnonymousStructWithRename(); + public record longFieldNames(AnonymousStructWithRenameLongFieldNamesInner Content): AnonymousStructWithRename(); + public record kebabCase(AnonymousStructWithRenameKebabCaseInner Content): AnonymousStructWithRename(); +} + + diff --git a/core/data/tests/can_apply_prefix_correctly/output.cs b/core/data/tests/can_apply_prefix_correctly/output.cs new file mode 100644 index 00000000..4608b126 --- /dev/null +++ b/core/data/tests/can_apply_prefix_correctly/output.cs @@ -0,0 +1,29 @@ +#nullable enable + +using System.Reflection; +using JsonSubTypes; +using Newtonsoft.Json; +using System.Runtime.Serialization; + +public class ItemDetailsFieldValue { + public string Hello { get; set; } +} + +[JsonConverter(typeof(JsonSubtypes), "t")] +[JsonSubtypes.KnownSubType(typeof(String), "String")] +[JsonSubtypes.KnownSubType(typeof(Number), "Number")] +[JsonSubtypes.KnownSubType(typeof(NumberArray), "NumberArray")] +[JsonSubtypes.KnownSubType(typeof(ReallyCoolType), "ReallyCoolType")] +[JsonSubtypes.KnownSubType(typeof(ArrayReallyCoolType), "ArrayReallyCoolType")] +[JsonSubtypes.KnownSubType(typeof(DictionaryReallyCoolType), "DictionaryReallyCoolType")] +public abstract record AdvancedColors +{ + public record String(string C) : AdvancedColors(); + public record Number(int C) : AdvancedColors(); + public record NumberArray(IEnumerable C) : AdvancedColors(); + public record ReallyCoolType(ItemDetailsFieldValue C) : AdvancedColors(); + public record ArrayReallyCoolType(IEnumerable C) : AdvancedColors(); + public record DictionaryReallyCoolType(IDictionary C) : AdvancedColors(); +} + + diff --git a/core/data/tests/can_generate_algebraic_enum/output.cs b/core/data/tests/can_generate_algebraic_enum/output.cs new file mode 100644 index 00000000..86991424 --- /dev/null +++ b/core/data/tests/can_generate_algebraic_enum/output.cs @@ -0,0 +1,48 @@ +#nullable enable + +using System.Reflection; +using JsonSubTypes; +using Newtonsoft.Json; +using System.Runtime.Serialization; + +namespace Company.Domain.Models; + +/** Struct comment */ +public class ItemDetailsFieldValue { +} + +/** Enum comment */ +[JsonConverter(typeof(JsonSubtypes), "type")] +[JsonSubtypes.KnownSubType(typeof(String), "String")] +[JsonSubtypes.KnownSubType(typeof(Number), "Number")] +[JsonSubtypes.KnownSubType(typeof(UnsignedNumber), "UnsignedNumber")] +[JsonSubtypes.KnownSubType(typeof(NumberArray), "NumberArray")] +[JsonSubtypes.KnownSubType(typeof(ReallyCoolType), "ReallyCoolType")] +public abstract record AdvancedColors +{ + /** This is a case comment */ + public record String(string Content) : AdvancedColors(); + public record Number(int Content) : AdvancedColors(); + public record UnsignedNumber(uint Content) : AdvancedColors(); + public record NumberArray(IEnumerable Content) : AdvancedColors(); + /** Comment on the last element */ + public record ReallyCoolType(ItemDetailsFieldValue Content) : AdvancedColors(); +} + + +[JsonConverter(typeof(JsonSubtypes), "type")] +[JsonSubtypes.KnownSubType(typeof(String), "String")] +[JsonSubtypes.KnownSubType(typeof(Number), "Number")] +[JsonSubtypes.KnownSubType(typeof(NumberArray), "NumberArray")] +[JsonSubtypes.KnownSubType(typeof(ReallyCoolType), "ReallyCoolType")] +public abstract record AdvancedColors2 +{ + /** This is a case comment */ + public record String(string Content) : AdvancedColors2(); + public record Number(int Content) : AdvancedColors2(); + public record NumberArray(IEnumerable Content) : AdvancedColors2(); + /** Comment on the last element */ + public record ReallyCoolType(ItemDetailsFieldValue Content) : AdvancedColors2(); +} + + diff --git a/core/data/tests/can_generate_algebraic_enum_with_skipped_variants/output.cs b/core/data/tests/can_generate_algebraic_enum_with_skipped_variants/output.cs new file mode 100644 index 00000000..49876fe0 --- /dev/null +++ b/core/data/tests/can_generate_algebraic_enum_with_skipped_variants/output.cs @@ -0,0 +1,17 @@ +#nullable enable + +using System.Reflection; +using JsonSubTypes; +using Newtonsoft.Json; +using System.Runtime.Serialization; + +[JsonConverter(typeof(JsonSubtypes), "type")] +[JsonSubtypes.KnownSubType(typeof(A), "A")] +[JsonSubtypes.KnownSubType(typeof(C), "C")] +public abstract record SomeEnum +{ + public record A(): SomeEnum(); + public record C(int Content) : SomeEnum(); +} + + diff --git a/core/data/tests/can_generate_bare_string_enum/output.cs b/core/data/tests/can_generate_bare_string_enum/output.cs new file mode 100644 index 00000000..d68e4830 --- /dev/null +++ b/core/data/tests/can_generate_bare_string_enum/output.cs @@ -0,0 +1,18 @@ +#nullable enable + +using System.Reflection; +using JsonSubTypes; +using Newtonsoft.Json; +using System.Runtime.Serialization; + +/** This is a comment. */ +public enum Colors +{ + Red, + + Blue, + + Green, + +} + diff --git a/core/data/tests/can_generate_empty_algebraic_enum/output.cs b/core/data/tests/can_generate_empty_algebraic_enum/output.cs new file mode 100644 index 00000000..8c023872 --- /dev/null +++ b/core/data/tests/can_generate_empty_algebraic_enum/output.cs @@ -0,0 +1,20 @@ +#nullable enable + +using System.Reflection; +using JsonSubTypes; +using Newtonsoft.Json; +using System.Runtime.Serialization; + +public class AddressDetails { +} + +[JsonConverter(typeof(JsonSubtypes), "type")] +[JsonSubtypes.KnownSubType(typeof(FixedAddress), "FixedAddress")] +[JsonSubtypes.KnownSubType(typeof(NoFixedAddress), "NoFixedAddress")] +public abstract record Address +{ + public record FixedAddress(AddressDetails Content) : Address(); + public record NoFixedAddress(): Address(); +} + + diff --git a/core/data/tests/can_generate_generic_enum/output.cs b/core/data/tests/can_generate_generic_enum/output.cs new file mode 100644 index 00000000..d7e26d9f --- /dev/null +++ b/core/data/tests/can_generate_generic_enum/output.cs @@ -0,0 +1,69 @@ +#nullable enable + +using System.Reflection; +using JsonSubTypes; +using Newtonsoft.Json; +using System.Runtime.Serialization; + +[JsonConverter(typeof(JsonSubtypes), "type")] +[JsonSubtypes.KnownSubType(typeof(VariantA), "VariantA")] +[JsonSubtypes.KnownSubType(typeof(VariantB), "VariantB")] +public abstract record GenericEnum +{ + public record VariantA(TA Content) : GenericEnum(); + public record VariantB(TB Content) : GenericEnum(); +} + + +public class StructUsingGenericEnum { + public GenericEnum EnumField { get; set; } +} + +[JsonConverter(typeof(JsonSubtypes), "type")] +[JsonSubtypes.KnownSubType(typeof(VariantC), "VariantC")] +[JsonSubtypes.KnownSubType(typeof(VariantD), "VariantD")] +[JsonSubtypes.KnownSubType(typeof(VariantE), "VariantE")] +public abstract record GenericEnumUsingGenericEnum +{ + public record VariantC(GenericEnum Content) : GenericEnumUsingGenericEnum(); + public record VariantD(GenericEnum> Content) : GenericEnumUsingGenericEnum(); + public record VariantE(GenericEnum Content) : GenericEnumUsingGenericEnum(); +} + + +/** Generated type representing the anonymous struct variant `VariantF` of the `GenericEnumsUsingStructVariants` Rust enum */ +public class GenericEnumsUsingStructVariantsVariantFInner { + public T Action { get; set; } +} + +/** Generated type representing the anonymous struct variant `VariantG` of the `GenericEnumsUsingStructVariants` Rust enum */ +public class GenericEnumsUsingStructVariantsVariantGInner { + public T Action { get; set; } + public TU Response { get; set; } +} + +/** Generated type representing the anonymous struct variant `VariantH` of the `GenericEnumsUsingStructVariants` Rust enum */ +public class GenericEnumsUsingStructVariantsVariantHInner { + public int NonGeneric { get; set; } +} + +/** Generated type representing the anonymous struct variant `VariantI` of the `GenericEnumsUsingStructVariants` Rust enum */ +public class GenericEnumsUsingStructVariantsVariantIInner { + public IEnumerable Vec { get; set; } + public MyType Action { get; set; } +} + +[JsonConverter(typeof(JsonSubtypes), "type")] +[JsonSubtypes.KnownSubType(typeof(VariantF), "VariantF")] +[JsonSubtypes.KnownSubType(typeof(VariantG), "VariantG")] +[JsonSubtypes.KnownSubType(typeof(VariantH), "VariantH")] +[JsonSubtypes.KnownSubType(typeof(VariantI), "VariantI")] +public abstract record GenericEnumsUsingStructVariants +{ + public record VariantF(GenericEnumsUsingStructVariantsVariantFInner Content): GenericEnumsUsingStructVariants(); + public record VariantG(GenericEnumsUsingStructVariantsVariantGInner Content): GenericEnumsUsingStructVariants(); + public record VariantH(GenericEnumsUsingStructVariantsVariantHInner Content): GenericEnumsUsingStructVariants(); + public record VariantI(GenericEnumsUsingStructVariantsVariantIInner Content): GenericEnumsUsingStructVariants(); +} + + diff --git a/core/data/tests/can_generate_readonly_fields/output.cs b/core/data/tests/can_generate_readonly_fields/output.cs new file mode 100644 index 00000000..585b0e43 --- /dev/null +++ b/core/data/tests/can_generate_readonly_fields/output.cs @@ -0,0 +1,11 @@ +#nullable enable + +using System.Reflection; +using JsonSubTypes; +using Newtonsoft.Json; +using System.Runtime.Serialization; + +public class SomeStruct { + public uint FieldA { get; set; } +} + diff --git a/core/data/tests/can_generate_simple_enum/output.cs b/core/data/tests/can_generate_simple_enum/output.cs new file mode 100644 index 00000000..e4379f3b --- /dev/null +++ b/core/data/tests/can_generate_simple_enum/output.cs @@ -0,0 +1,22 @@ +#nullable enable + +using System.Reflection; +using JsonSubTypes; +using Newtonsoft.Json; +using System.Runtime.Serialization; + +/** + * This is a comment. + * Continued lovingly here + */ +public enum Colors +{ + Red, + + Blue, + + /** Green is a cool color */ + Green, + +} + diff --git a/core/data/tests/can_generate_simple_struct_with_a_comment/output.cs b/core/data/tests/can_generate_simple_struct_with_a_comment/output.cs new file mode 100644 index 00000000..744de2df --- /dev/null +++ b/core/data/tests/can_generate_simple_struct_with_a_comment/output.cs @@ -0,0 +1,20 @@ +#nullable enable + +using System.Reflection; +using JsonSubTypes; +using Newtonsoft.Json; +using System.Runtime.Serialization; + +public class Location { +} + +/** This is a comment. */ +public class Person { + /** This is another comment */ + public string Name { get; set; } + public ushort Age { get; set; } + public string? Info { get; set; } + public IEnumerable Emails { get; set; } + public Location Location { get; set; } +} + diff --git a/core/data/tests/can_generate_slice_of_user_type/output.cs b/core/data/tests/can_generate_slice_of_user_type/output.cs new file mode 100644 index 00000000..8696141c --- /dev/null +++ b/core/data/tests/can_generate_slice_of_user_type/output.cs @@ -0,0 +1,11 @@ +#nullable enable + +using System.Reflection; +using JsonSubTypes; +using Newtonsoft.Json; +using System.Runtime.Serialization; + +public class Video { + public IEnumerable Tags { get; set; } +} + diff --git a/core/data/tests/can_generate_struct_with_skipped_fields/output.cs b/core/data/tests/can_generate_struct_with_skipped_fields/output.cs new file mode 100644 index 00000000..a469a34b --- /dev/null +++ b/core/data/tests/can_generate_struct_with_skipped_fields/output.cs @@ -0,0 +1,12 @@ +#nullable enable + +using System.Reflection; +using JsonSubTypes; +using Newtonsoft.Json; +using System.Runtime.Serialization; + +public class MyStruct { + public int A { get; set; } + public int C { get; set; } +} + diff --git a/core/data/tests/can_generate_unit_structs/output.cs b/core/data/tests/can_generate_unit_structs/output.cs new file mode 100644 index 00000000..0affd99b --- /dev/null +++ b/core/data/tests/can_generate_unit_structs/output.cs @@ -0,0 +1,10 @@ +#nullable enable + +using System.Reflection; +using JsonSubTypes; +using Newtonsoft.Json; +using System.Runtime.Serialization; + +public class UnitStruct { +} + diff --git a/core/data/tests/can_handle_anonymous_struct/output.cs b/core/data/tests/can_handle_anonymous_struct/output.cs new file mode 100644 index 00000000..3ed38536 --- /dev/null +++ b/core/data/tests/can_handle_anonymous_struct/output.cs @@ -0,0 +1,64 @@ +#nullable enable + +using System.Reflection; +using JsonSubTypes; +using Newtonsoft.Json; +using System.Runtime.Serialization; + +/** Generated type representing the anonymous struct variant `Us` of the `AutofilledBy` Rust enum */ +public class AutofilledByUsInner { + /** The UUID for the fill */ + public string Uuid { get; set; } +} + +/** Generated type representing the anonymous struct variant `SomethingElse` of the `AutofilledBy` Rust enum */ +public class AutofilledBySomethingElseInner { + /** The UUID for the fill */ + public string Uuid { get; set; } + /** Some other thing */ + public int Thing { get; set; } +} + +/** Enum keeping track of who autofilled a field */ +[JsonConverter(typeof(JsonSubtypes), "type")] +[JsonSubtypes.KnownSubType(typeof(Us), "Us")] +[JsonSubtypes.KnownSubType(typeof(SomethingElse), "SomethingElse")] +public abstract record AutofilledBy +{ + /** This field was autofilled by us */ + public record Us(AutofilledByUsInner Content): AutofilledBy(); + /** Something else autofilled this field */ + public record SomethingElse(AutofilledBySomethingElseInner Content): AutofilledBy(); +} + + +/** Generated type representing the anonymous struct variant `AnonVariant` of the `EnumWithManyVariants` Rust enum */ +public class EnumWithManyVariantsAnonVariantInner { + public string Uuid { get; set; } +} + +/** Generated type representing the anonymous struct variant `AnotherAnonVariant` of the `EnumWithManyVariants` Rust enum */ +public class EnumWithManyVariantsAnotherAnonVariantInner { + public string Uuid { get; set; } + public int Thing { get; set; } +} + +/** This is a comment (yareek sameek wuz here) */ +[JsonConverter(typeof(JsonSubtypes), "type")] +[JsonSubtypes.KnownSubType(typeof(UnitVariant), "UnitVariant")] +[JsonSubtypes.KnownSubType(typeof(TupleVariantString), "TupleVariantString")] +[JsonSubtypes.KnownSubType(typeof(AnonVariant), "AnonVariant")] +[JsonSubtypes.KnownSubType(typeof(TupleVariantInt), "TupleVariantInt")] +[JsonSubtypes.KnownSubType(typeof(AnotherUnitVariant), "AnotherUnitVariant")] +[JsonSubtypes.KnownSubType(typeof(AnotherAnonVariant), "AnotherAnonVariant")] +public abstract record EnumWithManyVariants +{ + public record UnitVariant(): EnumWithManyVariants(); + public record TupleVariantString(string Content) : EnumWithManyVariants(); + public record AnonVariant(EnumWithManyVariantsAnonVariantInner Content): EnumWithManyVariants(); + public record TupleVariantInt(int Content) : EnumWithManyVariants(); + public record AnotherUnitVariant(): EnumWithManyVariants(); + public record AnotherAnonVariant(EnumWithManyVariantsAnotherAnonVariantInner Content): EnumWithManyVariants(); +} + + diff --git a/core/data/tests/can_handle_quote_in_serde_rename/output.cs b/core/data/tests/can_handle_quote_in_serde_rename/output.cs new file mode 100644 index 00000000..4a9b4b81 --- /dev/null +++ b/core/data/tests/can_handle_quote_in_serde_rename/output.cs @@ -0,0 +1,15 @@ +#nullable enable + +using System.Reflection; +using JsonSubTypes; +using Newtonsoft.Json; +using System.Runtime.Serialization; + +/** This is a comment. */ +public enum Colors +{ + [EnumMember(Value = "Green\"")] + Green, + +} + diff --git a/core/data/tests/can_handle_serde_rename/output.cs b/core/data/tests/can_handle_serde_rename/output.cs new file mode 100644 index 00000000..4d6440d6 --- /dev/null +++ b/core/data/tests/can_handle_serde_rename/output.cs @@ -0,0 +1,20 @@ +#nullable enable + +using System.Reflection; +using JsonSubTypes; +using Newtonsoft.Json; +using System.Runtime.Serialization; + +public class OtherType { +} + +/** This is a comment. */ +public class Person { + public string name { get; set; } + public ushort age { get; set; } + public int extraSpecialFieldOne { get; set; } + public IEnumerable? extraSpecialFieldTwo { get; set; } + public OtherType nonStandardDataType { get; set; } + public IEnumerable? nonStandardDataTypeInArray { get; set; } +} + diff --git a/core/data/tests/can_handle_serde_rename_all/output.cs b/core/data/tests/can_handle_serde_rename_all/output.cs new file mode 100644 index 00000000..584912f7 --- /dev/null +++ b/core/data/tests/can_handle_serde_rename_all/output.cs @@ -0,0 +1,23 @@ +#nullable enable + +using System.Reflection; +using JsonSubTypes; +using Newtonsoft.Json; +using System.Runtime.Serialization; + +/** This is a Person struct with camelCase rename */ +public class Person { + public string firstName { get; set; } + public string lastName { get; set; } + public ushort age { get; set; } + public int extraSpecialField1 { get; set; } + public IEnumerable? extraSpecialField2 { get; set; } +} + +/** This is a Person2 struct with UPPERCASE rename */ +public class Person2 { + public string FIRST_NAME { get; set; } + public string LAST_NAME { get; set; } + public ushort AGE { get; set; } +} + diff --git a/core/data/tests/can_handle_serde_rename_on_top_level/output.cs b/core/data/tests/can_handle_serde_rename_on_top_level/output.cs new file mode 100644 index 00000000..1043375d --- /dev/null +++ b/core/data/tests/can_handle_serde_rename_on_top_level/output.cs @@ -0,0 +1,20 @@ +#nullable enable + +using System.Reflection; +using JsonSubTypes; +using Newtonsoft.Json; +using System.Runtime.Serialization; + +public class OtherType { +} + +/** This is a comment. */ +public class PersonTwo { + public string name { get; set; } + public ushort age { get; set; } + public int extraSpecialFieldOne { get; set; } + public IEnumerable? extraSpecialFieldTwo { get; set; } + public OtherType nonStandardDataType { get; set; } + public IEnumerable? nonStandardDataTypeInArray { get; set; } +} + diff --git a/core/data/tests/can_override_types/input.rs b/core/data/tests/can_override_types/input.rs index 994cc9aa..a2bade53 100644 --- a/core/data/tests/can_override_types/input.rs +++ b/core/data/tests/can_override_types/input.rs @@ -5,8 +5,10 @@ struct OverrideStruct { #[typeshare( swift(type = "Int"), typescript(readonly, type = "any | undefined"), - kotlin(type = "Int"), go(type = "uint"), - scala(type = "Short") + kotlin(type = "Int"), + go(type = "uint"), + scala(type = "Short"), + csharp(type = "char") )] field_to_override: String, } @@ -21,9 +23,11 @@ enum OverrideEnum { #[typeshare( swift(type = "Int"), typescript(readonly, type = "any | undefined"), - kotlin(type = "Int"), go(type = "uint"), - scala(type = "Short") + kotlin(type = "Int"), + go(type = "uint"), + scala(type = "Short"), + csharp(type = "char") )] - field_to_override: String - } -} \ No newline at end of file + field_to_override: String, + }, +} diff --git a/core/data/tests/can_override_types/output.cs b/core/data/tests/can_override_types/output.cs new file mode 100644 index 00000000..836a61ca --- /dev/null +++ b/core/data/tests/can_override_types/output.cs @@ -0,0 +1,28 @@ +#nullable enable + +using System.Reflection; +using JsonSubTypes; +using Newtonsoft.Json; +using System.Runtime.Serialization; + +public class OverrideStruct { + public char fieldToOverride { get; set; } +} + +/** Generated type representing the anonymous struct variant `AnonymousStructVariant` of the `OverrideEnum` Rust enum */ +public class OverrideEnumAnonymousStructVariantInner { + public char fieldToOverride { get; set; } +} + +[JsonConverter(typeof(JsonSubtypes), "type")] +[JsonSubtypes.KnownSubType(typeof(UnitVariant), "UnitVariant")] +[JsonSubtypes.KnownSubType(typeof(TupleVariant), "TupleVariant")] +[JsonSubtypes.KnownSubType(typeof(AnonymousStructVariant), "AnonymousStructVariant")] +public abstract record OverrideEnum +{ + public record UnitVariant(): OverrideEnum(); + public record TupleVariant(string Content) : OverrideEnum(); + public record AnonymousStructVariant(OverrideEnumAnonymousStructVariantInner Content): OverrideEnum(); +} + + diff --git a/core/data/tests/can_recognize_types_inside_modules/output.cs b/core/data/tests/can_recognize_types_inside_modules/output.cs new file mode 100644 index 00000000..feacde35 --- /dev/null +++ b/core/data/tests/can_recognize_types_inside_modules/output.cs @@ -0,0 +1,23 @@ +#nullable enable + +using System.Reflection; +using JsonSubTypes; +using Newtonsoft.Json; +using System.Runtime.Serialization; + +public class A { + public uint Field { get; set; } +} + +public class ABC { + public uint Field { get; set; } +} + +public class AB { + public uint Field { get; set; } +} + +public class OutsideOfModules { + public uint Field { get; set; } +} + diff --git a/core/data/tests/enum_is_properly_named_with_serde_overrides/output.cs b/core/data/tests/enum_is_properly_named_with_serde_overrides/output.cs new file mode 100644 index 00000000..e7dc9e1e --- /dev/null +++ b/core/data/tests/enum_is_properly_named_with_serde_overrides/output.cs @@ -0,0 +1,25 @@ +#nullable enable + +using System.Reflection; +using JsonSubTypes; +using Newtonsoft.Json; +using System.Runtime.Serialization; + +/** + * This is a comment. + * Continued lovingly here + */ +public enum Colors +{ + [EnumMember(Value = "red")] + Red, + + [EnumMember(Value = "blue")] + Blue, + + /** Green is a cool color */ + [EnumMember(Value = "green-like")] + Green, + +} + diff --git a/core/data/tests/generate_types/output.cs b/core/data/tests/generate_types/output.cs new file mode 100644 index 00000000..5c93be46 --- /dev/null +++ b/core/data/tests/generate_types/output.cs @@ -0,0 +1,23 @@ +#nullable enable + +using System.Reflection; +using JsonSubTypes; +using Newtonsoft.Json; +using System.Runtime.Serialization; + +public class CustomType { +} + +public class Types { + public string S { get; set; } + public string StaticS { get; set; } + public short Int8 { get; set; } + public float Float { get; set; } + public double Double { get; set; } + public IEnumerable Array { get; set; } + public string[] FixedLengthArray { get; set; } + public IDictionary Dictionary { get; set; } + public IDictionary? OptionalDictionary { get; set; } + public CustomType CustomType { get; set; } +} + diff --git a/core/data/tests/generates_empty_structs_and_initializers/output.cs b/core/data/tests/generates_empty_structs_and_initializers/output.cs new file mode 100644 index 00000000..da8babc1 --- /dev/null +++ b/core/data/tests/generates_empty_structs_and_initializers/output.cs @@ -0,0 +1,10 @@ +#nullable enable + +using System.Reflection; +using JsonSubTypes; +using Newtonsoft.Json; +using System.Runtime.Serialization; + +public class MyEmptyStruct { +} + diff --git a/core/data/tests/kebab_case_rename/output.cs b/core/data/tests/kebab_case_rename/output.cs new file mode 100644 index 00000000..b79628e0 --- /dev/null +++ b/core/data/tests/kebab_case_rename/output.cs @@ -0,0 +1,14 @@ +#nullable enable + +using System.Reflection; +using JsonSubTypes; +using Newtonsoft.Json; +using System.Runtime.Serialization; + +/** This is a comment. */ +public class Things { + public string bla { get; set; } + public string? label { get; set; } + public string? label-left { get; set; } +} + diff --git a/core/data/tests/recursive_enum_decorator/output.cs b/core/data/tests/recursive_enum_decorator/output.cs new file mode 100644 index 00000000..f309cdaf --- /dev/null +++ b/core/data/tests/recursive_enum_decorator/output.cs @@ -0,0 +1,41 @@ +#nullable enable + +using System.Reflection; +using JsonSubTypes; +using Newtonsoft.Json; +using System.Runtime.Serialization; + +[JsonConverter(typeof(JsonSubtypes), "type")] +[JsonSubtypes.KnownSubType(typeof(Red), "Red")] +[JsonSubtypes.KnownSubType(typeof(Banana), "Banana")] +[JsonSubtypes.KnownSubType(typeof(Vermont), "Vermont")] +public abstract record Options +{ + public record Red(bool Content) : Options(); + public record Banana(string Content) : Options(); + public record Vermont(Options Content) : Options(); +} + + +/** Generated type representing the anonymous struct variant `Exactly` of the `MoreOptions` Rust enum */ +public class MoreOptionsExactlyInner { + public string Config { get; set; } +} + +/** Generated type representing the anonymous struct variant `Built` of the `MoreOptions` Rust enum */ +public class MoreOptionsBuiltInner { + public MoreOptions Top { get; set; } +} + +[JsonConverter(typeof(JsonSubtypes), "type")] +[JsonSubtypes.KnownSubType(typeof(News), "News")] +[JsonSubtypes.KnownSubType(typeof(Exactly), "Exactly")] +[JsonSubtypes.KnownSubType(typeof(Built), "Built")] +public abstract record MoreOptions +{ + public record News(bool Content) : MoreOptions(); + public record exactly(MoreOptionsExactlyInner Content): MoreOptions(); + public record built(MoreOptionsBuiltInner Content): MoreOptions(); +} + + diff --git a/core/data/tests/resolves_qualified_type/output.cs b/core/data/tests/resolves_qualified_type/output.cs new file mode 100644 index 00000000..1fb82657 --- /dev/null +++ b/core/data/tests/resolves_qualified_type/output.cs @@ -0,0 +1,16 @@ +#nullable enable + +using System.Reflection; +using JsonSubTypes; +using Newtonsoft.Json; +using System.Runtime.Serialization; + +public class QualifiedTypes { + public string Unqualified { get; set; } + public string Qualified { get; set; } + public IEnumerable QualifiedVec { get; set; } + public IDictionary QualifiedHashmap { get; set; } + public string? QualifiedOptional { get; set; } + public IDictionary>? QualfiedOptionalHashmapVec { get; set; } +} + diff --git a/core/data/tests/serialize_anonymous_field_as/output.cs b/core/data/tests/serialize_anonymous_field_as/output.cs new file mode 100644 index 00000000..11592596 --- /dev/null +++ b/core/data/tests/serialize_anonymous_field_as/output.cs @@ -0,0 +1,18 @@ +#nullable enable + +using System.Reflection; +using JsonSubTypes; +using Newtonsoft.Json; +using System.Runtime.Serialization; + +[JsonConverter(typeof(JsonSubtypes), "type")] +[JsonSubtypes.KnownSubType(typeof(Context), "Context")] +[JsonSubtypes.KnownSubType(typeof(Other), "Other")] +public abstract record SomeEnum +{ + /** The associated String contains some opaque context */ + public record Context(string Content) : SomeEnum(); + public record Other(int Content) : SomeEnum(); +} + + diff --git a/core/data/tests/serialize_field_as/output.cs b/core/data/tests/serialize_field_as/output.cs new file mode 100644 index 00000000..038a5506 --- /dev/null +++ b/core/data/tests/serialize_field_as/output.cs @@ -0,0 +1,13 @@ +#nullable enable + +using System.Reflection; +using JsonSubTypes; +using Newtonsoft.Json; +using System.Runtime.Serialization; + +public class EditItemViewModelSaveRequest { + public string Context { get; set; } + public IEnumerable Values { get; set; } + public AutoFillItemActionRequest? FillAction { get; set; } +} + diff --git a/core/data/tests/smart_pointers/output.cs b/core/data/tests/smart_pointers/output.cs new file mode 100644 index 00000000..b3e8470f --- /dev/null +++ b/core/data/tests/smart_pointers/output.cs @@ -0,0 +1,56 @@ +#nullable enable + +using System.Reflection; +using JsonSubTypes; +using Newtonsoft.Json; +using System.Runtime.Serialization; + +/** This is a comment. */ +public class ArcyColors { + public ushort Red { get; set; } + public string Blue { get; set; } + public IEnumerable Green { get; set; } +} + +/** This is a comment. */ +public class MutexyColors { + public IEnumerable Blue { get; set; } + public string Green { get; set; } +} + +/** This is a comment. */ +public class RcyColors { + public string Red { get; set; } + public IEnumerable Blue { get; set; } + public string Green { get; set; } +} + +/** This is a comment. */ +public class CellyColors { + public string Red { get; set; } + public IEnumerable Blue { get; set; } +} + +/** This is a comment. */ +public class LockyColors { + public string Red { get; set; } +} + +/** This is a comment. */ +public class CowyColors { + public string Lifetime { get; set; } +} + +/** This is a comment. */ +[JsonConverter(typeof(JsonSubtypes), "type")] +[JsonSubtypes.KnownSubType(typeof(Red), "Red")] +[JsonSubtypes.KnownSubType(typeof(Blue), "Blue")] +[JsonSubtypes.KnownSubType(typeof(Green), "Green")] +public abstract record BoxyColors +{ + public record Red(): BoxyColors(); + public record Blue(): BoxyColors(); + public record Green(string Content) : BoxyColors(); +} + + diff --git a/core/data/tests/test_algebraic_enum_case_name_support/output.cs b/core/data/tests/test_algebraic_enum_case_name_support/output.cs new file mode 100644 index 00000000..b465ad9c --- /dev/null +++ b/core/data/tests/test_algebraic_enum_case_name_support/output.cs @@ -0,0 +1,24 @@ +#nullable enable + +using System.Reflection; +using JsonSubTypes; +using Newtonsoft.Json; +using System.Runtime.Serialization; + +public class ItemDetailsFieldValue { +} + +[JsonConverter(typeof(JsonSubtypes), "type")] +[JsonSubtypes.KnownSubType(typeof(String), "String")] +[JsonSubtypes.KnownSubType(typeof(Number), "Number")] +[JsonSubtypes.KnownSubType(typeof(NumberArray), "NumberArray")] +[JsonSubtypes.KnownSubType(typeof(ReallyCoolType), "ReallyCoolType")] +public abstract record AdvancedColors +{ + public record String(string Content) : AdvancedColors(); + public record Number(int Content) : AdvancedColors(); + public record NumberArray(IEnumerable Content) : AdvancedColors(); + public record ReallyCoolType(ItemDetailsFieldValue Content) : AdvancedColors(); +} + + diff --git a/core/data/tests/test_generate_char/output.cs b/core/data/tests/test_generate_char/output.cs new file mode 100644 index 00000000..cb2c1677 --- /dev/null +++ b/core/data/tests/test_generate_char/output.cs @@ -0,0 +1,11 @@ +#nullable enable + +using System.Reflection; +using JsonSubTypes; +using Newtonsoft.Json; +using System.Runtime.Serialization; + +public class MyType { + public char Field { get; set; } +} + diff --git a/core/data/tests/test_i54_u53_type/output.cs b/core/data/tests/test_i54_u53_type/output.cs new file mode 100644 index 00000000..de49db18 --- /dev/null +++ b/core/data/tests/test_i54_u53_type/output.cs @@ -0,0 +1,12 @@ +#nullable enable + +using System.Reflection; +using JsonSubTypes; +using Newtonsoft.Json; +using System.Runtime.Serialization; + +public class Foo { + public long A { get; set; } + public ulong B { get; set; } +} + diff --git a/core/data/tests/test_serde_default_struct/output.cs b/core/data/tests/test_serde_default_struct/output.cs new file mode 100644 index 00000000..c670bec2 --- /dev/null +++ b/core/data/tests/test_serde_default_struct/output.cs @@ -0,0 +1,11 @@ +#nullable enable + +using System.Reflection; +using JsonSubTypes; +using Newtonsoft.Json; +using System.Runtime.Serialization; + +public class Foo { + public bool? Bar { get; set; } +} + diff --git a/core/data/tests/test_serde_iso8601/output.cs b/core/data/tests/test_serde_iso8601/output.cs new file mode 100644 index 00000000..0a8c5123 --- /dev/null +++ b/core/data/tests/test_serde_iso8601/output.cs @@ -0,0 +1,11 @@ +#nullable enable + +using System.Reflection; +using JsonSubTypes; +using Newtonsoft.Json; +using System.Runtime.Serialization; + +public class Foo { + public string Time { get; set; } +} + diff --git a/core/data/tests/test_serde_url/output.cs b/core/data/tests/test_serde_url/output.cs new file mode 100644 index 00000000..dde28904 --- /dev/null +++ b/core/data/tests/test_serde_url/output.cs @@ -0,0 +1,11 @@ +#nullable enable + +using System.Reflection; +using JsonSubTypes; +using Newtonsoft.Json; +using System.Runtime.Serialization; + +public class Foo { + public string Url { get; set; } +} + diff --git a/core/data/tests/test_simple_enum_case_name_support/output.cs b/core/data/tests/test_simple_enum_case_name_support/output.cs new file mode 100644 index 00000000..c612e8ae --- /dev/null +++ b/core/data/tests/test_simple_enum_case_name_support/output.cs @@ -0,0 +1,20 @@ +#nullable enable + +using System.Reflection; +using JsonSubTypes; +using Newtonsoft.Json; +using System.Runtime.Serialization; + +/** This is a comment. */ +public enum Colors +{ + [EnumMember(Value = "red")] + Red, + + [EnumMember(Value = "blue-ish")] + Blue, + + Green, + +} + diff --git a/core/data/tests/use_correct_decoded_variable_name/output.cs b/core/data/tests/use_correct_decoded_variable_name/output.cs new file mode 100644 index 00000000..da8babc1 --- /dev/null +++ b/core/data/tests/use_correct_decoded_variable_name/output.cs @@ -0,0 +1,10 @@ +#nullable enable + +using System.Reflection; +using JsonSubTypes; +using Newtonsoft.Json; +using System.Runtime.Serialization; + +public class MyEmptyStruct { +} + diff --git a/core/data/tests/use_correct_integer_types/output.cs b/core/data/tests/use_correct_integer_types/output.cs new file mode 100644 index 00000000..3ba7c226 --- /dev/null +++ b/core/data/tests/use_correct_integer_types/output.cs @@ -0,0 +1,17 @@ +#nullable enable + +using System.Reflection; +using JsonSubTypes; +using Newtonsoft.Json; +using System.Runtime.Serialization; + +/** This is a comment. */ +public class Foo { + public short A { get; set; } + public short B { get; set; } + public int C { get; set; } + public ushort E { get; set; } + public ushort F { get; set; } + public uint G { get; set; } +} + diff --git a/core/src/language/csharp.rs b/core/src/language/csharp.rs new file mode 100644 index 00000000..171d31c1 --- /dev/null +++ b/core/src/language/csharp.rs @@ -0,0 +1,442 @@ +use crate::parser::{remove_dash_from_identifier, ParsedData}; +use crate::rename::RenameExt; +use crate::rust_types::{RustType, RustTypeFormatError, SpecialRustType}; +use crate::{ + language::{Language, SupportedLanguage}, + rust_types::{RustEnum, RustEnumVariant, RustField, RustStruct, RustTypeAlias}, +}; +use itertools::Itertools; +use joinery::JoinableIterator; +use lazy_format::lazy_format; +use std::io; +use std::{collections::HashMap, io::Write}; + +/// All information needed to generate C# type-code +#[derive(Default)] +pub struct CSharp { + /// Mappings from Rust type names to C# type names + pub type_mappings: HashMap, + /// Whether or not to exclude the version header that normally appears at the top of generated code. + /// If you aren't generating a snapshot test, this setting can just be left as a default (false) + pub no_version_header: bool, + /// Namespace to use in the generated file + pub namespace: String, + /// Disable C# property naming convention and follow Serde renaming rules on properties + pub without_csharp_naming_convention: bool, +} + +impl Language for CSharp { + fn type_map(&mut self) -> &HashMap { + &self.type_mappings + } + + fn format_type( + &mut self, + ty: &RustType, + generic_types: &[String], + ) -> Result { + match ty { + RustType::Simple { id } => self.format_simple_type(id, generic_types), + RustType::Generic { id, parameters } => { + self.format_generic_type(id, parameters.as_slice(), generic_types) + } + RustType::Special(special) => self.format_special_type(special, generic_types), + } + .map(|name| with_generic_naming_convention(generic_types, &name)) + } + + fn format_special_type( + &mut self, + special_ty: &SpecialRustType, + generic_types: &[String], + ) -> Result { + match special_ty { + SpecialRustType::Vec(rtype) => Ok(format!( + "IEnumerable<{}>", + self.format_type(rtype, generic_types)? + )), + SpecialRustType::Array(rtype, _len) => { + Ok(format!("{}[]", self.format_type(rtype, generic_types)?)) + } + SpecialRustType::Slice(rtype) => Ok(format!( + "IEnumerable<{}>", + self.format_type(rtype, generic_types)? + )), + SpecialRustType::Option(rtype) => self.format_type(rtype, generic_types), + SpecialRustType::HashMap(rtype1, rtype2) => Ok(format!( + "IDictionary<{}, {}>", + match rtype1.as_ref() { + RustType::Simple { id } if generic_types.contains(id) => { + return Err(RustTypeFormatError::GenericKeyForbiddenInTS(id.clone())); + } + _ => self.format_type(rtype1, generic_types)?, + }, + self.format_type(rtype2, generic_types)? + )), + SpecialRustType::Unit => Err(RustTypeFormatError::TypeUnitInCS()), + SpecialRustType::String => Ok("string".into()), + SpecialRustType::Char => Ok("char".into()), + SpecialRustType::I8 => Ok("short".into()), + SpecialRustType::I16 => Ok("short".into()), + SpecialRustType::U8 => Ok("ushort".into()), + SpecialRustType::U16 => Ok("ushort".into()), + SpecialRustType::I32 => Ok("int".into()), + SpecialRustType::U32 => Ok("uint".into()), + SpecialRustType::I54 => Ok("long".into()), + SpecialRustType::I64 => Ok("long".into()), + SpecialRustType::U53 => Ok("ulong".into()), + SpecialRustType::U64 => Ok("ulong".into()), + SpecialRustType::F32 => Ok("float".into()), + SpecialRustType::F64 => Ok("double".into()), + SpecialRustType::Bool => Ok("bool".into()), + SpecialRustType::ISize => Ok("nint".into()), + SpecialRustType::USize => Ok("nuint".into()), + } + } + + fn begin_file(&mut self, w: &mut dyn Write, _parsed_data: &ParsedData) -> io::Result<()> { + if !self.no_version_header { + writeln!(w, "/*")?; + writeln!(w, " Generated by typeshare {}", env!("CARGO_PKG_VERSION"))?; + writeln!(w, "*/")?; + } + + writeln!(w, "#nullable enable")?; + writeln!(w)?; + + writeln!(w, "using System.Reflection;")?; + writeln!(w, "using JsonSubTypes;")?; + writeln!(w, "using Newtonsoft.Json;")?; + writeln!(w, "using System.Runtime.Serialization;")?; + writeln!(w)?; + + if !self.namespace.is_empty() { + writeln!(w, "namespace {};", self.namespace)?; + writeln!(w)?; + } + + Ok(()) + } + + fn write_type_alias(&mut self, _w: &mut dyn Write, ty: &RustTypeAlias) -> io::Result<()> { + Err(io::Error::new( + io::ErrorKind::Unsupported, + RustTypeFormatError::TypeAliasesForbiddenInCS(format!( + "C# 11 does not support type aliases. At type alias \"{}\".", + ty.id.original + )), + )) + } + + fn write_struct(&mut self, w: &mut dyn Write, rs: &RustStruct) -> io::Result<()> { + self.write_comments(w, 0, &rs.comments)?; + writeln!( + w, + "public class {}{} {{", + rs.id.renamed, + (!rs.generic_types.is_empty()) + .then(|| { + format!( + "<{}>", + with_generic_definition_naming_convention(rs.generic_types.as_slice()) + ) + }) + .unwrap_or_default() + )?; + + rs.fields + .iter() + .try_for_each(|f| self.write_field(w, f, &rs.generic_types.as_slice()))?; + + writeln!(w, "}}\n") + } + + fn write_enum(&mut self, w: &mut dyn Write, e: &RustEnum) -> io::Result<()> { + self.write_types_for_anonymous_structs(w, e, &|variant_name| { + format!("{}{}Inner", &e.shared().id.renamed, variant_name) + })?; + self.write_comments(w, 0, &e.shared().comments)?; + + let generic_parameters = (!e.shared().generic_types.is_empty()) + .then(|| { + format!( + "<{}>", + with_generic_definition_naming_convention(&e.shared().generic_types) + ) + }) + .unwrap_or_default(); + + match e { + RustEnum::Unit(shared) => { + write!( + w, + "public enum {}{}\n{{", + shared.id.renamed, generic_parameters + )?; + + self.write_enum_variants(w, e)?; + + writeln!(w, "\n}}\n") + } + RustEnum::Algebraic { shared, .. } => { + write_discriminated_union_json_attributes(w, e)?; + write!( + w, + "public abstract record {}{} \n{{", + shared.id.renamed, generic_parameters + )?; + + self.write_enum_variants(w, e)?; + + writeln!(w, "\n}}\n")?; + writeln!(w) + } + } + } + + fn write_imports( + &mut self, + _writer: &mut dyn Write, + _imports: super::ScopedCrateTypes<'_>, + ) -> std::io::Result<()> { + todo!() + } +} + +fn write_discriminated_union_json_attributes(w: &mut dyn Write, e: &RustEnum) -> io::Result<()> { + match e { + RustEnum::Algebraic { + tag_key, + content_key: _content_key, + shared, + } => { + writeln!(w, "[JsonConverter(typeof(JsonSubtypes), \"{}\")]", tag_key)?; + let case_attributes = shared.variants.iter().map(|v| { + let case_name = match v { + RustEnumVariant::AnonymousStruct { shared, .. } => &shared.id.original, + RustEnumVariant::Unit(shared) => &shared.id.original, + RustEnumVariant::Tuple { shared, .. } => &shared.id.original, + }; + format!( + "[JsonSubtypes.KnownSubType(typeof({0}), \"{0}\")]", + case_name, + ) + }); + + writeln!(w, "{}", case_attributes.join_with("\n")) + } + _ => Ok(()), + } +} + +impl CSharp { + 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) + RustEnum::Unit(shared) => shared.variants.iter().try_for_each(|v| match v { + RustEnumVariant::Unit(shared) => { + writeln!(w)?; + self.write_comments(w, 1, &shared.comments)?; + if shared.id.renamed != shared.id.original { + writeln!(w, "\t[EnumMember(Value = {:?})]", &shared.id.renamed)?; + } + writeln!(w, "\t{},", shared.id.original) + } + _ => unreachable!(), + }), + + // Write all the algebraic variants out (all three variant types are possible + // here) + RustEnum::Algebraic { + tag_key: _tag_key, + content_key, + shared, + } => shared.variants.iter().try_for_each(|v| { + writeln!(w)?; + self.write_comments(w, 1, &v.shared().comments)?; + let generic_types = &e.shared().generic_types; + let base_type = &e.shared().id.original; + let base_generics_names = generic_types + .iter() + .map(|name| with_generic_naming_convention(generic_types, &name)) + .collect_vec(); + + let base_generic_types = match base_generics_names.as_slice() { + [] => String::new(), + values => format!("<{}>", values.iter().join_with(", ")), + }; + + match v { + RustEnumVariant::Unit(shared) => write!( + w, + "\tpublic record {}(): {}{}();", + shared.id.original, base_type, base_generic_types, + ), + RustEnumVariant::Tuple { ty, shared } => { + let r#type = self + .format_type(&ty, e.shared().generic_types.as_slice()) + .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; + write!( + w, + "\tpublic record {}({}{} {}) : {}{}();", + shared.id.original, + r#type, + ty.is_optional().then(|| "?").unwrap_or_default(), + content_key.to_pascal_case(), + base_type, + base_generic_types, + ) + } + RustEnumVariant::AnonymousStruct { fields, shared } => { + let generics = fields + .iter() + .flat_map(|field| { + generic_types + .iter() + .filter(|g| field.ty.contains_type(g)) + .map(|name| { + with_generic_naming_convention(generic_types, &name) + }) + }) + .unique() + .collect_vec(); + + let generics = lazy_format!(match (generics.is_empty()) { + false => ("<{}>", generics.iter().join_with(", ")), + true => (""), + }); + + write!( + w, + "\tpublic record {}({}{}Inner{} {}): {}{}();", + shared.id.renamed, + e.shared().id.original, + shared.id.original, + generics, + content_key.to_pascal_case(), + base_type, + base_generic_types, + ) + } + } + }), + } + } + + fn write_field( + &mut self, + w: &mut dyn Write, + field: &RustField, + generic_types: &[String], + ) -> io::Result<()> { + self.write_comments(w, 1, &field.comments)?; + let cs_ty: String = match field.type_override(SupportedLanguage::CSharp) { + 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 is_readonly = field + .decorators + .get(&SupportedLanguage::CSharp) + .filter(|v| v.iter().any(|dec| dec.name() == "readonly")) + .is_some(); + + let property_name = if self.without_csharp_naming_convention { + field.id.renamed.clone() + } else { + csharp_property_aware_rename(&field.id.renamed) + }; + + writeln!( + w, + "{}\tpublic {}{} {} {{ get;{} }}", + if !optional { + "\t[JsonProperty(Required = Required.Always)]\n" + } else { + "" + }, + cs_ty, + optional.then(|| "?").unwrap_or_default(), + property_name, + if !is_readonly { " set;" } else { "" }, + )?; + + Ok(()) + } + + fn write_comments( + &mut self, + w: &mut dyn Write, + indent: usize, + comments: &[String], + ) -> io::Result<()> { + // Only attempt to write a comment if there are some, otherwise we're Ok() + if !comments.is_empty() { + let comment: String = { + let tab_indent = "\t".repeat(indent); + // If there's only one comment then keep it on the same line, otherwise we'll make a nice multi-line comment + if comments.len() == 1 { + format!("{}/** {} */", tab_indent, comments.first().unwrap()) + } else { + let joined_comments = comments.join(&format!("\n{} * ", tab_indent)); + format!( + "{tab}/** +{tab} * {comment} +{tab} */", + tab = tab_indent, + comment = joined_comments + ) + } + }; + writeln!(w, "{}", comment)?; + } + Ok(()) + } +} + +fn with_generic_definition_naming_convention(generic_types: &[String]) -> String { + generic_types + .into_iter() + .map(|name| { + if name == "T" { + return name.clone(); + } else { + format!("T{}", name.to_string()) + } + }) + .collect::>() + .join(", ") +} + +fn with_generic_naming_convention<'a, 'b>(generic_types: &'a [String], name: &'b String) -> String { + if generic_types + .into_iter() + .any(|generic_name| generic_name == name && generic_name != "T") + { + format!("T{}", name.to_string()) + } else { + name.clone() + } +} + +fn csharp_property_aware_rename(name: &str) -> String { + remove_dash_from_identifier(name) + .to_pascal_case() + .to_string() +} + +#[cfg(test)] +mod tests { + #[test] + fn rename_property() { + let input = "open_links"; + let expected = "OpenLinks"; + let actual = super::csharp_property_aware_rename(input); + + assert_eq!(actual, expected) + } +} diff --git a/core/src/language/mod.rs b/core/src/language/mod.rs index 4d007411..703ff6b3 100644 --- a/core/src/language/mod.rs +++ b/core/src/language/mod.rs @@ -19,6 +19,7 @@ use std::{ str::FromStr, }; +mod csharp; mod go; mod kotlin; mod python; @@ -26,6 +27,7 @@ mod scala; mod swift; mod typescript; +pub use csharp::CSharp; pub use go::Go; pub use kotlin::Kotlin; pub use python::Python; @@ -103,13 +105,14 @@ pub enum SupportedLanguage { Swift, TypeScript, Python, + CSharp, } impl SupportedLanguage { /// Returns an iterator over all supported language variants. pub fn all_languages() -> impl Iterator { use SupportedLanguage::*; - [Go, Kotlin, Scala, Swift, TypeScript, Python].into_iter() + [Go, Kotlin, Scala, Swift, TypeScript, Python, CSharp].into_iter() } /// Get the file name extension for the supported language. @@ -121,6 +124,7 @@ impl SupportedLanguage { SupportedLanguage::Swift => "swift", SupportedLanguage::TypeScript => "ts", SupportedLanguage::Python => "py", + SupportedLanguage::CSharp => "cs", } } } @@ -136,6 +140,7 @@ impl FromStr for SupportedLanguage { "swift" => Ok(Self::Swift), "typescript" => Ok(Self::TypeScript), "python" => Ok(Self::Python), + "csharp" => Ok(Self::CSharp), _ => Err(ParseError::UnsupportedLanguage(s.into())), } } diff --git a/core/src/rust_types.rs b/core/src/rust_types.rs index cb231384..ad7042e8 100644 --- a/core/src/rust_types.rs +++ b/core/src/rust_types.rs @@ -493,6 +493,10 @@ pub enum RustTypeFormatError { GenericsForbiddenInGo(String), #[error("Generic type `{0}` cannot be used as a map key in Typescript")] GenericKeyForbiddenInTS(String), + #[error("Type aliases are not supported in C# 11 or lower")] + TypeAliasesForbiddenInCS(String), + #[error("Type Unit is not supported in C#")] + TypeUnitInCS(), } impl SpecialRustType { diff --git a/core/tests/snapshot_tests.rs b/core/tests/snapshot_tests.rs index eb02798f..6785a6a4 100644 --- a/core/tests/snapshot_tests.rs +++ b/core/tests/snapshot_tests.rs @@ -154,6 +154,9 @@ macro_rules! output_file_for_ident { (python) => { "output.py" }; + (csharp) => { + "output.cs" + }; } /// Simplifies the construction of `Language` instances for each language. @@ -286,6 +289,22 @@ macro_rules! language_instance { ..Default::default() }) }; + + // Default C# + (csharp) => { + language_instance!(csharp { + without_csharp_naming_convention: false, + }) + }; + // C# with configuration fields forwarded + (csharp {$($field:ident: $val:expr),* $(,)?}) => { + #[allow(clippy::needless_update)] + Box::new(typeshare_core::language::CSharp { + no_version_header: true, + $($field: $val,)* + ..Default::default() + }) + }; } macro_rules! target_os { @@ -443,6 +462,12 @@ static PYTHON_MAPPINGS: Lazy> = Lazy::new(|| { .map(|(k, v)| (k.to_string(), v.to_string())) .collect() }); +static CSHARP_MAPPINGS: Lazy> = Lazy::new(|| { + [("Url", "string"), ("DateTime", "string")] + .iter() + .map(|(k, v)| (k.to_string(), v.to_string())) + .collect() +}); tests! { /// Enums @@ -460,7 +485,10 @@ tests! { }, typescript, go, - python + python, + csharp { + namespace: "Company.Domain.Models".to_string(), + } ]; can_generate_generic_enum: [ swift { @@ -468,7 +496,8 @@ tests! { }, kotlin, scala, - typescript + typescript, + csharp, ]; can_generate_generic_struct: [ swift { @@ -488,9 +517,10 @@ tests! { typescript ]; can_generate_const: [typescript, go, python]; - can_generate_slice_of_user_type: [swift, kotlin, scala, typescript, go, python]; + can_generate_slice_of_user_type: [swift, kotlin, scala, typescript, go, python, csharp]; can_generate_readonly_fields: [ - typescript + typescript, + csharp ]; can_generate_simple_enum: [ swift { @@ -500,16 +530,17 @@ tests! { scala, typescript, go, + csharp, python ]; - can_generate_bare_string_enum: [swift, kotlin, scala, typescript, go, python ]; + can_generate_bare_string_enum: [swift, kotlin, scala, typescript, go, python, csharp ]; can_generate_double_option_pattern: [ typescript ]; can_recognize_types_inside_modules: [ - swift, kotlin, scala, typescript, go, python + swift, kotlin, scala, typescript, go, python, csharp ]; - test_simple_enum_case_name_support: [swift, kotlin, scala, typescript, go, python ]; + test_simple_enum_case_name_support: [swift, kotlin, scala, typescript, go, python, csharp ]; test_algebraic_enum_case_name_support: [ swift { prefix: "OP".to_string(), @@ -524,16 +555,17 @@ tests! { }, typescript, go, + csharp, python ]; - can_apply_prefix_correctly: [ swift { prefix: "OP".to_string(), }, kotlin { prefix: "OP".to_string(), }, scala, typescript, go, python ]; - can_generate_empty_algebraic_enum: [ swift { prefix: "OP".to_string(), }, kotlin { prefix: "OP".to_string(), }, scala, typescript, go, python ]; - can_generate_algebraic_enum_with_skipped_variants: [swift, kotlin, scala, typescript, go, python]; - can_generate_struct_with_skipped_fields: [swift, kotlin, scala, typescript, go, python]; - enum_is_properly_named_with_serde_overrides: [swift, kotlin, scala, typescript, go, python]; - can_handle_quote_in_serde_rename: [swift, kotlin, scala, typescript, go, python]; - can_handle_anonymous_struct: [swift, kotlin, scala, typescript, go, python]; - test_generate_char: [swift, kotlin, scala, typescript, go, python]; + can_apply_prefix_correctly: [ swift { prefix: "OP".to_string(), }, kotlin { prefix: "OP".to_string(), }, scala, typescript, go, python, csharp ]; + can_generate_empty_algebraic_enum: [ swift { prefix: "OP".to_string(), }, kotlin { prefix: "OP".to_string(), }, scala, typescript, go, python, csharp ]; + can_generate_algebraic_enum_with_skipped_variants: [swift, kotlin, scala, typescript, go, python, csharp]; + can_generate_struct_with_skipped_fields: [swift, kotlin, scala, typescript, go, python, csharp]; + enum_is_properly_named_with_serde_overrides: [swift, kotlin, scala, typescript, go, python, csharp]; + can_handle_quote_in_serde_rename: [swift, kotlin, scala, typescript, go, python, csharp]; + can_handle_anonymous_struct: [swift, kotlin, scala, typescript, go, python, csharp]; + test_generate_char: [swift, kotlin, scala, typescript, go, python, csharp]; anonymous_struct_with_rename: [ swift { prefix: "Core".to_string(), @@ -542,13 +574,15 @@ tests! { scala, typescript, go, - python + python, + csharp { + without_csharp_naming_convention: true, + }, ]; - can_override_types: [swift, kotlin, scala, typescript, go]; /// Structs - can_generate_simple_struct_with_a_comment: [kotlin, swift, typescript, scala, go, python]; - generate_types: [kotlin, swift, typescript, scala, go, python]; + can_generate_simple_struct_with_a_comment: [kotlin, swift, typescript, scala, go, python, csharp]; + generate_types: [kotlin, swift, typescript, scala, go, python, csharp]; can_handle_serde_rename: [ swift { prefix: "TypeShareX_".to_string(), @@ -557,14 +591,17 @@ tests! { scala, typescript, go, + csharp { + without_csharp_naming_convention: true, + }, python ]; // TODO: kotlin and typescript don't appear to support this yet - generates_empty_structs_and_initializers: [swift, kotlin, scala, typescript, go,python]; + generates_empty_structs_and_initializers: [swift, kotlin, scala, typescript, go,python, csharp]; test_default_decorators: [swift { default_decorators: vec!["Sendable".into(), "Identifiable".into()]}]; test_default_generic_constraints: [swift { default_generic_constraints: typeshare_core::language::GenericConstraints::from_config(vec!["Sendable".into(), "Identifiable".into()]) }]; - test_i54_u53_type: [swift, kotlin, scala, typescript, go, python]; - test_serde_default_struct: [swift, kotlin, scala, typescript, go, python]; + test_i54_u53_type: [swift, kotlin, scala, typescript, go, python, csharp]; + test_serde_default_struct: [swift, kotlin, scala, typescript, go, python, csharp]; test_serde_iso8601: [ swift { prefix: String::new(), @@ -583,9 +620,12 @@ tests! { typescript { type_mappings: super::TYPESCRIPT_MAPPINGS.clone(), }, - go { + go { type_mappings: super::GO_MAPPINGS.clone(), }, + csharp { + type_mappings: super::CSHARP_MAPPINGS.clone(), + }, ]; test_serde_url: [ swift { @@ -609,6 +649,9 @@ tests! { type_mappings: super::GO_MAPPINGS.clone(), uppercase_acronyms: vec!["URL".to_string()], }, + csharp { + type_mappings: super::CSHARP_MAPPINGS.clone(), + }, python{ type_mappings: super::PYTHON_MAPPINGS.clone() } @@ -628,31 +671,31 @@ tests! { }, python ]; - can_handle_serde_rename_all: [swift, kotlin, scala, typescript, go,python]; - can_handle_serde_rename_on_top_level: [swift { prefix: "OP".to_string(), }, kotlin, scala, typescript, go, python]; - can_generate_unit_structs: [swift, kotlin, scala, typescript, go, python]; - kebab_case_rename: [swift, kotlin, scala, typescript, go, python]; + can_handle_serde_rename_all: [swift, kotlin, scala, typescript, go,python, csharp { without_csharp_naming_convention: true, }]; + can_handle_serde_rename_on_top_level: [swift { prefix: "OP".to_string(), }, kotlin, scala, typescript, go, python, csharp { without_csharp_naming_convention: true, }]; + can_generate_unit_structs: [swift, kotlin, scala, typescript, go, python, csharp]; + kebab_case_rename: [swift, kotlin, scala, typescript, go, python, csharp { without_csharp_naming_convention: true, }]; /// Globals get topologically sorted orders_types: [swift, kotlin, go, python]; /// Other - use_correct_integer_types: [swift, kotlin, scala, typescript, go, python]; + use_correct_integer_types: [swift, kotlin, scala, typescript, go, python, csharp]; // Only swift supports generating types with keywords generate_types_with_keywords: [swift]; // TODO: how is this different from generates_empty_structs_and_initializers? - use_correct_decoded_variable_name: [swift, kotlin, scala, typescript, go, python]; + use_correct_decoded_variable_name: [swift, kotlin, scala, typescript, go, python, csharp]; can_handle_unit_type: [swift { codablevoid_constraints: vec!["Equatable".into()]} , kotlin, scala, typescript, go, python]; //3 tests for adding decorators to enums and structs const_enum_decorator: [ swift{ prefix: "OP".to_string(), } ]; algebraic_enum_decorator: [ swift{ prefix: "OP".to_string(), } ]; struct_decorator: [ kotlin, swift{ prefix: "OP".to_string(), } ]; - serialize_field_as: [kotlin, swift, typescript, scala, go, python]; + serialize_field_as: [kotlin, swift, typescript, scala, go, python, csharp]; serialize_type_alias: [kotlin, swift, typescript, scala, go, python]; - serialize_anonymous_field_as: [kotlin, swift, typescript, scala, go, python]; - smart_pointers: [kotlin, swift, typescript, scala, go, python]; - recursive_enum_decorator: [kotlin, swift, typescript, scala, go, python]; + serialize_anonymous_field_as: [kotlin, swift, typescript, scala, go, python, csharp]; + smart_pointers: [kotlin, swift, typescript, scala, go, python, csharp]; + recursive_enum_decorator: [kotlin, swift, typescript, scala, go, python, csharp]; uppercase_go_acronyms: [ go { @@ -667,7 +710,8 @@ tests! { kotlin, scala, go, - python + python, + csharp ]; can_generate_anonymous_struct_with_skipped_fields: [swift, kotlin, scala, typescript, go, python]; generic_struct_with_constraints_and_decorators: [swift { codablevoid_constraints: vec!["Equatable".into()] }]; From aabb15a7bb9e934eb794a08f2d36410efeb93418 Mon Sep 17 00:00:00 2001 From: Daniel Martinez Date: Thu, 13 Jun 2024 13:29:58 +1000 Subject: [PATCH 2/6] Update C# snapshots with required attr --- .../tests/anonymous_struct_with_rename/output.cs | 7 +++++++ core/data/tests/can_apply_prefix_correctly/output.cs | 1 + core/data/tests/can_generate_generic_enum/output.cs | 7 +++++++ .../tests/can_generate_readonly_fields/output.cs | 1 + .../output.cs | 4 ++++ .../tests/can_generate_slice_of_user_type/output.cs | 1 + .../output.cs | 2 ++ .../data/tests/can_handle_anonymous_struct/output.cs | 6 ++++++ core/data/tests/can_handle_serde_rename/output.cs | 4 ++++ .../data/tests/can_handle_serde_rename_all/output.cs | 7 +++++++ .../can_handle_serde_rename_on_top_level/output.cs | 4 ++++ core/data/tests/can_override_types/output.cs | 2 ++ .../can_recognize_types_inside_modules/output.cs | 4 ++++ core/data/tests/generate_types/output.cs | 9 +++++++++ core/data/tests/kebab_case_rename/output.cs | 1 + core/data/tests/recursive_enum_decorator/output.cs | 2 ++ core/data/tests/resolves_qualified_type/output.cs | 4 ++++ core/data/tests/serialize_field_as/output.cs | 2 ++ core/data/tests/smart_pointers/output.cs | 12 ++++++++++++ core/data/tests/test_generate_char/output.cs | 1 + core/data/tests/test_i54_u53_type/output.cs | 2 ++ core/data/tests/test_serde_iso8601/output.cs | 1 + core/data/tests/test_serde_url/output.cs | 1 + core/data/tests/use_correct_integer_types/output.cs | 6 ++++++ 24 files changed, 91 insertions(+) diff --git a/core/data/tests/anonymous_struct_with_rename/output.cs b/core/data/tests/anonymous_struct_with_rename/output.cs index d4e2f2da..84db6773 100644 --- a/core/data/tests/anonymous_struct_with_rename/output.cs +++ b/core/data/tests/anonymous_struct_with_rename/output.cs @@ -7,20 +7,27 @@ /** Generated type representing the anonymous struct variant `List` of the `AnonymousStructWithRename` Rust enum */ public class AnonymousStructWithRenameListInner { + [JsonProperty(Required = Required.Always)] public IEnumerable list { get; set; } } /** Generated type representing the anonymous struct variant `LongFieldNames` of the `AnonymousStructWithRename` Rust enum */ public class AnonymousStructWithRenameLongFieldNamesInner { + [JsonProperty(Required = Required.Always)] public string some_long_field_name { get; set; } + [JsonProperty(Required = Required.Always)] public bool and { get; set; } + [JsonProperty(Required = Required.Always)] public IEnumerable but_one_more { get; set; } } /** Generated type representing the anonymous struct variant `KebabCase` of the `AnonymousStructWithRename` Rust enum */ public class AnonymousStructWithRenameKebabCaseInner { + [JsonProperty(Required = Required.Always)] public IEnumerable another-list { get; set; } + [JsonProperty(Required = Required.Always)] public string camelCaseStringField { get; set; } + [JsonProperty(Required = Required.Always)] public bool something-else { get; set; } } diff --git a/core/data/tests/can_apply_prefix_correctly/output.cs b/core/data/tests/can_apply_prefix_correctly/output.cs index 4608b126..c8129964 100644 --- a/core/data/tests/can_apply_prefix_correctly/output.cs +++ b/core/data/tests/can_apply_prefix_correctly/output.cs @@ -6,6 +6,7 @@ using System.Runtime.Serialization; public class ItemDetailsFieldValue { + [JsonProperty(Required = Required.Always)] public string Hello { get; set; } } diff --git a/core/data/tests/can_generate_generic_enum/output.cs b/core/data/tests/can_generate_generic_enum/output.cs index d7e26d9f..6d3adab1 100644 --- a/core/data/tests/can_generate_generic_enum/output.cs +++ b/core/data/tests/can_generate_generic_enum/output.cs @@ -16,6 +16,7 @@ public record VariantB(TB Content) : GenericEnum(); public class StructUsingGenericEnum { + [JsonProperty(Required = Required.Always)] public GenericEnum EnumField { get; set; } } @@ -33,23 +34,29 @@ public record VariantE(GenericEnum Content) : GenericEnumUsingGene /** Generated type representing the anonymous struct variant `VariantF` of the `GenericEnumsUsingStructVariants` Rust enum */ public class GenericEnumsUsingStructVariantsVariantFInner { + [JsonProperty(Required = Required.Always)] public T Action { get; set; } } /** Generated type representing the anonymous struct variant `VariantG` of the `GenericEnumsUsingStructVariants` Rust enum */ public class GenericEnumsUsingStructVariantsVariantGInner { + [JsonProperty(Required = Required.Always)] public T Action { get; set; } + [JsonProperty(Required = Required.Always)] public TU Response { get; set; } } /** Generated type representing the anonymous struct variant `VariantH` of the `GenericEnumsUsingStructVariants` Rust enum */ public class GenericEnumsUsingStructVariantsVariantHInner { + [JsonProperty(Required = Required.Always)] public int NonGeneric { get; set; } } /** Generated type representing the anonymous struct variant `VariantI` of the `GenericEnumsUsingStructVariants` Rust enum */ public class GenericEnumsUsingStructVariantsVariantIInner { + [JsonProperty(Required = Required.Always)] public IEnumerable Vec { get; set; } + [JsonProperty(Required = Required.Always)] public MyType Action { get; set; } } diff --git a/core/data/tests/can_generate_readonly_fields/output.cs b/core/data/tests/can_generate_readonly_fields/output.cs index 585b0e43..881d66ba 100644 --- a/core/data/tests/can_generate_readonly_fields/output.cs +++ b/core/data/tests/can_generate_readonly_fields/output.cs @@ -6,6 +6,7 @@ using System.Runtime.Serialization; public class SomeStruct { + [JsonProperty(Required = Required.Always)] public uint FieldA { get; set; } } diff --git a/core/data/tests/can_generate_simple_struct_with_a_comment/output.cs b/core/data/tests/can_generate_simple_struct_with_a_comment/output.cs index 744de2df..76a6dbd9 100644 --- a/core/data/tests/can_generate_simple_struct_with_a_comment/output.cs +++ b/core/data/tests/can_generate_simple_struct_with_a_comment/output.cs @@ -11,10 +11,14 @@ public class Location { /** This is a comment. */ public class Person { /** This is another comment */ + [JsonProperty(Required = Required.Always)] public string Name { get; set; } + [JsonProperty(Required = Required.Always)] public ushort Age { get; set; } public string? Info { get; set; } + [JsonProperty(Required = Required.Always)] public IEnumerable Emails { get; set; } + [JsonProperty(Required = Required.Always)] public Location Location { get; set; } } diff --git a/core/data/tests/can_generate_slice_of_user_type/output.cs b/core/data/tests/can_generate_slice_of_user_type/output.cs index 8696141c..95de631c 100644 --- a/core/data/tests/can_generate_slice_of_user_type/output.cs +++ b/core/data/tests/can_generate_slice_of_user_type/output.cs @@ -6,6 +6,7 @@ using System.Runtime.Serialization; public class Video { + [JsonProperty(Required = Required.Always)] public IEnumerable Tags { get; set; } } diff --git a/core/data/tests/can_generate_struct_with_skipped_fields/output.cs b/core/data/tests/can_generate_struct_with_skipped_fields/output.cs index a469a34b..da974387 100644 --- a/core/data/tests/can_generate_struct_with_skipped_fields/output.cs +++ b/core/data/tests/can_generate_struct_with_skipped_fields/output.cs @@ -6,7 +6,9 @@ using System.Runtime.Serialization; public class MyStruct { + [JsonProperty(Required = Required.Always)] public int A { get; set; } + [JsonProperty(Required = Required.Always)] public int C { get; set; } } diff --git a/core/data/tests/can_handle_anonymous_struct/output.cs b/core/data/tests/can_handle_anonymous_struct/output.cs index 3ed38536..c93bb1bf 100644 --- a/core/data/tests/can_handle_anonymous_struct/output.cs +++ b/core/data/tests/can_handle_anonymous_struct/output.cs @@ -8,14 +8,17 @@ /** Generated type representing the anonymous struct variant `Us` of the `AutofilledBy` Rust enum */ public class AutofilledByUsInner { /** The UUID for the fill */ + [JsonProperty(Required = Required.Always)] public string Uuid { get; set; } } /** Generated type representing the anonymous struct variant `SomethingElse` of the `AutofilledBy` Rust enum */ public class AutofilledBySomethingElseInner { /** The UUID for the fill */ + [JsonProperty(Required = Required.Always)] public string Uuid { get; set; } /** Some other thing */ + [JsonProperty(Required = Required.Always)] public int Thing { get; set; } } @@ -34,12 +37,15 @@ public record SomethingElse(AutofilledBySomethingElseInner Content): AutofilledB /** Generated type representing the anonymous struct variant `AnonVariant` of the `EnumWithManyVariants` Rust enum */ public class EnumWithManyVariantsAnonVariantInner { + [JsonProperty(Required = Required.Always)] public string Uuid { get; set; } } /** Generated type representing the anonymous struct variant `AnotherAnonVariant` of the `EnumWithManyVariants` Rust enum */ public class EnumWithManyVariantsAnotherAnonVariantInner { + [JsonProperty(Required = Required.Always)] public string Uuid { get; set; } + [JsonProperty(Required = Required.Always)] public int Thing { get; set; } } diff --git a/core/data/tests/can_handle_serde_rename/output.cs b/core/data/tests/can_handle_serde_rename/output.cs index 4d6440d6..4385e58d 100644 --- a/core/data/tests/can_handle_serde_rename/output.cs +++ b/core/data/tests/can_handle_serde_rename/output.cs @@ -10,10 +10,14 @@ public class OtherType { /** This is a comment. */ public class Person { + [JsonProperty(Required = Required.Always)] public string name { get; set; } + [JsonProperty(Required = Required.Always)] public ushort age { get; set; } + [JsonProperty(Required = Required.Always)] public int extraSpecialFieldOne { get; set; } public IEnumerable? extraSpecialFieldTwo { get; set; } + [JsonProperty(Required = Required.Always)] public OtherType nonStandardDataType { get; set; } public IEnumerable? nonStandardDataTypeInArray { get; set; } } diff --git a/core/data/tests/can_handle_serde_rename_all/output.cs b/core/data/tests/can_handle_serde_rename_all/output.cs index 584912f7..1d67137d 100644 --- a/core/data/tests/can_handle_serde_rename_all/output.cs +++ b/core/data/tests/can_handle_serde_rename_all/output.cs @@ -7,17 +7,24 @@ /** This is a Person struct with camelCase rename */ public class Person { + [JsonProperty(Required = Required.Always)] public string firstName { get; set; } + [JsonProperty(Required = Required.Always)] public string lastName { get; set; } + [JsonProperty(Required = Required.Always)] public ushort age { get; set; } + [JsonProperty(Required = Required.Always)] public int extraSpecialField1 { get; set; } public IEnumerable? extraSpecialField2 { get; set; } } /** This is a Person2 struct with UPPERCASE rename */ public class Person2 { + [JsonProperty(Required = Required.Always)] public string FIRST_NAME { get; set; } + [JsonProperty(Required = Required.Always)] public string LAST_NAME { get; set; } + [JsonProperty(Required = Required.Always)] public ushort AGE { get; set; } } diff --git a/core/data/tests/can_handle_serde_rename_on_top_level/output.cs b/core/data/tests/can_handle_serde_rename_on_top_level/output.cs index 1043375d..0fc6b3e8 100644 --- a/core/data/tests/can_handle_serde_rename_on_top_level/output.cs +++ b/core/data/tests/can_handle_serde_rename_on_top_level/output.cs @@ -10,10 +10,14 @@ public class OtherType { /** This is a comment. */ public class PersonTwo { + [JsonProperty(Required = Required.Always)] public string name { get; set; } + [JsonProperty(Required = Required.Always)] public ushort age { get; set; } + [JsonProperty(Required = Required.Always)] public int extraSpecialFieldOne { get; set; } public IEnumerable? extraSpecialFieldTwo { get; set; } + [JsonProperty(Required = Required.Always)] public OtherType nonStandardDataType { get; set; } public IEnumerable? nonStandardDataTypeInArray { get; set; } } diff --git a/core/data/tests/can_override_types/output.cs b/core/data/tests/can_override_types/output.cs index 836a61ca..21c53789 100644 --- a/core/data/tests/can_override_types/output.cs +++ b/core/data/tests/can_override_types/output.cs @@ -6,11 +6,13 @@ using System.Runtime.Serialization; public class OverrideStruct { + [JsonProperty(Required = Required.Always)] public char fieldToOverride { get; set; } } /** Generated type representing the anonymous struct variant `AnonymousStructVariant` of the `OverrideEnum` Rust enum */ public class OverrideEnumAnonymousStructVariantInner { + [JsonProperty(Required = Required.Always)] public char fieldToOverride { get; set; } } diff --git a/core/data/tests/can_recognize_types_inside_modules/output.cs b/core/data/tests/can_recognize_types_inside_modules/output.cs index feacde35..e631b983 100644 --- a/core/data/tests/can_recognize_types_inside_modules/output.cs +++ b/core/data/tests/can_recognize_types_inside_modules/output.cs @@ -6,18 +6,22 @@ using System.Runtime.Serialization; public class A { + [JsonProperty(Required = Required.Always)] public uint Field { get; set; } } public class ABC { + [JsonProperty(Required = Required.Always)] public uint Field { get; set; } } public class AB { + [JsonProperty(Required = Required.Always)] public uint Field { get; set; } } public class OutsideOfModules { + [JsonProperty(Required = Required.Always)] public uint Field { get; set; } } diff --git a/core/data/tests/generate_types/output.cs b/core/data/tests/generate_types/output.cs index 5c93be46..690fdefe 100644 --- a/core/data/tests/generate_types/output.cs +++ b/core/data/tests/generate_types/output.cs @@ -9,15 +9,24 @@ public class CustomType { } public class Types { + [JsonProperty(Required = Required.Always)] public string S { get; set; } + [JsonProperty(Required = Required.Always)] public string StaticS { get; set; } + [JsonProperty(Required = Required.Always)] public short Int8 { get; set; } + [JsonProperty(Required = Required.Always)] public float Float { get; set; } + [JsonProperty(Required = Required.Always)] public double Double { get; set; } + [JsonProperty(Required = Required.Always)] public IEnumerable Array { get; set; } + [JsonProperty(Required = Required.Always)] public string[] FixedLengthArray { get; set; } + [JsonProperty(Required = Required.Always)] public IDictionary Dictionary { get; set; } public IDictionary? OptionalDictionary { get; set; } + [JsonProperty(Required = Required.Always)] public CustomType CustomType { get; set; } } diff --git a/core/data/tests/kebab_case_rename/output.cs b/core/data/tests/kebab_case_rename/output.cs index b79628e0..2652b7c4 100644 --- a/core/data/tests/kebab_case_rename/output.cs +++ b/core/data/tests/kebab_case_rename/output.cs @@ -7,6 +7,7 @@ /** This is a comment. */ public class Things { + [JsonProperty(Required = Required.Always)] public string bla { get; set; } public string? label { get; set; } public string? label-left { get; set; } diff --git a/core/data/tests/recursive_enum_decorator/output.cs b/core/data/tests/recursive_enum_decorator/output.cs index f309cdaf..a3e581e0 100644 --- a/core/data/tests/recursive_enum_decorator/output.cs +++ b/core/data/tests/recursive_enum_decorator/output.cs @@ -19,11 +19,13 @@ public record Vermont(Options Content) : Options(); /** Generated type representing the anonymous struct variant `Exactly` of the `MoreOptions` Rust enum */ public class MoreOptionsExactlyInner { + [JsonProperty(Required = Required.Always)] public string Config { get; set; } } /** Generated type representing the anonymous struct variant `Built` of the `MoreOptions` Rust enum */ public class MoreOptionsBuiltInner { + [JsonProperty(Required = Required.Always)] public MoreOptions Top { get; set; } } diff --git a/core/data/tests/resolves_qualified_type/output.cs b/core/data/tests/resolves_qualified_type/output.cs index 1fb82657..ad1f9cd1 100644 --- a/core/data/tests/resolves_qualified_type/output.cs +++ b/core/data/tests/resolves_qualified_type/output.cs @@ -6,9 +6,13 @@ using System.Runtime.Serialization; public class QualifiedTypes { + [JsonProperty(Required = Required.Always)] public string Unqualified { get; set; } + [JsonProperty(Required = Required.Always)] public string Qualified { get; set; } + [JsonProperty(Required = Required.Always)] public IEnumerable QualifiedVec { get; set; } + [JsonProperty(Required = Required.Always)] public IDictionary QualifiedHashmap { get; set; } public string? QualifiedOptional { get; set; } public IDictionary>? QualfiedOptionalHashmapVec { get; set; } diff --git a/core/data/tests/serialize_field_as/output.cs b/core/data/tests/serialize_field_as/output.cs index 038a5506..048d0178 100644 --- a/core/data/tests/serialize_field_as/output.cs +++ b/core/data/tests/serialize_field_as/output.cs @@ -6,7 +6,9 @@ using System.Runtime.Serialization; public class EditItemViewModelSaveRequest { + [JsonProperty(Required = Required.Always)] public string Context { get; set; } + [JsonProperty(Required = Required.Always)] public IEnumerable Values { get; set; } public AutoFillItemActionRequest? FillAction { get; set; } } diff --git a/core/data/tests/smart_pointers/output.cs b/core/data/tests/smart_pointers/output.cs index b3e8470f..23e0a027 100644 --- a/core/data/tests/smart_pointers/output.cs +++ b/core/data/tests/smart_pointers/output.cs @@ -7,37 +7,49 @@ /** This is a comment. */ public class ArcyColors { + [JsonProperty(Required = Required.Always)] public ushort Red { get; set; } + [JsonProperty(Required = Required.Always)] public string Blue { get; set; } + [JsonProperty(Required = Required.Always)] public IEnumerable Green { get; set; } } /** This is a comment. */ public class MutexyColors { + [JsonProperty(Required = Required.Always)] public IEnumerable Blue { get; set; } + [JsonProperty(Required = Required.Always)] public string Green { get; set; } } /** This is a comment. */ public class RcyColors { + [JsonProperty(Required = Required.Always)] public string Red { get; set; } + [JsonProperty(Required = Required.Always)] public IEnumerable Blue { get; set; } + [JsonProperty(Required = Required.Always)] public string Green { get; set; } } /** This is a comment. */ public class CellyColors { + [JsonProperty(Required = Required.Always)] public string Red { get; set; } + [JsonProperty(Required = Required.Always)] public IEnumerable Blue { get; set; } } /** This is a comment. */ public class LockyColors { + [JsonProperty(Required = Required.Always)] public string Red { get; set; } } /** This is a comment. */ public class CowyColors { + [JsonProperty(Required = Required.Always)] public string Lifetime { get; set; } } diff --git a/core/data/tests/test_generate_char/output.cs b/core/data/tests/test_generate_char/output.cs index cb2c1677..83c000b1 100644 --- a/core/data/tests/test_generate_char/output.cs +++ b/core/data/tests/test_generate_char/output.cs @@ -6,6 +6,7 @@ using System.Runtime.Serialization; public class MyType { + [JsonProperty(Required = Required.Always)] public char Field { get; set; } } diff --git a/core/data/tests/test_i54_u53_type/output.cs b/core/data/tests/test_i54_u53_type/output.cs index de49db18..fd0defaa 100644 --- a/core/data/tests/test_i54_u53_type/output.cs +++ b/core/data/tests/test_i54_u53_type/output.cs @@ -6,7 +6,9 @@ using System.Runtime.Serialization; public class Foo { + [JsonProperty(Required = Required.Always)] public long A { get; set; } + [JsonProperty(Required = Required.Always)] public ulong B { get; set; } } diff --git a/core/data/tests/test_serde_iso8601/output.cs b/core/data/tests/test_serde_iso8601/output.cs index 0a8c5123..5bbe3772 100644 --- a/core/data/tests/test_serde_iso8601/output.cs +++ b/core/data/tests/test_serde_iso8601/output.cs @@ -6,6 +6,7 @@ using System.Runtime.Serialization; public class Foo { + [JsonProperty(Required = Required.Always)] public string Time { get; set; } } diff --git a/core/data/tests/test_serde_url/output.cs b/core/data/tests/test_serde_url/output.cs index dde28904..4b1f3f64 100644 --- a/core/data/tests/test_serde_url/output.cs +++ b/core/data/tests/test_serde_url/output.cs @@ -6,6 +6,7 @@ using System.Runtime.Serialization; public class Foo { + [JsonProperty(Required = Required.Always)] public string Url { get; set; } } diff --git a/core/data/tests/use_correct_integer_types/output.cs b/core/data/tests/use_correct_integer_types/output.cs index 3ba7c226..eb0690b6 100644 --- a/core/data/tests/use_correct_integer_types/output.cs +++ b/core/data/tests/use_correct_integer_types/output.cs @@ -7,11 +7,17 @@ /** This is a comment. */ public class Foo { + [JsonProperty(Required = Required.Always)] public short A { get; set; } + [JsonProperty(Required = Required.Always)] public short B { get; set; } + [JsonProperty(Required = Required.Always)] public int C { get; set; } + [JsonProperty(Required = Required.Always)] public ushort E { get; set; } + [JsonProperty(Required = Required.Always)] public ushort F { get; set; } + [JsonProperty(Required = Required.Always)] public uint G { get; set; } } From 8b1498d73d67940e124eaafb5f9ab9af0fa92083 Mon Sep 17 00:00:00 2001 From: Daniel Martinez Date: Thu, 18 Jul 2024 16:50:44 +1000 Subject: [PATCH 3/6] Fix enum property missing allow rename --- .../csharp_without_naming_convention/input.rs | 24 ++++++++++++ .../output.cs | 38 +++++++++++++++++++ core/src/language/csharp.rs | 31 ++++++++++++--- core/tests/snapshot_tests.rs | 6 ++- 4 files changed, 93 insertions(+), 6 deletions(-) create mode 100644 core/data/tests/csharp_without_naming_convention/input.rs create mode 100644 core/data/tests/csharp_without_naming_convention/output.cs diff --git a/core/data/tests/csharp_without_naming_convention/input.rs b/core/data/tests/csharp_without_naming_convention/input.rs new file mode 100644 index 00000000..79cd6c16 --- /dev/null +++ b/core/data/tests/csharp_without_naming_convention/input.rs @@ -0,0 +1,24 @@ +#[typeshare] +#[serde(rename_all = "camelCase")] +pub struct ObjectNamedA { + depends_on: String, + age: i32, + some_string_value: String, +} + +#[typeshare] +#[derive(Serialize, Deserialize)] +#[serde(rename_all = "kebab-case")] +pub enum DimensionFitValue { + WrapContent, + FitHeight, +} + +#[typeshare] +#[derive(Serialize, Deserialize)] +#[serde(tag = "type", content = "value", rename_all = "kebab-case")] +pub enum DimensionValue { + FixedSize(f32), + Percentage(f32), + Fit(DimensionFitValue), +} diff --git a/core/data/tests/csharp_without_naming_convention/output.cs b/core/data/tests/csharp_without_naming_convention/output.cs new file mode 100644 index 00000000..426f3f05 --- /dev/null +++ b/core/data/tests/csharp_without_naming_convention/output.cs @@ -0,0 +1,38 @@ +#nullable enable + +using System.Reflection; +using JsonSubTypes; +using Newtonsoft.Json; +using System.Runtime.Serialization; + +public class ObjectNamedA { + [JsonProperty(Required = Required.Always)] + public string dependsOn { get; set; } + [JsonProperty(Required = Required.Always)] + public int age { get; set; } + [JsonProperty(Required = Required.Always)] + public string someStringValue { get; set; } +} + +public enum DimensionFitValue +{ + [EnumMember(Value = "wrap-content")] + WrapContent, + + [EnumMember(Value = "fit-height")] + FitHeight, + +} + +[JsonConverter(typeof(JsonSubtypes), "type")] +[JsonSubtypes.KnownSubType(typeof(fixed-size), "fixed-size")] +[JsonSubtypes.KnownSubType(typeof(percentage), "percentage")] +[JsonSubtypes.KnownSubType(typeof(fit), "fit")] +public abstract record DimensionValue +{ + public record FixedSize(float Value) : DimensionValue(); + public record Percentage(float Value) : DimensionValue(); + public record Fit(DimensionFitValue Value) : DimensionValue(); +} + + diff --git a/core/src/language/csharp.rs b/core/src/language/csharp.rs index 171d31c1..ae97b453 100644 --- a/core/src/language/csharp.rs +++ b/core/src/language/csharp.rs @@ -179,7 +179,11 @@ impl Language for CSharp { writeln!(w, "\n}}\n") } RustEnum::Algebraic { shared, .. } => { - write_discriminated_union_json_attributes(w, e)?; + write_discriminated_union_json_attributes( + w, + e, + self.without_csharp_naming_convention, + )?; write!( w, "public abstract record {}{} \n{{", @@ -203,7 +207,11 @@ impl Language for CSharp { } } -fn write_discriminated_union_json_attributes(w: &mut dyn Write, e: &RustEnum) -> io::Result<()> { +fn write_discriminated_union_json_attributes( + w: &mut dyn Write, + e: &RustEnum, + with_rename: bool, +) -> io::Result<()> { match e { RustEnum::Algebraic { tag_key, @@ -213,9 +221,11 @@ fn write_discriminated_union_json_attributes(w: &mut dyn Write, e: &RustEnum) -> writeln!(w, "[JsonConverter(typeof(JsonSubtypes), \"{}\")]", tag_key)?; let case_attributes = shared.variants.iter().map(|v| { let case_name = match v { - RustEnumVariant::AnonymousStruct { shared, .. } => &shared.id.original, - RustEnumVariant::Unit(shared) => &shared.id.original, - RustEnumVariant::Tuple { shared, .. } => &shared.id.original, + RustEnumVariant::AnonymousStruct { shared, .. } => { + get_property_name(with_rename, shared) + } + RustEnumVariant::Unit(shared) => get_property_name(with_rename, shared), + RustEnumVariant::Tuple { shared, .. } => get_property_name(with_rename, shared), }; format!( "[JsonSubtypes.KnownSubType(typeof({0}), \"{0}\")]", @@ -229,6 +239,17 @@ fn write_discriminated_union_json_attributes(w: &mut dyn Write, e: &RustEnum) -> } } +fn get_property_name( + with_rename: bool, + shared: &crate::rust_types::RustEnumVariantShared, +) -> &String { + if with_rename { + &shared.id.renamed + } else { + &shared.id.original + } +} + impl CSharp { fn write_enum_variants(&mut self, w: &mut dyn Write, e: &RustEnum) -> io::Result<()> { match e { diff --git a/core/tests/snapshot_tests.rs b/core/tests/snapshot_tests.rs index 6785a6a4..ac8e3a63 100644 --- a/core/tests/snapshot_tests.rs +++ b/core/tests/snapshot_tests.rs @@ -715,7 +715,11 @@ tests! { ]; can_generate_anonymous_struct_with_skipped_fields: [swift, kotlin, scala, typescript, go, python]; generic_struct_with_constraints_and_decorators: [swift { codablevoid_constraints: vec!["Equatable".into()] }]; - excluded_by_target_os: [ swift, kotlin, scala, typescript, go,python ] target_os: ["android", "macos"]; + csharp_without_naming_convention: [csharp { + without_csharp_naming_convention: true, + } + ]; + excluded_by_target_os: [ swift, kotlin, scala, typescript, go, python ] target_os: ["android", "macos"]; // excluded_by_target_os_full_module: [swift] target_os: "ios"; serde_rename_references: [ swift, kotlin, scala, typescript, go ]; } From 4177c84640638ab1e38dc99c4271b4c0eac3b133 Mon Sep 17 00:00:00 2001 From: Daniel Martinez Date: Tue, 23 Jul 2024 16:35:37 +1000 Subject: [PATCH 4/6] Always use renamed property on subtype value position --- .../anonymous_struct_with_rename/output.cs | 6 +-- .../can_generate_algebraic_enum/output.cs | 8 ++-- .../output.cs | 6 +-- .../tests/recursive_enum_decorator/output.cs | 16 +++---- .../output.cs | 8 ++-- core/src/language/csharp.rs | 43 +++++++------------ 6 files changed, 37 insertions(+), 50 deletions(-) diff --git a/core/data/tests/anonymous_struct_with_rename/output.cs b/core/data/tests/anonymous_struct_with_rename/output.cs index 84db6773..cd382f75 100644 --- a/core/data/tests/anonymous_struct_with_rename/output.cs +++ b/core/data/tests/anonymous_struct_with_rename/output.cs @@ -32,9 +32,9 @@ public class AnonymousStructWithRenameKebabCaseInner { } [JsonConverter(typeof(JsonSubtypes), "type")] -[JsonSubtypes.KnownSubType(typeof(List), "List")] -[JsonSubtypes.KnownSubType(typeof(LongFieldNames), "LongFieldNames")] -[JsonSubtypes.KnownSubType(typeof(KebabCase), "KebabCase")] +[JsonSubtypes.KnownSubType(typeof(List), "list")] +[JsonSubtypes.KnownSubType(typeof(LongFieldNames), "longFieldNames")] +[JsonSubtypes.KnownSubType(typeof(KebabCase), "kebabCase")] public abstract record AnonymousStructWithRename { public record list(AnonymousStructWithRenameListInner Content): AnonymousStructWithRename(); diff --git a/core/data/tests/can_generate_algebraic_enum/output.cs b/core/data/tests/can_generate_algebraic_enum/output.cs index 86991424..8f787b0f 100644 --- a/core/data/tests/can_generate_algebraic_enum/output.cs +++ b/core/data/tests/can_generate_algebraic_enum/output.cs @@ -31,10 +31,10 @@ public record ReallyCoolType(ItemDetailsFieldValue Content) : AdvancedColors(); [JsonConverter(typeof(JsonSubtypes), "type")] -[JsonSubtypes.KnownSubType(typeof(String), "String")] -[JsonSubtypes.KnownSubType(typeof(Number), "Number")] -[JsonSubtypes.KnownSubType(typeof(NumberArray), "NumberArray")] -[JsonSubtypes.KnownSubType(typeof(ReallyCoolType), "ReallyCoolType")] +[JsonSubtypes.KnownSubType(typeof(String), "string")] +[JsonSubtypes.KnownSubType(typeof(Number), "number")] +[JsonSubtypes.KnownSubType(typeof(NumberArray), "number-array")] +[JsonSubtypes.KnownSubType(typeof(ReallyCoolType), "really-cool-type")] public abstract record AdvancedColors2 { /** This is a case comment */ diff --git a/core/data/tests/csharp_without_naming_convention/output.cs b/core/data/tests/csharp_without_naming_convention/output.cs index 426f3f05..4f613117 100644 --- a/core/data/tests/csharp_without_naming_convention/output.cs +++ b/core/data/tests/csharp_without_naming_convention/output.cs @@ -25,9 +25,9 @@ public enum DimensionFitValue } [JsonConverter(typeof(JsonSubtypes), "type")] -[JsonSubtypes.KnownSubType(typeof(fixed-size), "fixed-size")] -[JsonSubtypes.KnownSubType(typeof(percentage), "percentage")] -[JsonSubtypes.KnownSubType(typeof(fit), "fit")] +[JsonSubtypes.KnownSubType(typeof(FixedSize), "fixed-size")] +[JsonSubtypes.KnownSubType(typeof(Percentage), "percentage")] +[JsonSubtypes.KnownSubType(typeof(Fit), "fit")] public abstract record DimensionValue { public record FixedSize(float Value) : DimensionValue(); diff --git a/core/data/tests/recursive_enum_decorator/output.cs b/core/data/tests/recursive_enum_decorator/output.cs index a3e581e0..12c16164 100644 --- a/core/data/tests/recursive_enum_decorator/output.cs +++ b/core/data/tests/recursive_enum_decorator/output.cs @@ -6,9 +6,9 @@ using System.Runtime.Serialization; [JsonConverter(typeof(JsonSubtypes), "type")] -[JsonSubtypes.KnownSubType(typeof(Red), "Red")] -[JsonSubtypes.KnownSubType(typeof(Banana), "Banana")] -[JsonSubtypes.KnownSubType(typeof(Vermont), "Vermont")] +[JsonSubtypes.KnownSubType(typeof(Red), "red")] +[JsonSubtypes.KnownSubType(typeof(Banana), "banana")] +[JsonSubtypes.KnownSubType(typeof(Vermont), "vermont")] public abstract record Options { public record Red(bool Content) : Options(); @@ -30,14 +30,14 @@ public class MoreOptionsBuiltInner { } [JsonConverter(typeof(JsonSubtypes), "type")] -[JsonSubtypes.KnownSubType(typeof(News), "News")] -[JsonSubtypes.KnownSubType(typeof(Exactly), "Exactly")] -[JsonSubtypes.KnownSubType(typeof(Built), "Built")] +[JsonSubtypes.KnownSubType(typeof(News), "news")] +[JsonSubtypes.KnownSubType(typeof(Exactly), "exactly")] +[JsonSubtypes.KnownSubType(typeof(Built), "built")] public abstract record MoreOptions { public record News(bool Content) : MoreOptions(); - public record exactly(MoreOptionsExactlyInner Content): MoreOptions(); - public record built(MoreOptionsBuiltInner Content): MoreOptions(); + public record Exactly(MoreOptionsExactlyInner Content): MoreOptions(); + public record Built(MoreOptionsBuiltInner Content): MoreOptions(); } diff --git a/core/data/tests/test_algebraic_enum_case_name_support/output.cs b/core/data/tests/test_algebraic_enum_case_name_support/output.cs index b465ad9c..be6bfd36 100644 --- a/core/data/tests/test_algebraic_enum_case_name_support/output.cs +++ b/core/data/tests/test_algebraic_enum_case_name_support/output.cs @@ -9,10 +9,10 @@ public class ItemDetailsFieldValue { } [JsonConverter(typeof(JsonSubtypes), "type")] -[JsonSubtypes.KnownSubType(typeof(String), "String")] -[JsonSubtypes.KnownSubType(typeof(Number), "Number")] -[JsonSubtypes.KnownSubType(typeof(NumberArray), "NumberArray")] -[JsonSubtypes.KnownSubType(typeof(ReallyCoolType), "ReallyCoolType")] +[JsonSubtypes.KnownSubType(typeof(String), "string")] +[JsonSubtypes.KnownSubType(typeof(Number), "number")] +[JsonSubtypes.KnownSubType(typeof(NumberArray), "number-array")] +[JsonSubtypes.KnownSubType(typeof(ReallyCoolType), "reallyCoolType")] public abstract record AdvancedColors { public record String(string Content) : AdvancedColors(); diff --git a/core/src/language/csharp.rs b/core/src/language/csharp.rs index ae97b453..153a8503 100644 --- a/core/src/language/csharp.rs +++ b/core/src/language/csharp.rs @@ -179,11 +179,7 @@ impl Language for CSharp { writeln!(w, "\n}}\n") } RustEnum::Algebraic { shared, .. } => { - write_discriminated_union_json_attributes( - w, - e, - self.without_csharp_naming_convention, - )?; + write_discriminated_union_json_attributes(w, e)?; write!( w, "public abstract record {}{} \n{{", @@ -207,11 +203,7 @@ impl Language for CSharp { } } -fn write_discriminated_union_json_attributes( - w: &mut dyn Write, - e: &RustEnum, - with_rename: bool, -) -> io::Result<()> { +fn write_discriminated_union_json_attributes(w: &mut dyn Write, e: &RustEnum) -> io::Result<()> { match e { RustEnum::Algebraic { tag_key, @@ -220,16 +212,18 @@ fn write_discriminated_union_json_attributes( } => { writeln!(w, "[JsonConverter(typeof(JsonSubtypes), \"{}\")]", tag_key)?; let case_attributes = shared.variants.iter().map(|v| { - let case_name = match v { + let (case_name, renamed) = match v { RustEnumVariant::AnonymousStruct { shared, .. } => { - get_property_name(with_rename, shared) + (&shared.id.original, &shared.id.renamed) + } + RustEnumVariant::Unit(shared) => (&shared.id.original, &shared.id.renamed), + RustEnumVariant::Tuple { shared, .. } => { + (&shared.id.original, &shared.id.renamed) } - RustEnumVariant::Unit(shared) => get_property_name(with_rename, shared), - RustEnumVariant::Tuple { shared, .. } => get_property_name(with_rename, shared), }; format!( - "[JsonSubtypes.KnownSubType(typeof({0}), \"{0}\")]", - case_name, + "[JsonSubtypes.KnownSubType(typeof({0}), \"{1}\")]", + case_name, renamed ) }); @@ -239,17 +233,6 @@ fn write_discriminated_union_json_attributes( } } -fn get_property_name( - with_rename: bool, - shared: &crate::rust_types::RustEnumVariantShared, -) -> &String { - if with_rename { - &shared.id.renamed - } else { - &shared.id.original - } -} - impl CSharp { fn write_enum_variants(&mut self, w: &mut dyn Write, e: &RustEnum) -> io::Result<()> { match e { @@ -331,7 +314,11 @@ impl CSharp { write!( w, "\tpublic record {}({}{}Inner{} {}): {}{}();", - shared.id.renamed, + if self.without_csharp_naming_convention { + &shared.id.renamed + } else { + &shared.id.original + }, e.shared().id.original, shared.id.original, generics, From 7e96efdd226868187e028b89b1feab4dbe87843f Mon Sep 17 00:00:00 2001 From: Daniel Martinez Date: Thu, 23 Jan 2025 22:31:14 +1100 Subject: [PATCH 5/6] update snapshots The order of elements in the output file has been updated --- .../output.cs | 4 +-- .../tests/recursive_enum_decorator/output.cs | 24 +++++++-------- core/data/tests/smart_pointers/output.cs | 30 +++++++++---------- 3 files changed, 29 insertions(+), 29 deletions(-) diff --git a/core/data/tests/can_recognize_types_inside_modules/output.cs b/core/data/tests/can_recognize_types_inside_modules/output.cs index e631b983..c3fe9447 100644 --- a/core/data/tests/can_recognize_types_inside_modules/output.cs +++ b/core/data/tests/can_recognize_types_inside_modules/output.cs @@ -10,12 +10,12 @@ public class A { public uint Field { get; set; } } -public class ABC { +public class AB { [JsonProperty(Required = Required.Always)] public uint Field { get; set; } } -public class AB { +public class ABC { [JsonProperty(Required = Required.Always)] public uint Field { get; set; } } diff --git a/core/data/tests/recursive_enum_decorator/output.cs b/core/data/tests/recursive_enum_decorator/output.cs index 12c16164..d9ddf7ef 100644 --- a/core/data/tests/recursive_enum_decorator/output.cs +++ b/core/data/tests/recursive_enum_decorator/output.cs @@ -5,18 +5,6 @@ using Newtonsoft.Json; using System.Runtime.Serialization; -[JsonConverter(typeof(JsonSubtypes), "type")] -[JsonSubtypes.KnownSubType(typeof(Red), "red")] -[JsonSubtypes.KnownSubType(typeof(Banana), "banana")] -[JsonSubtypes.KnownSubType(typeof(Vermont), "vermont")] -public abstract record Options -{ - public record Red(bool Content) : Options(); - public record Banana(string Content) : Options(); - public record Vermont(Options Content) : Options(); -} - - /** Generated type representing the anonymous struct variant `Exactly` of the `MoreOptions` Rust enum */ public class MoreOptionsExactlyInner { [JsonProperty(Required = Required.Always)] @@ -41,3 +29,15 @@ public record Built(MoreOptionsBuiltInner Content): MoreOptions(); } +[JsonConverter(typeof(JsonSubtypes), "type")] +[JsonSubtypes.KnownSubType(typeof(Red), "red")] +[JsonSubtypes.KnownSubType(typeof(Banana), "banana")] +[JsonSubtypes.KnownSubType(typeof(Vermont), "vermont")] +public abstract record Options +{ + public record Red(bool Content) : Options(); + public record Banana(string Content) : Options(); + public record Vermont(Options Content) : Options(); +} + + diff --git a/core/data/tests/smart_pointers/output.cs b/core/data/tests/smart_pointers/output.cs index 23e0a027..a40bf963 100644 --- a/core/data/tests/smart_pointers/output.cs +++ b/core/data/tests/smart_pointers/output.cs @@ -16,17 +16,27 @@ public class ArcyColors { } /** This is a comment. */ -public class MutexyColors { +public class CellyColors { + [JsonProperty(Required = Required.Always)] + public string Red { get; set; } [JsonProperty(Required = Required.Always)] public IEnumerable Blue { get; set; } +} + +/** This is a comment. */ +public class CowyColors { [JsonProperty(Required = Required.Always)] - public string Green { get; set; } + public string Lifetime { get; set; } } /** This is a comment. */ -public class RcyColors { +public class LockyColors { [JsonProperty(Required = Required.Always)] public string Red { get; set; } +} + +/** This is a comment. */ +public class MutexyColors { [JsonProperty(Required = Required.Always)] public IEnumerable Blue { get; set; } [JsonProperty(Required = Required.Always)] @@ -34,23 +44,13 @@ public class RcyColors { } /** This is a comment. */ -public class CellyColors { +public class RcyColors { [JsonProperty(Required = Required.Always)] public string Red { get; set; } [JsonProperty(Required = Required.Always)] public IEnumerable Blue { get; set; } -} - -/** This is a comment. */ -public class LockyColors { [JsonProperty(Required = Required.Always)] - public string Red { get; set; } -} - -/** This is a comment. */ -public class CowyColors { - [JsonProperty(Required = Required.Always)] - public string Lifetime { get; set; } + public string Green { get; set; } } /** This is a comment. */ From d379501726360675cea17bb98c3af40205fe70a9 Mon Sep 17 00:00:00 2001 From: Daniel Martinez Date: Thu, 23 Jan 2025 22:42:33 +1100 Subject: [PATCH 6/6] Fix rebasing conflicts Re add CSharp imports and update configuration and Args handling --- cli/src/args.rs | 5 +++++ cli/src/main.rs | 5 +++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/cli/src/args.rs b/cli/src/args.rs index 05b79619..800ad719 100644 --- a/cli/src/args.rs +++ b/cli/src/args.rs @@ -12,6 +12,7 @@ pub enum AvailableLanguage { Go, #[cfg(feature = "python")] Python, + CSharp, } #[derive(clap::Parser)] @@ -49,6 +50,10 @@ pub struct Args { #[arg(long)] pub scala_package: Option, + /// CSharp namespace + #[arg(long)] + pub csharp_namespace: Option, + /// Scala serializer module name #[arg(long)] pub scala_module_name: Option, diff --git a/cli/src/main.rs b/cli/src/main.rs index ca087418..dc96d992 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -27,7 +27,7 @@ use typeshare_core::language::Go; use typeshare_core::language::Python; use typeshare_core::{ context::ParseContext, - language::{CrateName, Kotlin, Language, Scala, SupportedLanguage, Swift, TypeScript}, + language::{CSharp, CrateName, Kotlin, Language, Scala, SupportedLanguage, Swift, TypeScript}, parser::ParsedData, reconcile::reconcile_aliases, }; @@ -95,6 +95,7 @@ fn generate_types(config_file: Option<&Path>, options: &Args) -> anyhow::Result< args::AvailableLanguage::Go => SupportedLanguage::Go, #[cfg(feature = "python")] args::AvailableLanguage::Python => SupportedLanguage::Python, + args::AvailableLanguage::CSharp => SupportedLanguage::CSharp, }, }; @@ -268,7 +269,7 @@ fn override_configuration(mut config: Config, options: &Args) -> anyhow::Result< config.scala.module_name = scala_module_name.to_string(); } - if let Some(csharp_namespace) = options.value_of(ARG_CSHARP_NAMESPACE) { + if let Some(csharp_namespace) = options.csharp_namespace.as_ref() { config.csharp.namespace = csharp_namespace.to_string(); }