Strict by default and implement Serializable::Unmapped

dev
Alberto Restifo 2020-05-22 23:16:10 +02:00
parent 2f8693ca34
commit 9d4c37460c
6 changed files with 117 additions and 57 deletions

View File

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

View File

@ -21,5 +21,7 @@ module CBOR
UInt32 |
Int64 |
UInt64 |
Int128
Int128 |
Float32 |
Float64
end

View File

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

View File

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

View 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

View File

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