~arestifo/crystal-cbor

crystal-cbor: use_cbor_discriminator: a rewrite v1 PROPOSED

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
#530187 .build.yml failed
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
Export patchset (mbox)
How do I use this?

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 -3
Learn more about email & git

[PATCH crystal-cbor 1/2] Decoding: use_cbor_discriminator Export this patch

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

[PATCH crystal-cbor 2/2] Add use_cbor_discrimination tests. Export this patch

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
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