Forget previous patch introducing use_cbor_discriminator. In this patch, I add the functionality (with less bugs) and some tests. PS: further features and bugfixes will follow. I'll add their tests in different files each time, otherwise it will be a nightmare to me to prepare these patches. Karchnu (2): Decoding: use_cbor_discriminator Add use_cbor_discrimination tests. spec/cbor_discrimination.cr | 65 +++++++++++++++++++++++++++++++++++++ src/cbor/decoder.cr | 13 ++++++++ src/cbor/from_cbor.cr | 4 +++ src/cbor/lexer.cr | 6 ++++ src/cbor/serializable.cr | 30 +++++++++++++++++ 5 files changed, 118 insertions(+) create mode 100644 spec/cbor_discrimination.cr -- 2.30.2
crystal-cbor/patches/.build.yml: FAILED in 27s [use_cbor_discriminator: a rewrite][0] from [~karchnu][1] [0]: https://lists.sr.ht/~arestifo/crystal-cbor/patches/23421 [1]: mailto:karchnu@karchnu.fr ✗ #530187 FAILED crystal-cbor/patches/.build.yml https://builds.sr.ht/~arestifo/job/530187
Copy & paste the following snippet into your terminal to import this patchset into git:
curl -s https://lists.sr.ht/~arestifo/crystal-cbor/patches/23421/mbox | git am -3Learn more about email & git
From: Karchnu <karchnu@karchnu.fr> --- src/cbor/decoder.cr | 13 +++++++++++++ src/cbor/from_cbor.cr | 4 ++++ src/cbor/lexer.cr | 6 ++++++ src/cbor/serializable.cr | 30 ++++++++++++++++++++++++++++++ 4 files changed, 53 insertions(+) diff --git a/src/cbor/decoder.cr b/src/cbor/decoder.cr index 510fb45..117759f 100644 --- a/src/cbor/decoder.cr +++ b/src/cbor/decoder.cr @@ -2,6 +2,19 @@ class CBOR::Decoder @lexer : Lexer getter current_token : Token::T? + # Decode until a certain point in the history (use_cbor_discriminator helper). + def reset(value : Int32 | Int64 = 0) + @lexer.reset + while pos < value + @current_token = @lexer.next_token + end + end + + # Give the current position in the decoder (use_cbor_discriminator helper). + def pos + @lexer.io.pos + end + def initialize(input) @lexer = Lexer.new(input) @current_token = @lexer.next_token diff --git a/src/cbor/from_cbor.cr b/src/cbor/from_cbor.cr index e2610e7..0374c1f 100644 --- a/src/cbor/from_cbor.cr +++ b/src/cbor/from_cbor.cr @@ -3,6 +3,10 @@ def Object.from_cbor(string_or_io) new(parser) end +def Object.from_cbor(parser : CBOR::Decoder) + new(parser) +end + def String.new(decoder : CBOR::Decoder) decoder.read_string end diff --git a/src/cbor/lexer.cr b/src/cbor/lexer.cr index 8ba2a11..883263a 100644 --- a/src/cbor/lexer.cr +++ b/src/cbor/lexer.cr @@ -1,4 +1,5 @@ class CBOR::Lexer + property io : IO def self.new(slice : Bytes) new IO::Memory.new(slice) end @@ -8,6 +9,11 @@ class CBOR::Lexer def initialize(@io : IO) end + def reset(value : Int32 | Int64 = 0) + @io.seek value + @eof = false + end + def next_token : Token::T? return nil if @eof diff --git a/src/cbor/serializable.cr b/src/cbor/serializable.cr index 4925470..1bd6d67 100644 --- a/src/cbor/serializable.cr +++ b/src/cbor/serializable.cr @@ -345,6 +345,36 @@ module CBOR end end + macro use_cbor_discriminator(field, mapping) + {% unless mapping.is_a?(HashLiteral) || mapping.is_a?(NamedTupleLiteral) %} + {% mapping.raise "mapping argument must be a HashLiteral or a NamedTupleLiteral, not #{mapping.class_name.id}" %} + {% end %} + + # SLOW. Read everything, get the type, read everything again. + def self.new(decoder : ::CBOR::Decoder) + current_offset = decoder.pos + if v = decoder.read_value + decoder.reset current_offset + case v + when Hash(CBOR::Type, CBOR::Type) + discriminator_value = v[{{field.id.stringify}}]? + case discriminator_value + {% for key, value in mapping %} + when {{key.id.stringify}} + return {{value.id}}.from_cbor(decoder) + {% end %} + else + raise "Unknown '{{field.id}}' discriminator value: #{discriminator_value.inspect}" + end + else + raise "cannot get cbor discriminator #{ {{ field.id.stringify }} }" + end + else + raise "cannot decode cbor value" + end + end + end + # Tells this class to decode CBOR by using a field as a discriminator. # # - *field* must be the field name to use as a discriminator -- 2.30.2
From: Karchnu <karchnu@karchnu.fr> --- spec/cbor_discrimination.cr | 65 +++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 spec/cbor_discrimination.cr diff --git a/spec/cbor_discrimination.cr b/spec/cbor_discrimination.cr new file mode 100644 index 0000000..16fec58 --- /dev/null +++ b/spec/cbor_discrimination.cr @@ -0,0 +1,65 @@ +require "./spec_helper" + +class Game + include CBOR::Serializable + + abstract class Player + include CBOR::Serializable + + @[CBOR::Field(key: "hp")] + property health_points : Int32 = 100 + + @[CBOR::Field(key: "dp")] + property defense_points : Int32 = 5 + + use_cbor_discriminator "type", { + magician: Magician, + warrior: Warrior + } + def initialize + end + end + + class Magician < Player + property wand_level : Int32 = 1 + def initialize + @type = "magician" + end + end + + class Warrior < Player + def initialize + @defense_points = 20 + @type = "warrior" + end + end + + property players : Array(Magician | Warrior) + + def initialize + @players = [] of (Magician | Warrior) + end +end + +describe CBOR do + describe "Complex object representations" do + it "Game#to_cbor with use_cbor_discriminator" do + game = Game.new + magician = Game::Magician.new + magician.wand_level = 5 + game.players << magician + game.players << Game::Warrior.new + game.to_cbor.hexstring.should eq "a167706c617965727382a46268701864626470056a77616e645f6c6576656c056474797065686d6167696369616ea362687018646264701464747970656777617272696f72" + end + + it "Game#from_cbor with use_cbor_discriminator" do + game = Game.new + magician = Game::Magician.new + magician.wand_level = 5 + game.players << magician + game.players << Game::Warrior.new + new_game = Game.from_cbor game.to_cbor + new_game.to_cbor.hexstring.should eq "a167706c617965727382a46268701864626470056a77616e645f6c6576656c056474797065686d6167696369616ea362687018646264701464747970656777617272696f72" + end + end +end -- 2.30.2
builds.sr.ht <builds@sr.ht>crystal-cbor/patches/.build.yml: FAILED in 27s [use_cbor_discriminator: a rewrite][0] from [~karchnu][1] [0]: https://lists.sr.ht/~arestifo/crystal-cbor/patches/23421 [1]: mailto:karchnu@karchnu.fr ✗ #530187 FAILED crystal-cbor/patches/.build.yml https://builds.sr.ht/~arestifo/job/530187