Strict by default and implement Serializable::Unmapped
This commit is contained in:
		
							parent
							
								
									2f8693ca34
								
							
						
					
					
						commit
						9d4c37460c
					
				
					 6 changed files with 117 additions and 57 deletions
				
			
		| 
						 | 
				
			
			@ -23,6 +23,19 @@ class ExampleC
 | 
			
		|||
  property b : String
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
class ExampleStrict
 | 
			
		||||
  include CBOR::Serializable
 | 
			
		||||
 | 
			
		||||
  property a : Int32
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
class ExampleUnmapped
 | 
			
		||||
  include CBOR::Serializable
 | 
			
		||||
  include CBOR::Serializable::Unmapped
 | 
			
		||||
 | 
			
		||||
  property a : Int32
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
describe CBOR::Serializable do
 | 
			
		||||
  describe "rfc examples" do
 | 
			
		||||
    describe %(example {_ "a": 1, "b": [_ 2, 3]}) do
 | 
			
		||||
| 
						 | 
				
			
			@ -54,4 +67,21 @@ describe CBOR::Serializable do
 | 
			
		|||
      end
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  describe "by default it's strict" do
 | 
			
		||||
    it "errors on missing fields" do
 | 
			
		||||
      expect_raises(CBOR::ParseError) do
 | 
			
		||||
        ExampleStrict.from_cbor(Bytes[0xbf, 0x61, 0x61, 0x01, 0x61, 0x62, 0x9f, 0x02, 0x03, 0xff, 0xff])
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  describe CBOR::Serializable::Unmapped do
 | 
			
		||||
    it "adds missing fields to the map" do
 | 
			
		||||
      result = ExampleUnmapped.from_cbor(Bytes[0xbf, 0x61, 0x61, 0x01, 0x61, 0x62, 0x9f, 0x02, 0x03, 0xff, 0xff])
 | 
			
		||||
 | 
			
		||||
      result.a.should eq(1)
 | 
			
		||||
      result.cbor_unmapped.should eq({"b" => [2, 3]})
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -21,5 +21,7 @@ module CBOR
 | 
			
		|||
               UInt32 |
 | 
			
		||||
               Int64 |
 | 
			
		||||
               UInt64 |
 | 
			
		||||
               Int128
 | 
			
		||||
               Int128 |
 | 
			
		||||
               Float32 |
 | 
			
		||||
               Float64
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -7,6 +7,40 @@ class CBOR::Decoder
 | 
			
		|||
    @current_token = @lexer.next_token
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def read_value : Type
 | 
			
		||||
    case token = @current_token
 | 
			
		||||
    when Token::TagT
 | 
			
		||||
      finish_token!
 | 
			
		||||
      read_value
 | 
			
		||||
    when Token::StringT
 | 
			
		||||
      finish_token!
 | 
			
		||||
      token.value
 | 
			
		||||
    when Token::IntT
 | 
			
		||||
      finish_token!
 | 
			
		||||
      token.value
 | 
			
		||||
    when Token::FloatT
 | 
			
		||||
      finish_token!
 | 
			
		||||
      token.value
 | 
			
		||||
    when Token::BytesT
 | 
			
		||||
      finish_token!
 | 
			
		||||
      token.value
 | 
			
		||||
    when Token::SimpleValueT
 | 
			
		||||
      token.value.to_t
 | 
			
		||||
    when Token::ArrayT
 | 
			
		||||
      finish_token!
 | 
			
		||||
      arr = Array(Type).new
 | 
			
		||||
      consume_sequence(token.size) { arr << read_value }
 | 
			
		||||
      arr
 | 
			
		||||
    when Token::MapT
 | 
			
		||||
      finish_token!
 | 
			
		||||
      map = Hash(Type, Type).new
 | 
			
		||||
      consume_sequence(token.size) { map[read_value] = read_value }
 | 
			
		||||
      map
 | 
			
		||||
    else
 | 
			
		||||
      unexpected_token(token)
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def read_string : String
 | 
			
		||||
    case token = @current_token
 | 
			
		||||
    when Token::StringT
 | 
			
		||||
| 
						 | 
				
			
			@ -91,23 +125,6 @@ class CBOR::Decoder
 | 
			
		|||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def skip
 | 
			
		||||
    case token = @current_token
 | 
			
		||||
    when Token::IntT,
 | 
			
		||||
         Token::FloatT,
 | 
			
		||||
         Token::BytesT,
 | 
			
		||||
         Token::StringT,
 | 
			
		||||
         Token::TagT,
 | 
			
		||||
         Token::SimpleValueT
 | 
			
		||||
      finish_token!
 | 
			
		||||
    when Token::ArrayT, Token::MapT
 | 
			
		||||
      finish_token!
 | 
			
		||||
      consume_sequence(token.size) { }
 | 
			
		||||
    else
 | 
			
		||||
      unexpected_token(token)
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def finish_token!
 | 
			
		||||
    @current_token = @lexer.next_token
 | 
			
		||||
  end
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -79,13 +79,10 @@ module CBOR
 | 
			
		|||
  # A.from_json(%<{"a":1}>) # => A(@a=1, @b=1.0) #TODO ----- FIX THIS!!!!
 | 
			
		||||
  # ```
 | 
			
		||||
  #
 | 
			
		||||
  # ### Extensions: `JSON::Serializable::Strict` and `JSON::Serializable::Unmapped`.
 | 
			
		||||
  # ### Extensions: `CBOR::Serializable::Unmapped`.
 | 
			
		||||
  #
 | 
			
		||||
  # If the `JSON::Serializable::Strict` module is included, unknown properties in the JSON
 | 
			
		||||
  # document will raise a parse exception. By default the unknown properties
 | 
			
		||||
  # are silently ignored.
 | 
			
		||||
  # If the `JSON::Serializable::Unmapped` module is included, unknown properties in the JSON
 | 
			
		||||
  # document will be stored in a `Hash(String, JSON::Any)`. On serialization, any keys inside json_unmapped
 | 
			
		||||
  # If the `CBOR::Serializable::Unmapped` module is included, unknown properties in the CBOR
 | 
			
		||||
  # document will be stored in a `Hash(String, CBOR::Type)`. On serialization, any keys inside cbor_unmapped
 | 
			
		||||
  # will be serialized and appended to the current json object.
 | 
			
		||||
  # ```
 | 
			
		||||
  # require "json"
 | 
			
		||||
| 
						 | 
				
			
			@ -181,9 +178,7 @@ module CBOR
 | 
			
		|||
        begin
 | 
			
		||||
          decoder.read_begin_hash
 | 
			
		||||
        rescue exc : ::CBOR::ParseError
 | 
			
		||||
          # TODO: Improve error message, use dedicated class
 | 
			
		||||
          raise "Error in mapping decoding when reading being hash: #{exc.message}"
 | 
			
		||||
          # raise ::JSON::MappingError.new(exc.message, self.class.to_s, nil, *%location, exc)
 | 
			
		||||
          raise ::CBOR::SerializationError.new(exc.message, self.class.to_s, nil)
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        decoder.consume_hash do
 | 
			
		||||
| 
						 | 
				
			
			@ -204,9 +199,7 @@ module CBOR
 | 
			
		|||
 | 
			
		||||
                {% if value[:nilable] || value[:has_default] %} } {% end %}
 | 
			
		||||
              rescue exc : ::CBOR::ParseError
 | 
			
		||||
                # TODO: Improve error message, use dedicated class
 | 
			
		||||
                raise "Error in mapping decoding when consuming hash: #{exc.message}"
 | 
			
		||||
                # raise ::JSON::MappingError.new(exc.message, self.class.to_s, {{value[:key]}}, *%key_location, exc)
 | 
			
		||||
                raise ::CBOR::SerializationError.new(exc.message, self.class.to_s, {{value[:key]}})
 | 
			
		||||
              end
 | 
			
		||||
          {% end %}
 | 
			
		||||
          else
 | 
			
		||||
| 
						 | 
				
			
			@ -217,9 +210,7 @@ module CBOR
 | 
			
		|||
        {% for name, value in properties %}
 | 
			
		||||
          {% unless value[:nilable] || value[:has_default] %}
 | 
			
		||||
            if %var{name}.nil? && !%found{name} && !::Union({{value[:type]}}).nilable?
 | 
			
		||||
                # TODO: Improve error message, use dedicated class
 | 
			
		||||
                raise "Missing CBOR attribute"
 | 
			
		||||
              # raise ::JSON::MappingError.new("Missing JSON attribute: {{value[:key].id}}", self.class.to_s, nil, *%location, nil)
 | 
			
		||||
              raise ::CBOR::SerializationError.new("Missing CBOR attribute: {{value[:key].id}}", self.class.to_s, nil)
 | 
			
		||||
            end
 | 
			
		||||
          {% end %}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -247,7 +238,7 @@ module CBOR
 | 
			
		|||
    end
 | 
			
		||||
 | 
			
		||||
    protected def on_unknown_cbor_attribute(decoder, key)
 | 
			
		||||
      decoder.skip
 | 
			
		||||
      raise ::CBOR::SerializationError.new("Unknown CBOR attribute: #{key}", self.class.to_s, nil)
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    # protected def on_to_cbor(cbor : ::CBOR::Builder)
 | 
			
		||||
| 
						 | 
				
			
			@ -322,33 +313,25 @@ module CBOR
 | 
			
		|||
    #   {% end %}
 | 
			
		||||
    # end
 | 
			
		||||
 | 
			
		||||
    module Strict
 | 
			
		||||
    module Unmapped
 | 
			
		||||
      @[CBOR::Field(ignore: true)]
 | 
			
		||||
      property cbor_unmapped = Hash(String, ::CBOR::Type).new
 | 
			
		||||
 | 
			
		||||
      protected def on_unknown_cbor_attribute(decoder, key)
 | 
			
		||||
        # TODO: Improve error
 | 
			
		||||
        raise "Unknown CBOR attribute: #{key}"
 | 
			
		||||
        # raise ::JSON::MappingError.new("Unknown JSON attribute: #{key}", self.class.to_s, nil, *key_location, nil)
 | 
			
		||||
        cbor_unmapped[key] = begin
 | 
			
		||||
          decoder.read_value
 | 
			
		||||
        rescue exc : ::CBOR::ParseError
 | 
			
		||||
          raise ::CBOR::SerializationError.new(exc.message, self.class.to_s, key)
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      #   protected def on_to_json(json)
 | 
			
		||||
      #     json_unmapped.each do |key, value|
 | 
			
		||||
      #       json.field(key) { value.to_json(json) }
 | 
			
		||||
      #     end
 | 
			
		||||
      #   end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    # module Unmapped
 | 
			
		||||
    #   @[CBOR::Field(ignore: true)]
 | 
			
		||||
    #   property cbor_unmapped = Hash(String, ::CBOR::Type).new
 | 
			
		||||
 | 
			
		||||
    #   protected def on_unknown_cbor_attribute(decoder, key)
 | 
			
		||||
    #     json_unmapped[key] = begin
 | 
			
		||||
    #       JSON::Any.new(pull)
 | 
			
		||||
    #     rescue exc : ::JSON::ParseException
 | 
			
		||||
    #       raise ::JSON::MappingError.new(exc.message, self.class.to_s, key, *key_location, exc)
 | 
			
		||||
    #     end
 | 
			
		||||
    #   end
 | 
			
		||||
 | 
			
		||||
    #   protected def on_to_json(json)
 | 
			
		||||
    #     json_unmapped.each do |key, value|
 | 
			
		||||
    #       json.field(key) { value.to_json(json) }
 | 
			
		||||
    #     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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										16
									
								
								src/cbor/serialization_error.cr
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								src/cbor/serialization_error.cr
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,16 @@
 | 
			
		|||
class CBOR::SerializationError < Exception
 | 
			
		||||
  getter klass : String
 | 
			
		||||
 | 
			
		||||
  def initialize(message : String?, @klass : String, @attribute : String?)
 | 
			
		||||
    message = String.build do |io|
 | 
			
		||||
      io << message
 | 
			
		||||
      io << "\n  parsing "
 | 
			
		||||
      io << klass
 | 
			
		||||
      if attribute = @attribute
 | 
			
		||||
        io << '#' << attribute
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    super(message)
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			@ -19,6 +19,18 @@ enum CBOR::SimpleValue : UInt8
 | 
			
		|||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def to_t : Bool | Nil
 | 
			
		||||
    case self
 | 
			
		||||
    when False
 | 
			
		||||
      false
 | 
			
		||||
    when True
 | 
			
		||||
      true
 | 
			
		||||
    when Null,
 | 
			
		||||
         Undefined
 | 
			
		||||
      nil
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def is_nil? : Bool
 | 
			
		||||
    case self
 | 
			
		||||
    when Null, Undefined
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		
		Reference in a new issue