diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..b3f1b30 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,201 @@ +# EditorConfig is awesome: http://editorconfig.org +root = true + +[*.{csproj,props,targets}] +charset = utf-8-bom +end_of_line = crlf +insert_final_newline = true +indent_style = space +indent_size = 2 +trim_trailing_whitespace = true + +[g_*.cs] +generated_code = true + +[*.cs] +end_of_line = crlf +insert_final_newline = true +indent_style = space +indent_size = 4 +trim_trailing_whitespace = true + +#license header +file_header_template = Copyright (c) 2026 SynesthesiaDev . Licensed under the MIT Licence.\nSee the LICENCE file in the repository root for full licence text. + +#Roslyn naming styles + +#PascalCase for public and protected members +dotnet_naming_style.pascalcase.capitalization = pascal_case +dotnet_naming_symbols.public_members.applicable_accessibilities = public,internal,protected,protected_internal,private_protected +dotnet_naming_symbols.public_members.applicable_kinds = property,method,field,event +dotnet_naming_rule.public_members_pascalcase.severity = error +dotnet_naming_rule.public_members_pascalcase.symbols = public_members +dotnet_naming_rule.public_members_pascalcase.style = pascalcase + +#camelCase for private members +dotnet_naming_style.camelcase.capitalization = camel_case + +dotnet_naming_symbols.private_members.applicable_accessibilities = private +dotnet_naming_symbols.private_members.applicable_kinds = property,method,field,event +dotnet_naming_rule.private_members_camelcase.severity = warning +dotnet_naming_rule.private_members_camelcase.symbols = private_members +dotnet_naming_rule.private_members_camelcase.style = camelcase + +dotnet_naming_symbols.local_function.applicable_kinds = local_function +dotnet_naming_rule.local_function_camelcase.severity = warning +dotnet_naming_rule.local_function_camelcase.symbols = local_function +dotnet_naming_rule.local_function_camelcase.style = camelcase + +#all_lower for private and local constants/static readonlys +dotnet_naming_style.all_lower.capitalization = all_lower +dotnet_naming_style.all_lower.word_separator = _ + +dotnet_naming_symbols.private_constants.applicable_accessibilities = private +dotnet_naming_symbols.private_constants.required_modifiers = const +dotnet_naming_symbols.private_constants.applicable_kinds = field +dotnet_naming_rule.private_const_all_lower.severity = warning +dotnet_naming_rule.private_const_all_lower.symbols = private_constants +dotnet_naming_rule.private_const_all_lower.style = all_lower + +dotnet_naming_symbols.private_static_readonly.applicable_accessibilities = private +dotnet_naming_symbols.private_static_readonly.required_modifiers = static,readonly +dotnet_naming_symbols.private_static_readonly.applicable_kinds = field +dotnet_naming_rule.private_static_readonly_all_lower.severity = warning +dotnet_naming_rule.private_static_readonly_all_lower.symbols = private_static_readonly +dotnet_naming_rule.private_static_readonly_all_lower.style = all_lower + +dotnet_naming_symbols.local_constants.applicable_kinds = local +dotnet_naming_symbols.local_constants.required_modifiers = const +dotnet_naming_rule.local_const_all_lower.severity = warning +dotnet_naming_rule.local_const_all_lower.symbols = local_constants +dotnet_naming_rule.local_const_all_lower.style = all_lower + +#ALL_UPPER for non private constants/static readonlys +dotnet_naming_style.all_upper.capitalization = all_upper +dotnet_naming_style.all_upper.word_separator = _ + +dotnet_naming_symbols.public_constants.applicable_accessibilities = public,internal,protected,protected_internal,private_protected +dotnet_naming_symbols.public_constants.required_modifiers = const +dotnet_naming_symbols.public_constants.applicable_kinds = field +dotnet_naming_rule.public_const_all_upper.severity = warning +dotnet_naming_rule.public_const_all_upper.symbols = public_constants +dotnet_naming_rule.public_const_all_upper.style = all_upper + +dotnet_naming_symbols.public_static_readonly.applicable_accessibilities = public,internal,protected,protected_internal,private_protected +dotnet_naming_symbols.public_static_readonly.required_modifiers = static,readonly +dotnet_naming_symbols.public_static_readonly.applicable_kinds = field +dotnet_naming_rule.public_static_readonly_all_upper.severity = warning +dotnet_naming_rule.public_static_readonly_all_upper.symbols = public_static_readonly +dotnet_naming_rule.public_static_readonly_all_upper.style = all_upper + +#Roslyn formating options + +#Formatting - indentation options +csharp_indent_case_contents = true +csharp_indent_case_contents_when_block = false +csharp_indent_labels = one_less_than_current +csharp_indent_switch_labels = true + +#Formatting - new line options +csharp_new_line_before_catch = true +csharp_new_line_before_else = true +csharp_new_line_before_finally = true +csharp_new_line_before_open_brace = all +#csharp_new_line_before_members_in_anonymous_types = true +#csharp_new_line_before_members_in_object_initializers = true # Currently no effect in VS/dotnet format (16.4), and makes Rider confusing +csharp_new_line_between_query_expression_clauses = true + +#Formatting - organize using options +dotnet_sort_system_directives_first = true + +#Formatting - spacing options +csharp_space_after_cast = false +csharp_space_after_colon_in_inheritance_clause = true +csharp_space_after_keywords_in_control_flow_statements = true +csharp_space_before_colon_in_inheritance_clause = true +csharp_space_between_method_call_empty_parameter_list_parentheses = false +csharp_space_between_method_call_name_and_opening_parenthesis = false +csharp_space_between_method_call_parameter_list_parentheses = false +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false +csharp_space_between_method_declaration_parameter_list_parentheses = false + +#Formatting - wrapping options +csharp_preserve_single_line_blocks = true +csharp_preserve_single_line_statements = true + +#Roslyn language styles + +#Style - this. qualification +dotnet_style_qualification_for_field = false:warning +dotnet_style_qualification_for_property = false:warning +dotnet_style_qualification_for_method = false:warning +dotnet_style_qualification_for_event = false:warning + +#Style - type names +dotnet_style_predefined_type_for_locals_parameters_members = true:warning +dotnet_style_predefined_type_for_member_access = true:warning +csharp_style_var_when_type_is_apparent = true:none +csharp_style_var_for_built_in_types = true:none +csharp_style_var_elsewhere = true:silent + +#Style - modifiers +dotnet_style_require_accessibility_modifiers = for_non_interface_members:warning +csharp_preferred_modifier_order = public,private,protected,internal,new,abstract,virtual,sealed,override,static,readonly,extern,unsafe,volatile,async:warning + +#Style - parentheses +# Skipped because roslyn cannot separate +-*/ with << >> + +#Style - expression bodies +csharp_style_expression_bodied_accessors = true:warning +csharp_style_expression_bodied_constructors = false:none +csharp_style_expression_bodied_indexers = true:warning +csharp_style_expression_bodied_methods = false:silent +csharp_style_expression_bodied_operators = true:warning +csharp_style_expression_bodied_properties = true:warning +csharp_style_expression_bodied_local_functions = true:silent + +#Style - expression preferences +dotnet_style_object_initializer = true:warning +dotnet_style_collection_initializer = true:warning +dotnet_style_prefer_inferred_anonymous_type_member_names = true:warning +dotnet_style_prefer_auto_properties = true:warning +dotnet_style_prefer_conditional_expression_over_assignment = true:silent +dotnet_style_prefer_conditional_expression_over_return = true:silent +dotnet_style_prefer_compound_assignment = true:warning + +#Style - null/type checks +dotnet_style_coalesce_expression = true:warning +dotnet_style_null_propagation = true:warning +csharp_style_pattern_matching_over_is_with_cast_check = true:warning +csharp_style_pattern_matching_over_as_with_null_check = true:warning +csharp_style_throw_expression = true:silent +csharp_style_conditional_delegate_call = true:warning + +#Style - unused +dotnet_style_readonly_field = true:silent +dotnet_code_quality_unused_parameters = non_public:silent +csharp_style_unused_value_expression_statement_preference = discard_variable:silent +csharp_style_unused_value_assignment_preference = discard_variable:warning + +#Style - variable declaration +csharp_style_inlined_variable_declaration = true:warning +csharp_style_deconstructed_variable_declaration = true:warning + +#Style - other C# 7.x features +dotnet_style_prefer_inferred_tuple_names = true:warning +csharp_prefer_simple_default_expression = true:warning +csharp_style_pattern_local_over_anonymous_function = true:warning +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:silent + +#Style - C# 8 features +csharp_prefer_static_local_function = true:warning +csharp_prefer_simple_using_statement = true:silent +csharp_style_prefer_index_operator = true:warning +csharp_style_prefer_range_operator = true:warning +csharp_style_prefer_switch_expression = false:none + +[*.{yaml,yml}] +insert_final_newline = true +indent_style = space +indent_size = 2 +trim_trailing_whitespace = true diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index 217f7cb..e81639e 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -5,9 +5,9 @@ name: .NET on: push: - branches: [ "main" ] + branches: [ "*" ] pull_request: - branches: [ "main" ] + branches: [ "*" ] jobs: build: diff --git a/.github/workflows/nuget.yml b/.github/workflows/nuget.yml new file mode 100644 index 0000000..48d8685 --- /dev/null +++ b/.github/workflows/nuget.yml @@ -0,0 +1,29 @@ +name: "Deploy to NuGet" + +on: + push: + branches: [ "main" ] + +env: + PACKAGE_OUTPUT_DIR: ${{ github.workspace }}\output + +jobs: + deploy: + name: 'Deploy' + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 8.0.x + - name: Restore dependencies + run: dotnet restore + - name: Build + run: dotnet build --no-restore --configuration Release + - name: Pack + run: dotnet pack --no-restore --no-build --configuration Release --include-symbols --output ${{ env.PACKAGE_OUTPUT_DIR }} + + - name: Push to NuGet + run: dotnet nuget push ${{ env.PACKAGE_OUTPUT_DIR }}\*.nupkg -k ${{ secrets.NUGET_AUTH_TOKEN }} -s https://api.nuget.org/v3/index.json diff --git a/Codon.BinaryCodec/Codon.BinaryCodec.csproj b/Codon.BinaryCodec/Codon.BinaryCodec.csproj index 84312a2..3090db3 100644 --- a/Codon.BinaryCodec/Codon.BinaryCodec.csproj +++ b/Codon.BinaryCodec/Codon.BinaryCodec.csproj @@ -21,4 +21,8 @@ + + + + diff --git a/Codon.BinaryCodec/IBinaryCodec.cs b/Codon.BinaryCodec/IBinaryCodec.cs index 73f38df..ef37abd 100644 --- a/Codon.BinaryCodec/IBinaryCodec.cs +++ b/Codon.BinaryCodec/IBinaryCodec.cs @@ -306,36 +306,36 @@ Func f public interface IBinaryCodec { - public void Write(BinaryBuffer buffer, T value); - public T Read(BinaryBuffer buffer); + void Write(BinaryBuffer buffer, T value); + T Read(BinaryBuffer buffer); - public BinaryCodecs.OptionalBinaryCodec Optional() + BinaryCodecs.OptionalBinaryCodec Optional() { return new BinaryCodecs.OptionalBinaryCodec(this); } - public BinaryCodecs.DefaultBinaryCodec Default(T defaultValue) + BinaryCodecs.DefaultBinaryCodec Default(T defaultValue) { return new BinaryCodecs.DefaultBinaryCodec(this, defaultValue); } - public BinaryCodecs.TransformativeBinaryCodec Transform(Func from, Func to) + BinaryCodecs.TransformativeBinaryCodec Transform(Func from, Func to) { return new BinaryCodecs.TransformativeBinaryCodec(this, from, to); } - public BinaryCodecs.DictionaryBinaryCodec MapTo(IBinaryCodec valueCodec) where V : notnull + BinaryCodecs.DictionaryBinaryCodec MapTo(IBinaryCodec valueCodec) where V : notnull { return new BinaryCodecs.DictionaryBinaryCodec(this, valueCodec); } - public BinaryCodecs.ListBinaryCodec List() + BinaryCodecs.ListBinaryCodec List() { return new BinaryCodecs.ListBinaryCodec(this); } - public BinaryCodecs.UnionBinaryCodec Union(Func> serializerFactory, Func keyFunc) where K : notnull + BinaryCodecs.UnionBinaryCodec Union(Func> serializerFactory, Func keyFunc) where K : notnull { return new BinaryCodecs.UnionBinaryCodec(this, keyFunc, serializerFactory); } -} \ No newline at end of file +} diff --git a/Codon.Buffer/Codon.Buffer.csproj b/Codon.Buffer/Codon.Buffer.csproj index 89845d9..d6c9d9c 100644 --- a/Codon.Buffer/Codon.Buffer.csproj +++ b/Codon.Buffer/Codon.Buffer.csproj @@ -16,4 +16,8 @@ Codon.Buffer + + + + diff --git a/Codon.Codec/Codecs.cs b/Codon.Codec/Codecs.cs index 083492d..59dc9d2 100644 --- a/Codon.Codec/Codecs.cs +++ b/Codon.Codec/Codecs.cs @@ -108,6 +108,11 @@ public Optional Decode(ITranscoder transcoder, D value) { try { + var nullValue = transcoder.EncodeNull(); + + if (EqualityComparer.Default.Equals(value, nullValue)) + return Optional.Empty(); + var decoded = Inner.Decode(transcoder, value); return new Optional(decoded); } @@ -256,4 +261,4 @@ public T Decode(ITranscoder transcoder, D value) return Delegate.Value.Decode(transcoder, value); } } -} \ No newline at end of file +} diff --git a/Codon.Codec/Codon.Codec.csproj b/Codon.Codec/Codon.Codec.csproj index f4d1391..6a895b5 100644 --- a/Codon.Codec/Codon.Codec.csproj +++ b/Codon.Codec/Codon.Codec.csproj @@ -19,4 +19,8 @@ + + + + diff --git a/Codon.Codec/ICodec.cs b/Codon.Codec/ICodec.cs index 9f979ed..6158efc 100644 --- a/Codon.Codec/ICodec.cs +++ b/Codon.Codec/ICodec.cs @@ -5,41 +5,41 @@ namespace Codon.Codec; public interface ICodec where T : notnull { - public D Encode(ITranscoder transcoder, T value); - public T Decode(ITranscoder transcoder, D value); + D Encode(ITranscoder transcoder, T value); + T Decode(ITranscoder transcoder, D value); - public ICodec> Optional() + ICodec> Optional() { return new Codecs.OptionalCodec(this); } - public ICodec Default(T value) + ICodec Default(T value) { return new Codecs.DefaultCodec(this, value); } - public ICodec> List() + ICodec> List() { return new Codecs.ListCodec(this); } - public ICodec> MapTo(ICodec valueCodec) where V : notnull + ICodec> MapTo(ICodec valueCodec) where V : notnull { return new Codecs.MapCodec(this, valueCodec); } - public ICodec ForwardRef() + ICodec ForwardRef() { return new Codecs.ForwardRefCodec(() => this); } - public ICodec Transform(Func to, Func from) where S : notnull + ICodec Transform(Func to, Func from) where S : notnull { return new Codecs.TransformativeCodec(this, to, from); } - public StructCodec Union(string keyField, Func> serializers, Func keyFunc) where R : notnull + StructCodec Union(string keyField, Func> serializers, Func keyFunc) where R : notnull { return new Codecs.UnionCodec(keyField, this, serializers, keyFunc); } -} \ No newline at end of file +} diff --git a/Codon.Codec/Json/JsonExtensions.cs b/Codon.Codec/Json/JsonExtensions.cs index 5157cd7..f63a75b 100644 --- a/Codon.Codec/Json/JsonExtensions.cs +++ b/Codon.Codec/Json/JsonExtensions.cs @@ -1,7 +1,7 @@ using System.Text.Json; using System.Text.Json.Nodes; -namespace Codon.Codec; +namespace Codon.Codec.Json; public static class JsonExtensions { @@ -16,4 +16,4 @@ public static class JsonExtensions var json = element.GetRawText(); return JsonNode.Parse(json)?.AsArray(); } -} \ No newline at end of file +} diff --git a/Codon.Codec/Json/JsonTranscoder.cs b/Codon.Codec/Json/JsonTranscoder.cs index 1f286a8..cff069c 100644 --- a/Codon.Codec/Json/JsonTranscoder.cs +++ b/Codon.Codec/Json/JsonTranscoder.cs @@ -1,18 +1,20 @@ using System.Text.Json; using System.Text.Json.Nodes; +using Codon.Codec.Transcoder; -namespace Codon.Codec.Transcoder.Transcoders; +namespace Codon.Codec.Json; public class JsonTranscoder : ITranscoder { - private static readonly JsonDocumentOptions DocumentOptions = new() { AllowTrailingCommas = true }; - public static JsonTranscoder Instance = new JsonTranscoder(); + private static readonly JsonDocumentOptions document_options = new() { AllowTrailingCommas = true }; - private static JsonElement SerializeValue(T? value) + public static readonly JsonTranscoder INSTANCE = new(); + + private static JsonElement serializeValue(T? value) { var bytes = JsonSerializer.SerializeToUtf8Bytes(value); - using var document = JsonDocument.Parse(bytes, DocumentOptions); + using var document = JsonDocument.Parse(bytes, document_options); return document.RootElement.Clone(); } @@ -26,7 +28,7 @@ public IListBuilder Add(JsonElement value) public JsonElement Build() { - return SerializeValue(array); + return serializeValue(array); } } @@ -46,7 +48,7 @@ public IVirtualMapBuilder Put(string key, JsonElement value) public JsonElement Build() { - return SerializeValue(jsonObject); + return serializeValue(jsonObject); } } @@ -66,18 +68,18 @@ public bool HasValue(string key) public JsonElement GetValue(string key) { - return jsonObject == null ? throw new InvalidOperationException("JsonElement is not JsonObject") : SerializeValue(jsonObject.ToDictionary()[key]); + return jsonObject == null ? throw new InvalidOperationException("JsonElement is not JsonObject") : serializeValue(jsonObject.ToDictionary()[key]); } } public JsonElement EncodeNull() { - return SerializeValue(null); + return serializeValue(null); } public JsonElement EncodeBool(bool value) { - return SerializeValue(value); + return serializeValue(value); } public bool DecodeBool(JsonElement value) @@ -87,7 +89,7 @@ public bool DecodeBool(JsonElement value) public JsonElement EncodeByte(byte value) { - return SerializeValue(value); + return serializeValue(value); } public byte DecodeByte(JsonElement value) @@ -97,7 +99,7 @@ public byte DecodeByte(JsonElement value) public JsonElement EncodeShort(short value) { - return SerializeValue(value); + return serializeValue(value); } public short DecodeShort(JsonElement value) @@ -107,7 +109,7 @@ public short DecodeShort(JsonElement value) public JsonElement EncodeInt(int value) { - return SerializeValue(value); + return serializeValue(value); } public int DecodeInt(JsonElement value) @@ -117,7 +119,7 @@ public int DecodeInt(JsonElement value) public JsonElement EncodeLong(long value) { - return SerializeValue(value); + return serializeValue(value); } public long DecodeLong(JsonElement value) @@ -127,7 +129,7 @@ public long DecodeLong(JsonElement value) public JsonElement EncodeFloat(float value) { - return SerializeValue(value); + return serializeValue(value); } public float DecodeFloat(JsonElement value) @@ -137,7 +139,7 @@ public float DecodeFloat(JsonElement value) public JsonElement EncodeDouble(double value) { - return SerializeValue(value); + return serializeValue(value); } public double DecodeDouble(JsonElement value) @@ -147,7 +149,7 @@ public double DecodeDouble(JsonElement value) public JsonElement EncodeString(string value) { - return SerializeValue(value); + return serializeValue(value); } public string DecodeString(JsonElement value) @@ -233,4 +235,4 @@ public IVirtualMap DecodeMap(JsonElement value) { return new JsonVirtualMap(value); } -} \ No newline at end of file +} diff --git a/Codon.Codec/PrimitiveCodec.cs b/Codon.Codec/PrimitiveCodec.cs index 01fc4ca..8f6ae46 100644 --- a/Codon.Codec/PrimitiveCodec.cs +++ b/Codon.Codec/PrimitiveCodec.cs @@ -2,15 +2,15 @@ namespace Codon.Codec; -public class PrimitiveCodec(Func Encoder, Func Decoder) : ICodec +public class PrimitiveCodec(Func encoder, Func decoder) : ICodec { public D Encode(ITranscoder transcoder, A value) { - return (D)Encoder.Invoke((dynamic)transcoder, value); + return (D)encoder.Invoke((dynamic)transcoder, value); } public A Decode(ITranscoder transcoder, D value) { - return Decoder.Invoke((dynamic)transcoder, value); + return decoder.Invoke((dynamic)transcoder, value); } } diff --git a/Codon.Codec/StructCodec.cs b/Codon.Codec/StructCodec.cs index fceb5df..de3af95 100644 --- a/Codon.Codec/StructCodec.cs +++ b/Codon.Codec/StructCodec.cs @@ -1,4 +1,3 @@ -using System.Linq.Expressions; using Codon.Codec.Transcoder; using Codon.Optionals; @@ -249,7 +248,7 @@ Func : ICodec { - public const string Inline = "_inline_"; + public const string INLINE = "_inline_"; public abstract T EncodeToMap(ITranscoder transcoder, R value, IVirtualMapBuilder mapBuilder); @@ -265,9 +264,9 @@ public R Decode(ITranscoder transcoder, D value) return DecodeFromMap(transcoder, transcoder.DecodeMap(value)); } - private static D Put(ITranscoder transcoder, ICodec codec, IVirtualMapBuilder mapBuilder, string key, T value) + private static D put(ITranscoder transcoder, ICodec codec, IVirtualMapBuilder mapBuilder, string key, T value) { - if (key == Inline) + if (key == INLINE) { var encodeCodec = codec switch { @@ -287,7 +286,7 @@ private static D Put(ITranscoder transcoder, ICodec codec, IVirtualM public static T Get(ITranscoder transcoder, ICodec codec, string key, IVirtualMap map) { - if (key == Inline) + if (key == INLINE) { var decodeCodec = codec switch { @@ -361,7 +360,7 @@ public class StructCodec1P( { public override T EncodeToMap(ITranscoder transcoder, Result value, IVirtualMapBuilder mapBuilder) { - Put(transcoder, codec1, mapBuilder, name1, getter1.Invoke(value)); + put(transcoder, codec1, mapBuilder, name1, getter1.Invoke(value)); return mapBuilder.Build(); } @@ -380,8 +379,8 @@ public class StructCodec2P( { public override T EncodeToMap(ITranscoder transcoder, Result value, IVirtualMapBuilder mapBuilder) { - Put(transcoder, codec1, mapBuilder, name1, getter1.Invoke(value)); - Put(transcoder, codec2, mapBuilder, name2, getter2.Invoke(value)); + put(transcoder, codec1, mapBuilder, name1, getter1.Invoke(value)); + put(transcoder, codec2, mapBuilder, name2, getter2.Invoke(value)); return mapBuilder.Build(); } @@ -402,9 +401,9 @@ public class StructCodec3P( { public override T EncodeToMap(ITranscoder transcoder, Result value, IVirtualMapBuilder mapBuilder) { - Put(transcoder, codec1, mapBuilder, name1, getter1.Invoke(value)); - Put(transcoder, codec2, mapBuilder, name2, getter2.Invoke(value)); - Put(transcoder, codec3, mapBuilder, name3, getter3.Invoke(value)); + put(transcoder, codec1, mapBuilder, name1, getter1.Invoke(value)); + put(transcoder, codec2, mapBuilder, name2, getter2.Invoke(value)); + put(transcoder, codec3, mapBuilder, name3, getter3.Invoke(value)); return mapBuilder.Build(); } @@ -427,10 +426,10 @@ public class StructCodec4P( { public override T EncodeToMap(ITranscoder transcoder, Result value, IVirtualMapBuilder mapBuilder) { - Put(transcoder, codec1, mapBuilder, name1, getter1.Invoke(value)); - Put(transcoder, codec2, mapBuilder, name2, getter2.Invoke(value)); - Put(transcoder, codec3, mapBuilder, name3, getter3.Invoke(value)); - Put(transcoder, codec4, mapBuilder, name4, getter4.Invoke(value)); + put(transcoder, codec1, mapBuilder, name1, getter1.Invoke(value)); + put(transcoder, codec2, mapBuilder, name2, getter2.Invoke(value)); + put(transcoder, codec3, mapBuilder, name3, getter3.Invoke(value)); + put(transcoder, codec4, mapBuilder, name4, getter4.Invoke(value)); return mapBuilder.Build(); } @@ -455,11 +454,11 @@ public class StructCodec5P( { public override T EncodeToMap(ITranscoder transcoder, Result value, IVirtualMapBuilder mapBuilder) { - Put(transcoder, codec1, mapBuilder, name1, getter1.Invoke(value)); - Put(transcoder, codec2, mapBuilder, name2, getter2.Invoke(value)); - Put(transcoder, codec3, mapBuilder, name3, getter3.Invoke(value)); - Put(transcoder, codec4, mapBuilder, name4, getter4.Invoke(value)); - Put(transcoder, codec5, mapBuilder, name5, getter5.Invoke(value)); + put(transcoder, codec1, mapBuilder, name1, getter1.Invoke(value)); + put(transcoder, codec2, mapBuilder, name2, getter2.Invoke(value)); + put(transcoder, codec3, mapBuilder, name3, getter3.Invoke(value)); + put(transcoder, codec4, mapBuilder, name4, getter4.Invoke(value)); + put(transcoder, codec5, mapBuilder, name5, getter5.Invoke(value)); return mapBuilder.Build(); } @@ -486,12 +485,12 @@ public class StructCodec6P( { public override T EncodeToMap(ITranscoder transcoder, Result value, IVirtualMapBuilder mapBuilder) { - Put(transcoder, codec1, mapBuilder, name1, getter1.Invoke(value)); - Put(transcoder, codec2, mapBuilder, name2, getter2.Invoke(value)); - Put(transcoder, codec3, mapBuilder, name3, getter3.Invoke(value)); - Put(transcoder, codec4, mapBuilder, name4, getter4.Invoke(value)); - Put(transcoder, codec5, mapBuilder, name5, getter5.Invoke(value)); - Put(transcoder, codec6, mapBuilder, name6, getter6.Invoke(value)); + put(transcoder, codec1, mapBuilder, name1, getter1.Invoke(value)); + put(transcoder, codec2, mapBuilder, name2, getter2.Invoke(value)); + put(transcoder, codec3, mapBuilder, name3, getter3.Invoke(value)); + put(transcoder, codec4, mapBuilder, name4, getter4.Invoke(value)); + put(transcoder, codec5, mapBuilder, name5, getter5.Invoke(value)); + put(transcoder, codec6, mapBuilder, name6, getter6.Invoke(value)); return mapBuilder.Build(); } @@ -520,13 +519,13 @@ public class StructCodec7P( { public override T EncodeToMap(ITranscoder transcoder, Result value, IVirtualMapBuilder mapBuilder) { - Put(transcoder, codec1, mapBuilder, name1, getter1.Invoke(value)); - Put(transcoder, codec2, mapBuilder, name2, getter2.Invoke(value)); - Put(transcoder, codec3, mapBuilder, name3, getter3.Invoke(value)); - Put(transcoder, codec4, mapBuilder, name4, getter4.Invoke(value)); - Put(transcoder, codec5, mapBuilder, name5, getter5.Invoke(value)); - Put(transcoder, codec6, mapBuilder, name6, getter6.Invoke(value)); - Put(transcoder, codec7, mapBuilder, name7, getter7.Invoke(value)); + put(transcoder, codec1, mapBuilder, name1, getter1.Invoke(value)); + put(transcoder, codec2, mapBuilder, name2, getter2.Invoke(value)); + put(transcoder, codec3, mapBuilder, name3, getter3.Invoke(value)); + put(transcoder, codec4, mapBuilder, name4, getter4.Invoke(value)); + put(transcoder, codec5, mapBuilder, name5, getter5.Invoke(value)); + put(transcoder, codec6, mapBuilder, name6, getter6.Invoke(value)); + put(transcoder, codec7, mapBuilder, name7, getter7.Invoke(value)); return mapBuilder.Build(); } @@ -557,14 +556,14 @@ public class StructCodec8P( { public override T EncodeToMap(ITranscoder transcoder, Result value, IVirtualMapBuilder mapBuilder) { - Put(transcoder, codec1, mapBuilder, name1, getter1.Invoke(value)); - Put(transcoder, codec2, mapBuilder, name2, getter2.Invoke(value)); - Put(transcoder, codec3, mapBuilder, name3, getter3.Invoke(value)); - Put(transcoder, codec4, mapBuilder, name4, getter4.Invoke(value)); - Put(transcoder, codec5, mapBuilder, name5, getter5.Invoke(value)); - Put(transcoder, codec6, mapBuilder, name6, getter6.Invoke(value)); - Put(transcoder, codec7, mapBuilder, name7, getter7.Invoke(value)); - Put(transcoder, codec8, mapBuilder, name8, getter8.Invoke(value)); + put(transcoder, codec1, mapBuilder, name1, getter1.Invoke(value)); + put(transcoder, codec2, mapBuilder, name2, getter2.Invoke(value)); + put(transcoder, codec3, mapBuilder, name3, getter3.Invoke(value)); + put(transcoder, codec4, mapBuilder, name4, getter4.Invoke(value)); + put(transcoder, codec5, mapBuilder, name5, getter5.Invoke(value)); + put(transcoder, codec6, mapBuilder, name6, getter6.Invoke(value)); + put(transcoder, codec7, mapBuilder, name7, getter7.Invoke(value)); + put(transcoder, codec8, mapBuilder, name8, getter8.Invoke(value)); return mapBuilder.Build(); } @@ -597,15 +596,15 @@ public class StructCodec9P( { public override T EncodeToMap(ITranscoder transcoder, Result value, IVirtualMapBuilder mapBuilder) { - Put(transcoder, codec1, mapBuilder, name1, getter1.Invoke(value)); - Put(transcoder, codec2, mapBuilder, name2, getter2.Invoke(value)); - Put(transcoder, codec3, mapBuilder, name3, getter3.Invoke(value)); - Put(transcoder, codec4, mapBuilder, name4, getter4.Invoke(value)); - Put(transcoder, codec5, mapBuilder, name5, getter5.Invoke(value)); - Put(transcoder, codec6, mapBuilder, name6, getter6.Invoke(value)); - Put(transcoder, codec7, mapBuilder, name7, getter7.Invoke(value)); - Put(transcoder, codec8, mapBuilder, name8, getter8.Invoke(value)); - Put(transcoder, codec9, mapBuilder, name9, getter9.Invoke(value)); + put(transcoder, codec1, mapBuilder, name1, getter1.Invoke(value)); + put(transcoder, codec2, mapBuilder, name2, getter2.Invoke(value)); + put(transcoder, codec3, mapBuilder, name3, getter3.Invoke(value)); + put(transcoder, codec4, mapBuilder, name4, getter4.Invoke(value)); + put(transcoder, codec5, mapBuilder, name5, getter5.Invoke(value)); + put(transcoder, codec6, mapBuilder, name6, getter6.Invoke(value)); + put(transcoder, codec7, mapBuilder, name7, getter7.Invoke(value)); + put(transcoder, codec8, mapBuilder, name8, getter8.Invoke(value)); + put(transcoder, codec9, mapBuilder, name9, getter9.Invoke(value)); return mapBuilder.Build(); } @@ -640,16 +639,16 @@ public class StructCodec10P( { public override T EncodeToMap(ITranscoder transcoder, Result value, IVirtualMapBuilder mapBuilder) { - Put(transcoder, codec1, mapBuilder, name1, getter1.Invoke(value)); - Put(transcoder, codec2, mapBuilder, name2, getter2.Invoke(value)); - Put(transcoder, codec3, mapBuilder, name3, getter3.Invoke(value)); - Put(transcoder, codec4, mapBuilder, name4, getter4.Invoke(value)); - Put(transcoder, codec5, mapBuilder, name5, getter5.Invoke(value)); - Put(transcoder, codec6, mapBuilder, name6, getter6.Invoke(value)); - Put(transcoder, codec7, mapBuilder, name7, getter7.Invoke(value)); - Put(transcoder, codec8, mapBuilder, name8, getter8.Invoke(value)); - Put(transcoder, codec9, mapBuilder, name9, getter9.Invoke(value)); - Put(transcoder, codec10, mapBuilder, name10, getter10.Invoke(value)); + put(transcoder, codec1, mapBuilder, name1, getter1.Invoke(value)); + put(transcoder, codec2, mapBuilder, name2, getter2.Invoke(value)); + put(transcoder, codec3, mapBuilder, name3, getter3.Invoke(value)); + put(transcoder, codec4, mapBuilder, name4, getter4.Invoke(value)); + put(transcoder, codec5, mapBuilder, name5, getter5.Invoke(value)); + put(transcoder, codec6, mapBuilder, name6, getter6.Invoke(value)); + put(transcoder, codec7, mapBuilder, name7, getter7.Invoke(value)); + put(transcoder, codec8, mapBuilder, name8, getter8.Invoke(value)); + put(transcoder, codec9, mapBuilder, name9, getter9.Invoke(value)); + put(transcoder, codec10, mapBuilder, name10, getter10.Invoke(value)); return mapBuilder.Build(); } @@ -686,17 +685,17 @@ public class StructCodec11P(ITranscoder transcoder, Result value, IVirtualMapBuilder mapBuilder) { - Put(transcoder, codec1, mapBuilder, name1, getter1.Invoke(value)); - Put(transcoder, codec2, mapBuilder, name2, getter2.Invoke(value)); - Put(transcoder, codec3, mapBuilder, name3, getter3.Invoke(value)); - Put(transcoder, codec4, mapBuilder, name4, getter4.Invoke(value)); - Put(transcoder, codec5, mapBuilder, name5, getter5.Invoke(value)); - Put(transcoder, codec6, mapBuilder, name6, getter6.Invoke(value)); - Put(transcoder, codec7, mapBuilder, name7, getter7.Invoke(value)); - Put(transcoder, codec8, mapBuilder, name8, getter8.Invoke(value)); - Put(transcoder, codec9, mapBuilder, name9, getter9.Invoke(value)); - Put(transcoder, codec10, mapBuilder, name10, getter10.Invoke(value)); - Put(transcoder, codec11, mapBuilder, name11, getter11.Invoke(value)); + put(transcoder, codec1, mapBuilder, name1, getter1.Invoke(value)); + put(transcoder, codec2, mapBuilder, name2, getter2.Invoke(value)); + put(transcoder, codec3, mapBuilder, name3, getter3.Invoke(value)); + put(transcoder, codec4, mapBuilder, name4, getter4.Invoke(value)); + put(transcoder, codec5, mapBuilder, name5, getter5.Invoke(value)); + put(transcoder, codec6, mapBuilder, name6, getter6.Invoke(value)); + put(transcoder, codec7, mapBuilder, name7, getter7.Invoke(value)); + put(transcoder, codec8, mapBuilder, name8, getter8.Invoke(value)); + put(transcoder, codec9, mapBuilder, name9, getter9.Invoke(value)); + put(transcoder, codec10, mapBuilder, name10, getter10.Invoke(value)); + put(transcoder, codec11, mapBuilder, name11, getter11.Invoke(value)); return mapBuilder.Build(); } @@ -735,18 +734,18 @@ public class StructCodec12P(ITranscoder transcoder, Result value, IVirtualMapBuilder mapBuilder) { - Put(transcoder, codec1, mapBuilder, name1, getter1.Invoke(value)); - Put(transcoder, codec2, mapBuilder, name2, getter2.Invoke(value)); - Put(transcoder, codec3, mapBuilder, name3, getter3.Invoke(value)); - Put(transcoder, codec4, mapBuilder, name4, getter4.Invoke(value)); - Put(transcoder, codec5, mapBuilder, name5, getter5.Invoke(value)); - Put(transcoder, codec6, mapBuilder, name6, getter6.Invoke(value)); - Put(transcoder, codec7, mapBuilder, name7, getter7.Invoke(value)); - Put(transcoder, codec8, mapBuilder, name8, getter8.Invoke(value)); - Put(transcoder, codec9, mapBuilder, name9, getter9.Invoke(value)); - Put(transcoder, codec10, mapBuilder, name10, getter10.Invoke(value)); - Put(transcoder, codec11, mapBuilder, name11, getter11.Invoke(value)); - Put(transcoder, codec12, mapBuilder, name12, getter12.Invoke(value)); + put(transcoder, codec1, mapBuilder, name1, getter1.Invoke(value)); + put(transcoder, codec2, mapBuilder, name2, getter2.Invoke(value)); + put(transcoder, codec3, mapBuilder, name3, getter3.Invoke(value)); + put(transcoder, codec4, mapBuilder, name4, getter4.Invoke(value)); + put(transcoder, codec5, mapBuilder, name5, getter5.Invoke(value)); + put(transcoder, codec6, mapBuilder, name6, getter6.Invoke(value)); + put(transcoder, codec7, mapBuilder, name7, getter7.Invoke(value)); + put(transcoder, codec8, mapBuilder, name8, getter8.Invoke(value)); + put(transcoder, codec9, mapBuilder, name9, getter9.Invoke(value)); + put(transcoder, codec10, mapBuilder, name10, getter10.Invoke(value)); + put(transcoder, codec11, mapBuilder, name11, getter11.Invoke(value)); + put(transcoder, codec12, mapBuilder, name12, getter12.Invoke(value)); return mapBuilder.Build(); } @@ -787,19 +786,19 @@ public class StructCodec13P(ITranscoder transcoder, Result value, IVirtualMapBuilder mapBuilder) { - Put(transcoder, codec1, mapBuilder, name1, getter1.Invoke(value)); - Put(transcoder, codec2, mapBuilder, name2, getter2.Invoke(value)); - Put(transcoder, codec3, mapBuilder, name3, getter3.Invoke(value)); - Put(transcoder, codec4, mapBuilder, name4, getter4.Invoke(value)); - Put(transcoder, codec5, mapBuilder, name5, getter5.Invoke(value)); - Put(transcoder, codec6, mapBuilder, name6, getter6.Invoke(value)); - Put(transcoder, codec7, mapBuilder, name7, getter7.Invoke(value)); - Put(transcoder, codec8, mapBuilder, name8, getter8.Invoke(value)); - Put(transcoder, codec9, mapBuilder, name9, getter9.Invoke(value)); - Put(transcoder, codec10, mapBuilder, name10, getter10.Invoke(value)); - Put(transcoder, codec11, mapBuilder, name11, getter11.Invoke(value)); - Put(transcoder, codec12, mapBuilder, name12, getter12.Invoke(value)); - Put(transcoder, codec13, mapBuilder, name13, getter13.Invoke(value)); + put(transcoder, codec1, mapBuilder, name1, getter1.Invoke(value)); + put(transcoder, codec2, mapBuilder, name2, getter2.Invoke(value)); + put(transcoder, codec3, mapBuilder, name3, getter3.Invoke(value)); + put(transcoder, codec4, mapBuilder, name4, getter4.Invoke(value)); + put(transcoder, codec5, mapBuilder, name5, getter5.Invoke(value)); + put(transcoder, codec6, mapBuilder, name6, getter6.Invoke(value)); + put(transcoder, codec7, mapBuilder, name7, getter7.Invoke(value)); + put(transcoder, codec8, mapBuilder, name8, getter8.Invoke(value)); + put(transcoder, codec9, mapBuilder, name9, getter9.Invoke(value)); + put(transcoder, codec10, mapBuilder, name10, getter10.Invoke(value)); + put(transcoder, codec11, mapBuilder, name11, getter11.Invoke(value)); + put(transcoder, codec12, mapBuilder, name12, getter12.Invoke(value)); + put(transcoder, codec13, mapBuilder, name13, getter13.Invoke(value)); return mapBuilder.Build(); } @@ -842,20 +841,20 @@ public class StructCodec14P(ITranscoder transcoder, Result value, IVirtualMapBuilder mapBuilder) { - Put(transcoder, codec1, mapBuilder, name1, getter1.Invoke(value)); - Put(transcoder, codec2, mapBuilder, name2, getter2.Invoke(value)); - Put(transcoder, codec3, mapBuilder, name3, getter3.Invoke(value)); - Put(transcoder, codec4, mapBuilder, name4, getter4.Invoke(value)); - Put(transcoder, codec5, mapBuilder, name5, getter5.Invoke(value)); - Put(transcoder, codec6, mapBuilder, name6, getter6.Invoke(value)); - Put(transcoder, codec7, mapBuilder, name7, getter7.Invoke(value)); - Put(transcoder, codec8, mapBuilder, name8, getter8.Invoke(value)); - Put(transcoder, codec9, mapBuilder, name9, getter9.Invoke(value)); - Put(transcoder, codec10, mapBuilder, name10, getter10.Invoke(value)); - Put(transcoder, codec11, mapBuilder, name11, getter11.Invoke(value)); - Put(transcoder, codec12, mapBuilder, name12, getter12.Invoke(value)); - Put(transcoder, codec13, mapBuilder, name13, getter13.Invoke(value)); - Put(transcoder, codec14, mapBuilder, name14, getter14.Invoke(value)); + put(transcoder, codec1, mapBuilder, name1, getter1.Invoke(value)); + put(transcoder, codec2, mapBuilder, name2, getter2.Invoke(value)); + put(transcoder, codec3, mapBuilder, name3, getter3.Invoke(value)); + put(transcoder, codec4, mapBuilder, name4, getter4.Invoke(value)); + put(transcoder, codec5, mapBuilder, name5, getter5.Invoke(value)); + put(transcoder, codec6, mapBuilder, name6, getter6.Invoke(value)); + put(transcoder, codec7, mapBuilder, name7, getter7.Invoke(value)); + put(transcoder, codec8, mapBuilder, name8, getter8.Invoke(value)); + put(transcoder, codec9, mapBuilder, name9, getter9.Invoke(value)); + put(transcoder, codec10, mapBuilder, name10, getter10.Invoke(value)); + put(transcoder, codec11, mapBuilder, name11, getter11.Invoke(value)); + put(transcoder, codec12, mapBuilder, name12, getter12.Invoke(value)); + put(transcoder, codec13, mapBuilder, name13, getter13.Invoke(value)); + put(transcoder, codec14, mapBuilder, name14, getter14.Invoke(value)); return mapBuilder.Build(); } @@ -900,21 +899,21 @@ public class StructCodec15P(ITranscoder transcoder, Result value, IVirtualMapBuilder mapBuilder) { - Put(transcoder, codec1, mapBuilder, name1, getter1.Invoke(value)); - Put(transcoder, codec2, mapBuilder, name2, getter2.Invoke(value)); - Put(transcoder, codec3, mapBuilder, name3, getter3.Invoke(value)); - Put(transcoder, codec4, mapBuilder, name4, getter4.Invoke(value)); - Put(transcoder, codec5, mapBuilder, name5, getter5.Invoke(value)); - Put(transcoder, codec6, mapBuilder, name6, getter6.Invoke(value)); - Put(transcoder, codec7, mapBuilder, name7, getter7.Invoke(value)); - Put(transcoder, codec8, mapBuilder, name8, getter8.Invoke(value)); - Put(transcoder, codec9, mapBuilder, name9, getter9.Invoke(value)); - Put(transcoder, codec10, mapBuilder, name10, getter10.Invoke(value)); - Put(transcoder, codec11, mapBuilder, name11, getter11.Invoke(value)); - Put(transcoder, codec12, mapBuilder, name12, getter12.Invoke(value)); - Put(transcoder, codec13, mapBuilder, name13, getter13.Invoke(value)); - Put(transcoder, codec14, mapBuilder, name14, getter14.Invoke(value)); - Put(transcoder, codec15, mapBuilder, name15, getter15.Invoke(value)); + put(transcoder, codec1, mapBuilder, name1, getter1.Invoke(value)); + put(transcoder, codec2, mapBuilder, name2, getter2.Invoke(value)); + put(transcoder, codec3, mapBuilder, name3, getter3.Invoke(value)); + put(transcoder, codec4, mapBuilder, name4, getter4.Invoke(value)); + put(transcoder, codec5, mapBuilder, name5, getter5.Invoke(value)); + put(transcoder, codec6, mapBuilder, name6, getter6.Invoke(value)); + put(transcoder, codec7, mapBuilder, name7, getter7.Invoke(value)); + put(transcoder, codec8, mapBuilder, name8, getter8.Invoke(value)); + put(transcoder, codec9, mapBuilder, name9, getter9.Invoke(value)); + put(transcoder, codec10, mapBuilder, name10, getter10.Invoke(value)); + put(transcoder, codec11, mapBuilder, name11, getter11.Invoke(value)); + put(transcoder, codec12, mapBuilder, name12, getter12.Invoke(value)); + put(transcoder, codec13, mapBuilder, name13, getter13.Invoke(value)); + put(transcoder, codec14, mapBuilder, name14, getter14.Invoke(value)); + put(transcoder, codec15, mapBuilder, name15, getter15.Invoke(value)); return mapBuilder.Build(); } @@ -961,22 +960,22 @@ public class StructCodec16P(ITranscoder transcoder, Result value, IVirtualMapBuilder mapBuilder) { - Put(transcoder, codec1, mapBuilder, name1, getter1.Invoke(value)); - Put(transcoder, codec2, mapBuilder, name2, getter2.Invoke(value)); - Put(transcoder, codec3, mapBuilder, name3, getter3.Invoke(value)); - Put(transcoder, codec4, mapBuilder, name4, getter4.Invoke(value)); - Put(transcoder, codec5, mapBuilder, name5, getter5.Invoke(value)); - Put(transcoder, codec6, mapBuilder, name6, getter6.Invoke(value)); - Put(transcoder, codec7, mapBuilder, name7, getter7.Invoke(value)); - Put(transcoder, codec8, mapBuilder, name8, getter8.Invoke(value)); - Put(transcoder, codec9, mapBuilder, name9, getter9.Invoke(value)); - Put(transcoder, codec10, mapBuilder, name10, getter10.Invoke(value)); - Put(transcoder, codec11, mapBuilder, name11, getter11.Invoke(value)); - Put(transcoder, codec12, mapBuilder, name12, getter12.Invoke(value)); - Put(transcoder, codec13, mapBuilder, name13, getter13.Invoke(value)); - Put(transcoder, codec14, mapBuilder, name14, getter14.Invoke(value)); - Put(transcoder, codec15, mapBuilder, name15, getter15.Invoke(value)); - Put(transcoder, codec16, mapBuilder, name16, getter16.Invoke(value)); + put(transcoder, codec1, mapBuilder, name1, getter1.Invoke(value)); + put(transcoder, codec2, mapBuilder, name2, getter2.Invoke(value)); + put(transcoder, codec3, mapBuilder, name3, getter3.Invoke(value)); + put(transcoder, codec4, mapBuilder, name4, getter4.Invoke(value)); + put(transcoder, codec5, mapBuilder, name5, getter5.Invoke(value)); + put(transcoder, codec6, mapBuilder, name6, getter6.Invoke(value)); + put(transcoder, codec7, mapBuilder, name7, getter7.Invoke(value)); + put(transcoder, codec8, mapBuilder, name8, getter8.Invoke(value)); + put(transcoder, codec9, mapBuilder, name9, getter9.Invoke(value)); + put(transcoder, codec10, mapBuilder, name10, getter10.Invoke(value)); + put(transcoder, codec11, mapBuilder, name11, getter11.Invoke(value)); + put(transcoder, codec12, mapBuilder, name12, getter12.Invoke(value)); + put(transcoder, codec13, mapBuilder, name13, getter13.Invoke(value)); + put(transcoder, codec14, mapBuilder, name14, getter14.Invoke(value)); + put(transcoder, codec15, mapBuilder, name15, getter15.Invoke(value)); + put(transcoder, codec16, mapBuilder, name16, getter16.Invoke(value)); return mapBuilder.Build(); } @@ -1001,4 +1000,4 @@ public override Result DecodeFromMap(ITranscoder transcoder, IVirtualMap { - public IListBuilder Add(T value); - public T Build(); -} \ No newline at end of file + IListBuilder Add(T value); + T Build(); +} diff --git a/Codon.Codec/Transcoder/ITranscoder.cs b/Codon.Codec/Transcoder/ITranscoder.cs index c434f5b..87f3290 100644 --- a/Codon.Codec/Transcoder/ITranscoder.cs +++ b/Codon.Codec/Transcoder/ITranscoder.cs @@ -2,41 +2,41 @@ namespace Codon.Codec.Transcoder; public interface ITranscoder { - public T EncodeNull(); + T EncodeNull(); - public T EncodeBool(bool value); - public bool DecodeBool(T value); + T EncodeBool(bool value); + bool DecodeBool(T value); - public T EncodeByte(byte value); - public byte DecodeByte(T value); + T EncodeByte(byte value); + byte DecodeByte(T value); - public T EncodeShort(short value); - public short DecodeShort(T value); + T EncodeShort(short value); + short DecodeShort(T value); - public T EncodeInt(int value); - public int DecodeInt(T value); + T EncodeInt(int value); + int DecodeInt(T value); - public T EncodeLong(long value); - public long DecodeLong(T value); + T EncodeLong(long value); + long DecodeLong(T value); - public T EncodeFloat(float value); - public float DecodeFloat(T value); + T EncodeFloat(float value); + float DecodeFloat(T value); - public T EncodeDouble(double value); - public double DecodeDouble(T value); + T EncodeDouble(double value); + double DecodeDouble(T value); - public T EncodeString(string value); - public string DecodeString(T value); + T EncodeString(string value); + string DecodeString(T value); - public IListBuilder EncodeList(int size); - public List DecodeList(T value); + IListBuilder EncodeList(int size); + List DecodeList(T value); - public IVirtualMapBuilder EncodeMap(); - public IVirtualMap DecodeMap(T value); + IVirtualMapBuilder EncodeMap(); + IVirtualMap DecodeMap(T value); - public T EmptyMap() => EncodeMap().Build(); + T EmptyMap() => EncodeMap().Build(); - public T EncodeByteArray(byte[] array) + T EncodeByteArray(byte[] array) { var list = EncodeList(array.Length); foreach (var b in array) @@ -47,14 +47,14 @@ public T EncodeByteArray(byte[] array) return list.Build(); } - public byte[] DecodeByteArray(T value) + byte[] DecodeByteArray(T value) { var list = new List(); DecodeList(value).ForEach(b => list.Add(DecodeByte(b))); return list.ToArray(); } - public T EncodeIntArray(int[] array) + T EncodeIntArray(int[] array) { var list = EncodeList(array.Length); foreach (var i in array) @@ -65,14 +65,14 @@ public T EncodeIntArray(int[] array) return list.Build(); } - public int[] DecodeIntArray(T value) + int[] DecodeIntArray(T value) { var list = new List(); DecodeList(value).ForEach(b => list.Add(DecodeInt(b))); return list.ToArray(); } - public T EncodeLongArray(long[] array) + T EncodeLongArray(long[] array) { var list = EncodeList(array.Length); foreach (var l in array) @@ -83,10 +83,10 @@ public T EncodeLongArray(long[] array) return list.Build(); } - public long[] DecodeLongArray(T value) + long[] DecodeLongArray(T value) { var list = new List(); DecodeList(value).ForEach(b => list.Add(DecodeLong(b))); return list.ToArray(); } -} \ No newline at end of file +} diff --git a/Codon.Codec/Transcoder/IVirtualMap.cs b/Codon.Codec/Transcoder/IVirtualMap.cs index 688b6d4..2db135f 100644 --- a/Codon.Codec/Transcoder/IVirtualMap.cs +++ b/Codon.Codec/Transcoder/IVirtualMap.cs @@ -2,10 +2,10 @@ namespace Codon.Codec.Transcoder; public interface IVirtualMap { - public List GetKeys(); - public bool HasValue(string key); - public T GetValue(string key); + List GetKeys(); + bool HasValue(string key); + T GetValue(string key); - public int Count => GetKeys().Count; - public bool IsEmpty => GetKeys().Count == 0; -} \ No newline at end of file + int Count => GetKeys().Count; + bool IsEmpty => GetKeys().Count == 0; +} diff --git a/Codon.Codec/Transcoder/IVirtualMapBuilder.cs b/Codon.Codec/Transcoder/IVirtualMapBuilder.cs index e81a503..0fc2c15 100644 --- a/Codon.Codec/Transcoder/IVirtualMapBuilder.cs +++ b/Codon.Codec/Transcoder/IVirtualMapBuilder.cs @@ -2,7 +2,7 @@ namespace Codon.Codec.Transcoder; public interface IVirtualMapBuilder { - public IVirtualMapBuilder Put(T key, T value); - public IVirtualMapBuilder Put(string key, T value); - public T Build(); -} \ No newline at end of file + IVirtualMapBuilder Put(T key, T value); + IVirtualMapBuilder Put(string key, T value); + T Build(); +} diff --git a/Codon.Codec/Versioned/SchemaMigration.cs b/Codon.Codec/Versioned/SchemaMigration.cs new file mode 100644 index 0000000..54dc372 --- /dev/null +++ b/Codon.Codec/Versioned/SchemaMigration.cs @@ -0,0 +1,9 @@ +using Codon.Codec.Transcoder; + +namespace Codon.Codec.Versioned; + +public class SchemaMigration +{ + public required int Version { get; init; } + public required Action, IVirtualMap, IVirtualMapBuilder> ApplyMethod { get; init; } +} diff --git a/Codon.Codec/Versioned/SchemaMigrationRegistry.cs b/Codon.Codec/Versioned/SchemaMigrationRegistry.cs new file mode 100644 index 0000000..90045c0 --- /dev/null +++ b/Codon.Codec/Versioned/SchemaMigrationRegistry.cs @@ -0,0 +1,33 @@ +// Copyright (c) 2026 SynesthesiaDev . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +namespace Codon.Codec.Versioned; + +public class SchemaMigrationRegistry +{ + public static SchemaMigrationRegistry Builder() => new(); + + + private readonly Dictionary typeToObject = new(); + + public SchemaMigrationRegistry For(Action.Builder> migrations) + { + var builder = SchemaMigrations.Builder(); + migrations.Invoke(builder); + Add(builder.Build()); + return this; + } + + public void Add(SchemaMigrations migrations) + { + typeToObject[typeof(D)] = migrations; + } + + public SchemaMigrations Get() + { + if (typeToObject.TryGetValue(typeof(D), out var boxed) && boxed is SchemaMigrations typed) + return typed; + + throw new KeyNotFoundException($"Migration for type {typeof(D)} was not found!"); + } +} diff --git a/Codon.Codec/Versioned/SchemaMigrations.cs b/Codon.Codec/Versioned/SchemaMigrations.cs new file mode 100644 index 0000000..c0e00c4 --- /dev/null +++ b/Codon.Codec/Versioned/SchemaMigrations.cs @@ -0,0 +1,44 @@ +// Copyright (c) 2026 SynesthesiaDev . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Codon.Codec.Transcoder; + +namespace Codon.Codec.Versioned; + +public static class SchemaMigrations +{ + public static SchemaMigrations Empty() => new SchemaMigrations.Builder().Build(); + + public static SchemaMigrations.Builder Builder() => new(); + +} + +public class SchemaMigrations +{ + public required IReadOnlyDictionary> Migrations { get; init; } + + + public class Builder + { + private readonly Dictionary> migrations = new(); + + public Builder Add(int version, Action, IVirtualMap, IVirtualMapBuilder> applyMethod) + { + migrations.Add(version, new SchemaMigration + { + Version = version, + ApplyMethod = applyMethod + }); + + return this; + } + + public SchemaMigrations Build() + { + return new SchemaMigrations + { + Migrations = migrations, + }; + } + } +} diff --git a/Codon.Codec/Versioned/VersionedStructCodec.cs b/Codon.Codec/Versioned/VersionedStructCodec.cs new file mode 100644 index 0000000..c30cf55 --- /dev/null +++ b/Codon.Codec/Versioned/VersionedStructCodec.cs @@ -0,0 +1,54 @@ +using Codon.Codec.Transcoder; + +namespace Codon.Codec.Versioned; + + +public sealed class VersionedStructCodec +{ + private const string schema_version_key = "_schemaVersion"; + + public required int CurrentSchemaVersion { get; init; } + + public required StructCodec InnerCodec { get; init; } + + public required SchemaMigrationRegistry SchemaMigrationRegistry { get; init; } + + // Apply any data migrations before actually decoding + public R Decode(ITranscoder transcoder, D value) + { + var map = transcoder.DecodeMap(value); + var schemaVersion = map.HasValue(schema_version_key) ? transcoder.DecodeInt(map.GetValue(schema_version_key)) : 0; + + if (schemaVersion > CurrentSchemaVersion) throw new InvalidOperationException($"Value has higher schema version ({schemaVersion}) than currently known ({CurrentSchemaVersion})"); + + var migrations = SchemaMigrationRegistry.Get(); + + while (schemaVersion < CurrentSchemaVersion) + { + schemaVersion++; + + if (!migrations.Migrations.TryGetValue(schemaVersion, out var migration)) + throw new KeyNotFoundException($"Missing migration for schema version {schemaVersion}."); + + var output = transcoder.EncodeMap(); + + foreach (var key in map.GetKeys()) + output.Put(key, map.GetValue(key)); + + migration.ApplyMethod.Invoke(transcoder, map, output); + + var migrated = output.Build(); + map = transcoder.DecodeMap(migrated); + } + + return InnerCodec.DecodeFromMap(transcoder, map); + } + + public D Encode(ITranscoder transcoder, R value) + { + var mapBuilder = transcoder.EncodeMap(); + mapBuilder.Put(schema_version_key, transcoder.EncodeInt(CurrentSchemaVersion)); + + return InnerCodec.EncodeToMap(transcoder, value, mapBuilder); + } +} diff --git a/Codon.Codon/Codon.Codon.csproj b/Codon.Codon/Codon.Codon.csproj index 3400690..a17d364 100644 --- a/Codon.Codon/Codon.Codon.csproj +++ b/Codon.Codon/Codon.Codon.csproj @@ -20,4 +20,8 @@ + + + + diff --git a/Codon.IniTranscoder/Codon.IniTranscoder.csproj b/Codon.IniTranscoder/Codon.IniTranscoder.csproj index 430447c..37bdd72 100644 --- a/Codon.IniTranscoder/Codon.IniTranscoder.csproj +++ b/Codon.IniTranscoder/Codon.IniTranscoder.csproj @@ -16,8 +16,11 @@ - + + + + diff --git a/Codon.IniTranscoder/Elements/IIniElement.cs b/Codon.IniTranscoder/Elements/IIniElement.cs index 51da286..3c7d9ee 100644 --- a/Codon.IniTranscoder/Elements/IIniElement.cs +++ b/Codon.IniTranscoder/Elements/IIniElement.cs @@ -4,22 +4,22 @@ namespace Codon.IniTranscoder.Elements; public interface IIniElement { - public IniValue GetAsValueOrThrow() + IniValue GetAsValueOrThrow() { if (this is IniValue) return (IniValue)this; throw new ParsingException("IniElement is not IniValue"); } - public IniSection GetAsSectionOrThrow() + IniSection GetAsSectionOrThrow() { if (this is IniSection) return (IniSection)this; throw new ParsingException("IniElement is not IniSection"); } - public IniList GetAsListOrThrow() + IniList GetAsListOrThrow() { if (this is IniList) return (IniList)this; throw new ParsingException("IniElement is not IniList"); } -} \ No newline at end of file +} diff --git a/Codon.IniTranscoder/Elements/IniValue.cs b/Codon.IniTranscoder/Elements/IniValue.cs index a5ba54e..a6d89f3 100644 --- a/Codon.IniTranscoder/Elements/IniValue.cs +++ b/Codon.IniTranscoder/Elements/IniValue.cs @@ -7,13 +7,18 @@ public class IniValue(string? value) : IIniElement { public static IniValue Null => new(null); - public string? Value => value; + public readonly string? Value = value == "null" ? null : value; public override string ToString() { return Value ?? "null"; } + // public static IniValue Parse(string? value) + // { + // return value == "null" ? new IniValue(null) : new IniValue(value); + // } + public T? GetOrNull() { if (Value == null) return default; @@ -37,4 +42,4 @@ public T GetOrThrow() { return GetOrNull() ?? throw new ParsingException("Value is null or has invalid type"); } -} \ No newline at end of file +} diff --git a/Codon.IniTranscoder/IniTranscoder.cs b/Codon.IniTranscoder/IniTranscoder.cs index f5b83dc..f71e8f0 100644 --- a/Codon.IniTranscoder/IniTranscoder.cs +++ b/Codon.IniTranscoder/IniTranscoder.cs @@ -1,3 +1,4 @@ +using System.Globalization; using Codon.Codec.Transcoder; using Codon.IniTranscoder.Elements; using Codon.IniTranscoder.Exceptions; @@ -6,13 +7,13 @@ namespace Codon.IniTranscoder; public class IniTranscoder : ITranscoder { - public static readonly IniTranscoder Instance = new(); + public static readonly IniTranscoder INSTANCE = new(); - private string? _nextSectionName; + private string? nextSectionName; public IniTranscoder Named(string name) { - _nextSectionName = name; + nextSectionName = name; return this; } @@ -140,7 +141,7 @@ public long DecodeLong(IIniElement value) public IIniElement EncodeFloat(float value) { - return new IniValue(value.ToString()); + return new IniValue(value.ToString(CultureInfo.InvariantCulture)); } public float DecodeFloat(IIniElement value) @@ -150,7 +151,7 @@ public float DecodeFloat(IIniElement value) public IIniElement EncodeDouble(double value) { - return new IniValue(value.ToString()); + return new IniValue(value.ToString(CultureInfo.InvariantCulture)); } public double DecodeDouble(IIniElement value) @@ -190,8 +191,8 @@ public List DecodeList(IIniElement value) public IVirtualMapBuilder EncodeMap() { - var section = new IniSection(_nextSectionName, []); - _nextSectionName = null; + var section = new IniSection(nextSectionName, []); + nextSectionName = null; return new IniVirtualMapBuilder(section); } @@ -199,4 +200,4 @@ public IVirtualMap DecodeMap(IIniElement value) { return new IniVirtualMap(value.GetAsSectionOrThrow()); } -} \ No newline at end of file +} diff --git a/Codon.Optionals/Codon.Optionals.csproj b/Codon.Optionals/Codon.Optionals.csproj index e8cdec4..41faee7 100644 --- a/Codon.Optionals/Codon.Optionals.csproj +++ b/Codon.Optionals/Codon.Optionals.csproj @@ -16,4 +16,8 @@ Codon.Optionals + + + + diff --git a/Codon.Tests/BinaryCodecTests.cs b/Codon.Tests/BinaryCodecTests.cs index 9f453b4..d1b4aef 100644 --- a/Codon.Tests/BinaryCodecTests.cs +++ b/Codon.Tests/BinaryCodecTests.cs @@ -1,6 +1,5 @@ using Codon.Buffer; using Codon.Binary; -using Codon.Codec; using Codon.Optionals; namespace Codon.Tests; diff --git a/Codon.Tests/CodecTests.cs b/Codon.Tests/CodecTests.cs index 9d572c6..4d93400 100644 --- a/Codon.Tests/CodecTests.cs +++ b/Codon.Tests/CodecTests.cs @@ -1,6 +1,6 @@ using System.Text.Json; using Codon.Codec; -using Codon.Codec.Transcoder.Transcoders; +using Codon.Codec.Json; using Codon.Optionals; namespace Codon.Tests; @@ -24,22 +24,22 @@ public record Person(string name, int age, Optional isAwesome) public void TestCodec() { var person = new Person("Silly Billy", 18, Optional.Of(true)); - var encoded = Person.Codec.Encode(JsonTranscoder.Instance, person); + var encoded = Person.Codec.Encode(JsonTranscoder.INSTANCE, person); Console.WriteLine(encoded.GetRawText()); // {"name":"Silly Billy","age":18,"is_awesome":true} - var decoded = Person.Codec.Decode(JsonTranscoder.Instance, encoded); + var decoded = Person.Codec.Decode(JsonTranscoder.INSTANCE, encoded); Console.WriteLine(decoded); // Person { name = Silly Billy, age = 18, isAwesome = True } - Assert.AreEqual(person, decoded); + Assert.That(decoded, Is.EqualTo(person)); } [Test] public void TestDecodeFromString() { - var decoded = Person.Codec.Decode(JsonTranscoder.Instance, JsonDocument.Parse(jeson).RootElement); + var decoded = Person.Codec.Decode(JsonTranscoder.INSTANCE, JsonDocument.Parse(jeson).RootElement); Assert.That(decoded.name, Is.EqualTo("Synesthesia Dev")); Assert.That(decoded.age, Is.EqualTo(20)); - Assert.AreEqual(decoded.isAwesome, Optional.Empty()); + Assert.That(Optional.Empty(), Is.EqualTo(decoded.isAwesome)); } -} \ No newline at end of file +} diff --git a/Codon.Tests/Codon.Tests.csproj b/Codon.Tests/Codon.Tests.csproj index 165275f..fe8dedd 100644 --- a/Codon.Tests/Codon.Tests.csproj +++ b/Codon.Tests/Codon.Tests.csproj @@ -15,6 +15,7 @@ + diff --git a/Codon.Tests/EdgeCasesTests.cs b/Codon.Tests/EdgeCasesTests.cs new file mode 100644 index 0000000..540b20d --- /dev/null +++ b/Codon.Tests/EdgeCasesTests.cs @@ -0,0 +1,22 @@ +// Copyright (c) 2026 SynesthesiaDev . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Codon.Codec; +using Codon.IniTranscoder.Elements; + +namespace Codon.Tests; + +public class EdgeCasesTests +{ + private readonly Codecs.OptionalCodec optionalString = new(Codecs.String); + + [Test] + public void Test() + { + var decoded = optionalString.Decode(IniTranscoder.IniTranscoder.INSTANCE, IniValue.Null); + var decodedStringifiedNull = optionalString.Decode(IniTranscoder.IniTranscoder.INSTANCE, new IniValue("null")); + + Assert.That(decoded.IsMissing, Is.True); + Assert.That(decodedStringifiedNull.IsMissing, Is.True); + } +} diff --git a/Codon.Tests/IniParserTests.cs b/Codon.Tests/IniParserTests.cs index d18f72e..d6296cb 100644 --- a/Codon.Tests/IniParserTests.cs +++ b/Codon.Tests/IniParserTests.cs @@ -1,14 +1,14 @@ using Codon.Codec; using Codon.IniTranscoder.Elements; using Codon.IniTranscoder.Util; -using Codon.Tests.Util; +using SynesthesiaUtil.Extensions; namespace Codon.Tests; public class IniParserTests { - private static string SectionTestText = AssemblyUtil.GetTextResource(AssemblyInfo.TestAssembly, "Codon.Tests.Resources.section_test.ini"); - private static string SectionTestWithoutHeaderText = AssemblyUtil.GetTextResource(AssemblyInfo.TestAssembly, "Codon.Tests.Resources.section_test_without_header.ini"); + private static readonly string section_test_text = AssemblyInfo.TestAssembly.GetTextResource("Codon.Tests.Resources.section_test.ini"); + private static readonly string section_test_without_header_text = AssemblyInfo.TestAssembly.GetTextResource("Codon.Tests.Resources.section_test_without_header.ini"); public record User(string Name, int Age, bool IsTester, List Properties) { @@ -26,7 +26,7 @@ public record User(string Name, int Age, bool IsTester, List Properties) public void TestTranscoder() { var user = new User("Synesthesia", 20, false, ["is_trans", "is_pan"]); - var ini = User.Codec.Encode(IniTranscoder.IniTranscoder.Instance, user); + var ini = User.Codec.Encode(IniTranscoder.IniTranscoder.INSTANCE, user); Console.WriteLine(ini); } @@ -46,24 +46,24 @@ public void TestListToString() var newList = IniList.Parse(stringified); Console.WriteLine(newList.ToString()); - Assert.AreEqual(stringified, newList.ToString()); + Assert.That(newList.ToString(), Is.EqualTo(stringified)); } [Test] public void TestSectionToString() { - var section = IniSection.Parse(SectionTestText); + var section = IniSection.Parse(section_test_text); var stringified = section.ToString(); var newSection = IniSection.Parse(stringified); Console.WriteLine(stringified); - Assert.AreEqual(stringified, newSection.ToString()); + Assert.That(newSection.ToString(), Is.EqualTo(stringified)); } [Test] public void TestParseSection() { - var section = IniSection.Parse(SectionTestText); + var section = IniSection.Parse(section_test_text); foreach (var iniKeyValuePair in section.Values) { Console.WriteLine(iniKeyValuePair); @@ -82,7 +82,7 @@ public void TestParseSection() [Test] public void TestParseSectionWithoutHeader() { - var section = IniSection.Parse(SectionTestWithoutHeaderText); + var section = IniSection.Parse(section_test_without_header_text); foreach (var iniKeyValuePair in section.Values) { Console.WriteLine(iniKeyValuePair); @@ -102,4 +102,4 @@ private static void AssertEquals(IniValue iniValue, string? value) { Assert.That(iniValue.Value, Is.EqualTo(value)); } -} \ No newline at end of file +} diff --git a/Codon.Tests/IniTranscoderTests.cs b/Codon.Tests/IniTranscoderTests.cs index c9fed8f..19b1e93 100644 --- a/Codon.Tests/IniTranscoderTests.cs +++ b/Codon.Tests/IniTranscoderTests.cs @@ -5,7 +5,7 @@ namespace Codon.Tests; public class IniTranscoderTests { - private IniTranscoder.IniTranscoder T => IniTranscoder.IniTranscoder.Instance; + private IniTranscoder.IniTranscoder T => IniTranscoder.IniTranscoder.INSTANCE; [Test] public void EncodeNull_ProducesNullValue() diff --git a/Codon.Tests/JsonTranscoderTests.cs b/Codon.Tests/JsonTranscoderTests.cs index 7ddc76d..bafabe3 100644 --- a/Codon.Tests/JsonTranscoderTests.cs +++ b/Codon.Tests/JsonTranscoderTests.cs @@ -1,7 +1,5 @@ using System.Text.Json; -using System.Text.Json.Nodes; -using Codon.Codec.Transcoder; -using Codon.Codec.Transcoder.Transcoders; +using Codon.Codec.Json; namespace Codon.Tests; diff --git a/Codon.Tests/ListMapEnumAndMoreCodecTests.cs b/Codon.Tests/ListMapEnumAndMoreCodecTests.cs index 67fc8bb..7cf2ff1 100644 --- a/Codon.Tests/ListMapEnumAndMoreCodecTests.cs +++ b/Codon.Tests/ListMapEnumAndMoreCodecTests.cs @@ -1,13 +1,12 @@ -using System.Text.Json; using Codon.Codec; -using Codon.Codec.Transcoder.Transcoders; +using Codon.Codec.Json; using Codon.Codec.Transcoder; namespace Codon.Tests; public class ListMapEnumAndMoreCodecTests { - private readonly JsonTranscoder _t = JsonTranscoder.Instance; + private readonly JsonTranscoder _t = JsonTranscoder.INSTANCE; [Test] public void List_Primitive_RoundTrip() diff --git a/Codon.Tests/OptionalAndDefaultCodecTests.cs b/Codon.Tests/OptionalAndDefaultCodecTests.cs index 358603a..cd735bc 100644 --- a/Codon.Tests/OptionalAndDefaultCodecTests.cs +++ b/Codon.Tests/OptionalAndDefaultCodecTests.cs @@ -1,13 +1,13 @@ using System.Text.Json; using Codon.Codec; -using Codon.Codec.Transcoder.Transcoders; +using Codon.Codec.Json; using Codon.Optionals; namespace Codon.Tests; public class OptionalAndDefaultCodecTests { - private readonly JsonTranscoder _t = JsonTranscoder.Instance; + private readonly JsonTranscoder _t = JsonTranscoder.INSTANCE; [Test] public void Optional_Present_RoundTrip() diff --git a/Codon.Tests/PrimitiveCodecTests.cs b/Codon.Tests/PrimitiveCodecTests.cs index e1ff3b9..29abc03 100644 --- a/Codon.Tests/PrimitiveCodecTests.cs +++ b/Codon.Tests/PrimitiveCodecTests.cs @@ -1,16 +1,16 @@ using Codon.Codec; -using Codon.Codec.Transcoder.Transcoders; +using Codon.Codec.Json; namespace Codon.Tests; public class PrimitiveCodecTests { - private readonly JsonTranscoder _t = JsonTranscoder.Instance; + private readonly JsonTranscoder _t = JsonTranscoder.INSTANCE; private static void RoundTrip(ICodec codec, T value) { - var encoded = codec.Encode(JsonTranscoder.Instance, value); - var decoded = codec.Decode(JsonTranscoder.Instance, encoded); + var encoded = codec.Encode(JsonTranscoder.INSTANCE, value); + var decoded = codec.Decode(JsonTranscoder.INSTANCE, encoded); Assert.That(decoded, Is.EqualTo(value)); } diff --git a/Codon.Tests/RecursiveCodecTests.cs b/Codon.Tests/RecursiveCodecTests.cs index e04fc67..55a5c51 100644 --- a/Codon.Tests/RecursiveCodecTests.cs +++ b/Codon.Tests/RecursiveCodecTests.cs @@ -1,5 +1,5 @@ using Codon.Codec; -using Codon.Codec.Transcoder.Transcoders; +using Codon.Codec.Json; namespace Codon.Tests; @@ -27,14 +27,14 @@ public void TestRecursiveCodec_EncodeDecodeRoundtrip() ] ); - var encoded = Node.Codec.Encode(JsonTranscoder.Instance, tree); + var encoded = Node.Codec.Encode(JsonTranscoder.INSTANCE, tree); var json = encoded.GetRawText(); Console.WriteLine(json); Assert.That(json, Does.Contain("\"name\":\"root\"")); Assert.That(json, Does.Contain("\"children\"")); - var decoded = Node.Codec.Decode(JsonTranscoder.Instance, encoded); + var decoded = Node.Codec.Decode(JsonTranscoder.INSTANCE, encoded); Assert.That(decoded.name, Is.EqualTo("root")); Assert.That(decoded.children, Has.Count.EqualTo(2)); diff --git a/Codon.Tests/TranscoderArrayHelpersTests.cs b/Codon.Tests/TranscoderArrayHelpersTests.cs index 992c55b..f449fc6 100644 --- a/Codon.Tests/TranscoderArrayHelpersTests.cs +++ b/Codon.Tests/TranscoderArrayHelpersTests.cs @@ -1,6 +1,6 @@ using System.Text.Json; +using Codon.Codec.Json; using Codon.Codec.Transcoder; -using Codon.Codec.Transcoder.Transcoders; namespace Codon.Tests; diff --git a/Codon.Tests/Util/AssemblyUtil.cs b/Codon.Tests/Util/AssemblyUtil.cs deleted file mode 100644 index 41790db..0000000 --- a/Codon.Tests/Util/AssemblyUtil.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System.Reflection; -using System.Text; - -namespace Codon.Tests.Util; - -public static class AssemblyUtil -{ - public static string GetTextResource(Assembly assembly, string resourcePath) - { - using var stream = assembly.GetManifestResourceStream(resourcePath); - if (stream == null) throw new FileNotFoundException($"Embedded resource '{resourcePath}' does not exist!"); - - using var reader = new StreamReader(stream, Encoding.UTF8, leaveOpen: false); - return reader.ReadToEnd(); - } -} \ No newline at end of file diff --git a/Codon.Tests/VersionCodecTests.cs b/Codon.Tests/VersionCodecTests.cs new file mode 100644 index 0000000..c9b3f5b --- /dev/null +++ b/Codon.Tests/VersionCodecTests.cs @@ -0,0 +1,154 @@ +using System.Text.Json; +using Codon.Codec; +using Codon.Codec.Json; +using Codon.Codec.Versioned; +using Codon.Optionals; + +namespace Codon.Tests; + +public class VersionCodecTests +{ + private const string jeson = "{\"display_name\":\"Synesthesia Dev\", \"is_awesome\":true}"; + private const string json_newer = "{\"_schemaVersion\": 3, \"name\":\"Synesthesia Dev\", \"age\": 99, \"is_awesome\":true}"; + + public record Person(string Name, int Age, Optional IsAwesome) + { + public static readonly StructCodec CODEC = StructCodec.Of + ( + "name", Codecs.String, p => p.Name, + "age", Codecs.Int, p => p.Age, + "is_awesome", Codecs.Boolean.Optional(), p => p.IsAwesome, + (name, age, someBoolean) => new Person(name, age, someBoolean) + ); + + // schema version 0 -> 1: added "age" field + // schema version 1 -> 2: renamed "display_name" to just "name" + + public static readonly VersionedStructCodec VERSIONED_CODEC = new VersionedStructCodec() + { + CurrentSchemaVersion = 2, + InnerCodec = Person.CODEC, + SchemaMigrationRegistry = SchemaMigrationRegistry.Builder() + .For(migrations => + { + // migration to version 1: ensure "age" exists + migrations.Add(1, (transcoder, _, output) => output.Put("age", transcoder.EncodeInt(0))); + + // migration to version 2: copy "display_name" -> "name" + migrations.Add(2, (transcoder, input, output) => + { + var name = transcoder.DecodeString(input.GetValue("display_name")); + output.Put("name", transcoder.EncodeString(name)); + }); + }) + }; + } + + private static JsonElement parseJson(string json) => JsonDocument.Parse(json).RootElement; + + [Test] + public void Decode_V0_NoSchemaVersionField_AppliesAllMigrations_AndDecodes() + { + + // no _schemaVersion, also uses "display_name" (pre-v2) + var v0 = parseJson("{\"display_name\":\"Synesthesia Dev\", \"is_awesome\":true}"); + + var decoded = Person.VERSIONED_CODEC.Decode(JsonTranscoder.INSTANCE, v0); + + Assert.That(decoded.Name, Is.EqualTo("Synesthesia Dev")); + Assert.That(decoded.Age, Is.EqualTo(0), "v0->v1 migration should default age to 0"); + Assert.That(decoded.IsAwesome.IsPresent, Is.True); + Assert.That(decoded.IsAwesome.Value, Is.True); + } + + [Test] + public void Decode_V1_WithSchemaVersion_AppliesRemainingMigrationsOnly() + { + // v1 has _schemaVersion=1 and already has age, but still uses display_name + var v1 = parseJson("{\"_schemaVersion\":1, \"display_name\":\"Ada\", \"age\":42, \"is_awesome\":false}"); + + var decoded = Person.VERSIONED_CODEC.Decode(JsonTranscoder.INSTANCE, v1); + + Assert.That(decoded.Name, Is.EqualTo("Ada")); + Assert.That(decoded.Age, Is.EqualTo(42), "Age should not be overwritten by v0->v1 migration when already v1"); + Assert.That(decoded.IsAwesome.IsPresent, Is.True); + Assert.That(decoded.IsAwesome.Value, Is.False); + } + + [Test] + public void Decode_AlreadyCurrentSchema_DoesNotRequireMigrations_AndIgnoresUnknownFields() + { + // name + age and includes extra field that should be ignored by StructCodec + var v2 = parseJson("{\"_schemaVersion\":2, \"name\":\"Grace\", \"age\":99, \"is_awesome\":true, \"extra\":\"ignored\"}"); + + var decoded = Person.VERSIONED_CODEC.Decode(JsonTranscoder.INSTANCE, v2); + + Assert.That(decoded.Name, Is.EqualTo("Grace")); + Assert.That(decoded.Age, Is.EqualTo(99)); + Assert.That(decoded.IsAwesome.IsPresent, Is.True); + Assert.That(decoded.IsAwesome.Value, Is.True); + } + + [Test] + public void Decode_MissingRequiredFieldDuringMigration_Throws() + { + // v1 -> v2 migration expects display_name. If it's missing, migration should fail. + var v1MissingDisplayName = parseJson("{\"_schemaVersion\":1, \"age\":5, \"is_awesome\":true}"); + + Assert.Throws(() => Person.VERSIONED_CODEC.Decode(JsonTranscoder.INSTANCE, v1MissingDisplayName)); + } + + [Test] + public void Decode_WhenMigrationIsMissing_ThrowsKeyNotFoundException() + { + var versioned = new VersionedStructCodec + { + CurrentSchemaVersion = 2, + InnerCodec = Person.CODEC, + SchemaMigrationRegistry = SchemaMigrationRegistry.Builder() + .For(migrations => + { + // Intentionally omit migration to version 2 + migrations.Add(1, (transcoder, _, output) => output.Put("age", transcoder.EncodeInt(0))); + }) + }; + + var v0 = parseJson("{\"display_name\":\"X\", \"is_awesome\":true}"); + + Assert.Throws(() => versioned.Decode(JsonTranscoder.INSTANCE, v0)); + } + + [Test] + public void Encode_AlwaysAddsSchemaVersion_AndRoundTripsWithDecode() + { + var original = new Person("Synesthesia Dev", 123, Optional.Of(true)); + + var encoded = Person.VERSIONED_CODEC.Encode(JsonTranscoder.INSTANCE, original); + + var map = JsonTranscoder.INSTANCE.DecodeMap(encoded); + Assert.That(map.HasValue("_schemaVersion"), Is.True); + Assert.That(JsonTranscoder.INSTANCE.DecodeInt(map.GetValue("_schemaVersion")), Is.EqualTo(2)); + + var decoded = Person.VERSIONED_CODEC.Decode(JsonTranscoder.INSTANCE, encoded); + Assert.That(decoded, Is.EqualTo(original)); + } + + [Test] + public void RoundTrip() + { + var decoded = Person.VERSIONED_CODEC.Decode(JsonTranscoder.INSTANCE, JsonDocument.Parse(jeson).RootElement); + Assert.That(decoded.Name, Is.EqualTo("Synesthesia Dev")); + Assert.That(decoded.Age, Is.EqualTo(0)); + + var encoded = Person.VERSIONED_CODEC.Encode(JsonTranscoder.INSTANCE, decoded); + var map = JsonTranscoder.INSTANCE.DecodeMap(encoded); + Assert.That(JsonTranscoder.INSTANCE.DecodeInt(map.GetValue("_schemaVersion")), Is.EqualTo(2)); + Assert.That(JsonTranscoder.INSTANCE.DecodeString(map.GetValue("name")), Is.EqualTo("Synesthesia Dev")); + } + + [Test] + public void Decode_FutureSchemaVersion() + { + Assert.Throws(() => Person.VERSIONED_CODEC.Decode(JsonTranscoder.INSTANCE, JsonDocument.Parse(json_newer).RootElement)); + } +} diff --git a/Codon.sln.DotSettings.user b/Codon.sln.DotSettings.user index 6af2d2c..b47f833 100644 --- a/Codon.sln.DotSettings.user +++ b/Codon.sln.DotSettings.user @@ -12,7 +12,9 @@ <AssemblyExplorer> <Assembly Path="E:\Coding\C#\Codon\Codon.Buffer\bin\Debug\net8.0\Codon.Buffer.dll" /> </AssemblyExplorer> - <SessionState ContinuousTestingMode="0" IsActive="True" Name="Junie Session" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"> - <Project Location="E:\Coding\C#\Codon\Codon.Tests" Presentation="&lt;Codon.Tests&gt;" /> + <SessionState ContinuousTestingMode="0" IsActive="True" Name="VersionCodecTests" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"> + <Project Location="F:\Coding\C#\Codon\Codon.Tests" Presentation="&lt;Codon.Tests&gt;" /> </SessionState> + + \ No newline at end of file diff --git a/Directory.Build.targets b/Directory.Build.targets new file mode 100644 index 0000000..3c2f96f --- /dev/null +++ b/Directory.Build.targets @@ -0,0 +1,5 @@ + + + 1.1.0 + + diff --git a/README.md b/README.md index bcb2c57..f30240b 100644 --- a/README.md +++ b/README.md @@ -216,6 +216,45 @@ This would be encoded as following: definitions. - Optional and Default wrappers help you model absent fields and fallback values. +### Versioned Struct Codecs + +Versioned struct codecs (`VersionedStructCodec`) tracks schema versions and automatically applies migrations: +```csharp +private const string old_person_json = "{\"display_name\":\"Synesthesia Dev\", \"is_awesome\":true}"; + +public record Person(string Name, int Age, Optional IsAwesome) +{ + private static readonly StructCodec codec = StructCodec.Of ( + "name", Codecs.String, p => p.Name, + "age", Codecs.Int, p => p.Age, + "is_awesome", Codecs.Boolean.Optional(), p => p.IsAwesome, + (name, age, someBoolean) => new Person(name, age, someBoolean) + ); + + // schema version 1: added "age" field + // schema version 2: renamed "display_name" to just "name" + + public static readonly VersionedStructCodec VERSIONED_CODEC = new VersionedStructCodec() + { + CurrentSchemaVersion = 2, + InnerCodec = Person.codec, + SchemaMigrationRegistry = SchemaMigrationRegistry.Builder() + // Specify for what transcoder type/format this migration is + .For(migrations => + { + // migration to version 1: ensure "age" exists + migrations.Add(1, (transcoder, _, output) => output.Put("age", transcoder.EncodeInt(0))); + // migration to version 2: copy "display_name" -> "name" + migrations.Add(2, (transcoder, input, output) => + { + var name = transcoder.DecodeString(input.GetValue("display_name")); + output.Put("name", transcoder.EncodeString(name)); + }); + }) + }; +} +``` + --- See the test suite under `Codon.Tests` for broader coverage (lists, maps, enums, unions, forward refs, array helpers,