~arestifo/crystal-cbor

crystal-cbor: use_cbor_discriminator: second rewrite v2 PROPOSED

This time with the right formatting.

I put tests in the same commit,
since this will be added this way in master.

Karchnu (1):
  Add use_cbor_discriminator (and its tests).

 spec/cbor_discrimination.cr | 67 +++++++++++++++++++++++++++++++++++++
 src/cbor/decoder.cr         | 13 +++++++
 src/cbor/from_cbor.cr       |  4 +++
 src/cbor/lexer.cr           |  7 ++++
 src/cbor/serializable.cr    | 30 +++++++++++++++++
 5 files changed, 121 insertions(+)
 create mode 100644 spec/cbor_discrimination.cr

-- 
2.30.2
#531620 .build.yml success
crystal-cbor/patches/.build.yml: SUCCESS in 28s

[use_cbor_discriminator: second rewrite][0] v2 from [~karchnu][1]

[0]: https://lists.sr.ht/~arestifo/crystal-cbor/patches/23447
[1]: mailto:karchnu@karchnu.fr

✓ #531620 SUCCESS crystal-cbor/patches/.build.yml https://builds.sr.ht/~arestifo/job/531620
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/23447/mbox | git am -3
Learn more about email & git

[PATCH crystal-cbor v2 1/1] Add use_cbor_discriminator (and its tests). Export this patch

From: Karchnu <karchnu@karchnu.fr>

---
 spec/cbor_discrimination.cr | 67 +++++++++++++++++++++++++++++++++++++
 src/cbor/decoder.cr         | 13 +++++++
 src/cbor/from_cbor.cr       |  4 +++
 src/cbor/lexer.cr           |  7 ++++
 src/cbor/serializable.cr    | 30 +++++++++++++++++
 5 files changed, 121 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..0c438ad
--- /dev/null
+++ b/spec/cbor_discrimination.cr
@@ -0,0 +1,67 @@
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
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..e469426 100644
--- a/src/cbor/lexer.cr
+++ b/src/cbor/lexer.cr
@@ -1,4 +1,6 @@
class CBOR::Lexer
  property io : IO

  def self.new(slice : Bytes)
    new IO::Memory.new(slice)
  end
@@ -8,6 +10,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
crystal-cbor/patches/.build.yml: SUCCESS in 28s

[use_cbor_discriminator: second rewrite][0] v2 from [~karchnu][1]

[0]: https://lists.sr.ht/~arestifo/crystal-cbor/patches/23447
[1]: mailto:karchnu@karchnu.fr

✓ #531620 SUCCESS crystal-cbor/patches/.build.yml https://builds.sr.ht/~arestifo/job/531620