Compare commits
16 Commits
Author | SHA1 | Date |
---|---|---|
Karchnu | c292476cef | |
Karchnu | 678494cec9 | |
Karchnu | b74a1af8e5 | |
Karchnu | 81c91e6eb2 | |
Karchnu | a8d76a9496 | |
Karchnu | bd49ce7655 | |
Karchnu | a7eb538892 | |
Karchnu | bdc14a1d20 | |
Karchnu | a058ab3ccf | |
Karchnu | 0c8baf1ccd | |
Karchnu | 4d4948418d | |
Karchnu | fef7d85349 | |
Karchnu | 2be80d9692 | |
Karchnu | b8229f9dde | |
Karchnu | 3d232e6005 | |
Karchnu | f45fb927f4 |
|
@ -1,5 +1,232 @@
|
|||
require "./spec_helper"
|
||||
|
||||
describe CBOR do
|
||||
# TODO: Write tests
|
||||
class Location
|
||||
include CBOR::Serializable
|
||||
|
||||
@[CBOR::Field(key: "lat")]
|
||||
property latitude : Float64
|
||||
|
||||
@[CBOR::Field(key: "lng")]
|
||||
property longitude : Float64
|
||||
|
||||
def initialize(@latitude, @longitude)
|
||||
end
|
||||
end
|
||||
|
||||
class House
|
||||
include CBOR::Serializable
|
||||
|
||||
property address : String
|
||||
property location : Location?
|
||||
|
||||
def initialize(@address)
|
||||
end
|
||||
end
|
||||
|
||||
class Person
|
||||
include CBOR::Serializable
|
||||
include CBOR::Serializable::Unmapped
|
||||
|
||||
property name : String?
|
||||
def initialize(@name = nil)
|
||||
end
|
||||
end
|
||||
|
||||
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 "basics: to_cbor" do
|
||||
it "empty array" do
|
||||
empty_array_cbor = [] of Nil
|
||||
empty_array_cbor.to_cbor.hexstring.should eq "80"
|
||||
end
|
||||
|
||||
it "array - strings" do
|
||||
["a", "b", "c"].to_cbor.hexstring.should eq "83616161626163"
|
||||
end
|
||||
|
||||
it "empty hash" do
|
||||
empty = {} of Nil => Nil
|
||||
cbor_stuff = empty.to_cbor
|
||||
cbor_stuff.hexstring.should eq "a0"
|
||||
end
|
||||
|
||||
it "hash" do
|
||||
{"a" => 10, "b" => true, "c" => nil}.to_cbor.hexstring.should eq "a361610a6162f56163f6"
|
||||
end
|
||||
|
||||
it "union String | Int32" do
|
||||
value = (String | Int32).from_cbor(30.to_cbor).to_cbor
|
||||
value.hexstring.should eq "181e"
|
||||
end
|
||||
it "union (String | Int32)" do
|
||||
value = (String | Int32).from_cbor("blah".to_cbor).to_cbor
|
||||
value.hexstring.should eq "64626c6168"
|
||||
end
|
||||
it "union (Bool | Int32)" do
|
||||
value = (Bool | Int32).from_cbor(30.to_cbor).to_cbor
|
||||
value.hexstring.should eq "181e"
|
||||
end
|
||||
it "union (Bool | Int32)" do
|
||||
value = (Bool | Int32).from_cbor(false.to_cbor).to_cbor
|
||||
value.hexstring.should eq "f4"
|
||||
end
|
||||
it "union (String | Bool | Int32)" do
|
||||
value = (String | Bool | Int32).from_cbor("hello".to_cbor).to_cbor
|
||||
value.hexstring.should eq "6568656c6c6f"
|
||||
end
|
||||
it "union (String | Nil | Int32)" do
|
||||
value = (String | Nil | Int32).from_cbor(nil.to_cbor).to_cbor
|
||||
value.hexstring.should eq "f6"
|
||||
end
|
||||
end
|
||||
|
||||
describe "Time" do
|
||||
it "Time#to_cbor" do
|
||||
time = Time.utc(2016, 2, 15, 10, 20, 30)
|
||||
time.to_cbor.hexstring.should eq "c074323031362d30322d31355431303a32303a33305a"
|
||||
end
|
||||
|
||||
it "Time#from_cbor" do
|
||||
time = Time.from_cbor Time.utc(2016, 2, 15, 10, 20, 30).to_cbor
|
||||
time.to_cbor.hexstring.should eq "c074323031362d30322d31355431303a32303a33305a"
|
||||
end
|
||||
end
|
||||
|
||||
describe "UUID" do
|
||||
it "UUID#to_cbor" do
|
||||
uuid = UUID.new "fc47eb8e-b13c-481e-863a-8f8c47a550f2"
|
||||
uuid.to_cbor.hexstring.should eq "50fc47eb8eb13c481e863a8f8c47a550f2"
|
||||
end
|
||||
|
||||
it "UUID#from_cbor" do
|
||||
uuid = UUID.from_cbor "50fc47eb8eb13c481e863a8f8c47a550f2".hexbytes
|
||||
uuid.to_cbor.hexstring.should eq "50fc47eb8eb13c481e863a8f8c47a550f2"
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
describe "CBOR library annotations and features" do
|
||||
it "House#to_cbor with CBOR::Field annotations" do
|
||||
house = House.new "my address"
|
||||
house.location = Location.new 1.1, 1.2
|
||||
house.to_cbor.hexstring.should eq "a267616464726573736a6d792061646472657373686c6f636174696f6ea2636c6174fb3ff199999999999a636c6e67fb3ff3333333333333"
|
||||
end
|
||||
|
||||
it "House#from_cbor with CBOR::Field annotations" do
|
||||
other_house = House.new "my address"
|
||||
other_house.location = Location.new 1.1, 1.2
|
||||
|
||||
house = House.from_cbor other_house.to_cbor
|
||||
house.to_cbor.hexstring.should eq "a267616464726573736a6d792061646472657373686c6f636174696f6ea2636c6174fb3ff199999999999a636c6e67fb3ff3333333333333"
|
||||
end
|
||||
|
||||
it "Person#from_cbor with unmapped values" do
|
||||
h = Hash(String | Int32, String | Int32).new
|
||||
h["name"] = "Alice"
|
||||
h["age"] = 30
|
||||
h["size"] = 160
|
||||
alice = Person.from_cbor h.to_cbor
|
||||
alice.to_cbor.hexstring.should eq "a3646e616d6565416c69636563616765181e6473697a6518a0"
|
||||
end
|
||||
|
||||
it "Person#to_cbor with unmapped values" do
|
||||
alice = Person.new "Alice"
|
||||
alice.cbor_unmapped["age"] = 30
|
||||
alice.cbor_unmapped["size"] = 160
|
||||
alice.to_cbor.hexstring.should eq "a3646e616d6565416c69636563616765181e6473697a6518a0"
|
||||
end
|
||||
end
|
||||
|
||||
describe "CBOR::Any" do
|
||||
it "from JSON::Any - 1" do
|
||||
h_any = {"a" => "b", "c" => "d", "e" => true, "f" => 10}
|
||||
json_any = JSON.parse h_any.to_json
|
||||
cbor_any = CBOR::Any.new json_any
|
||||
cbor_any.to_cbor.hexstring.should eq "a461616162616361646165f561660a"
|
||||
end
|
||||
|
||||
it "from JSON::Any - 2" do
|
||||
h_any = {"a" => "b", "c" => "d", "e" => true, "f" => 10}
|
||||
json_any = JSON.parse h_any.to_json
|
||||
cbor_any = CBOR::Any.new json_any
|
||||
cbor_any["f"].should eq 10
|
||||
end
|
||||
|
||||
it "from array" do
|
||||
array = CBOR::Any.from_cbor [ "a", "b", "c", "d" ].to_cbor
|
||||
array.to_cbor.hexstring.should eq "846161616261636164"
|
||||
end
|
||||
|
||||
it "from hash" do
|
||||
h_cany = Hash(String | Int32, String | Int32).new
|
||||
h_cany["name"] = "Alice"
|
||||
h_cany["age"] = 30
|
||||
h_cany["size"] = 160
|
||||
cbor_any_hash = CBOR::Any.from_cbor h_cany.to_cbor
|
||||
cbor_any_hash.to_cbor.hexstring.should eq "a3646e616d6565416c69636563616765181e6473697a6518a0"
|
||||
end
|
||||
end
|
||||
|
||||
# db[i]?.should be_nil
|
||||
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
|
||||
|
|
21
src/cbor.cr
21
src/cbor.cr
|
@ -5,13 +5,32 @@ require "./cbor/**"
|
|||
module CBOR
|
||||
VERSION = "0.1.0"
|
||||
|
||||
# Represents CBOR Hash Keys: everything except Nil, hashs, arrays.
|
||||
alias HashKeyType = Int8 | Int16 | Int32 | Int64 | Int128 |
|
||||
UInt8 | UInt16 | UInt32 | UInt64 |
|
||||
Float32 | Float64 |
|
||||
String |
|
||||
Bool |
|
||||
Bytes
|
||||
|
||||
# Represents CBOR types
|
||||
alias Type = Nil |
|
||||
Bool |
|
||||
String |
|
||||
Bytes |
|
||||
Array(Type) |
|
||||
Hash(Type, Type) |
|
||||
# Hash(Int8, Type) |
|
||||
# Hash(Int16, Type) |
|
||||
# Hash(Int32, Type) |
|
||||
# Hash(Int64, Type) |
|
||||
# Hash(UInt8, Type) |
|
||||
# Hash(UInt16, Type) |
|
||||
# Hash(UInt32, Type) |
|
||||
# Hash(UInt64, Type) |
|
||||
# Hash(Float32, Type) |
|
||||
# Hash(Float64, Type) |
|
||||
# Hash(String, Type) |
|
||||
Hash(HashKeyType, Type) |
|
||||
Int8 |
|
||||
UInt8 |
|
||||
Int16 |
|
||||
|
|
|
@ -0,0 +1,395 @@
|
|||
# `CBOR::Any` is a convenient wrapper around all possible CBOR types (`CBOR::Any::Type`)
|
||||
# and can be used for traversing dynamic or unknown CBOR structures.
|
||||
#
|
||||
# ```
|
||||
# require "json"
|
||||
#
|
||||
# obj = CBOR::Any.new JSON.parse(%({"access": [{"name": "mapping", "speed": "fast"}, {"name": "any", "speed": "slow"}]}))
|
||||
# obj["access"][1]["name"].as_s # => "any"
|
||||
# obj["access"][1]["speed"].as_s # => "slow"
|
||||
#
|
||||
# # through a cbor buffer
|
||||
# hash = {"access" => [{"name" => "mapping", "speed" => "fast"}, {"name" => "any", "speed" => "slow"}]}
|
||||
# obj2 = CBOR::Any.new hash.to_cbor
|
||||
# obj2["access"][1]["name"].as_s # => "any"
|
||||
# obj2["access"][1]["speed"].as_s # => "slow"
|
||||
# ```
|
||||
#
|
||||
# Note that methods used to traverse a CBOR structure, `#[]` and `#[]?`,
|
||||
# always return a `CBOR::Any` to allow further traversal. To convert them to `String`,
|
||||
# `Int32`, etc., use the `as_` methods, such as `#as_s`, `#as_i`, which perform
|
||||
# a type check against the raw underlying value. This means that invoking `#as_s`
|
||||
# when the underlying value is not a String will raise: the value won't automatically
|
||||
# be converted (parsed) to a `String`.
|
||||
struct CBOR::Any
|
||||
|
||||
# All possible CBOR types.
|
||||
alias Type = Nil | Bool | String | Bytes |
|
||||
Int8 | UInt8 | Int16 | UInt16 | Int32 | UInt32 | Int64 | UInt64 | Int128 |
|
||||
Float32 | Float64 |
|
||||
Array(Any) |
|
||||
Hash(String, Any) | Hash(Any, Any) |
|
||||
Time
|
||||
|
||||
# Reads a `CBOR::Any` from a JSON::Any structure.
|
||||
def self.new(json : JSON::Any)
|
||||
new json.raw
|
||||
end
|
||||
|
||||
def self.new(value)
|
||||
case value
|
||||
when Nil
|
||||
new nil
|
||||
when Bool
|
||||
new value
|
||||
when Int64
|
||||
new value
|
||||
when Float64
|
||||
new value
|
||||
when String
|
||||
new value
|
||||
when Time
|
||||
new value
|
||||
|
||||
when Array(Type)
|
||||
ary = [] of CBOR::Any
|
||||
value.each do |v|
|
||||
ary << new(v)
|
||||
end
|
||||
new ary
|
||||
|
||||
when Array(JSON::Any)
|
||||
ary = [] of CBOR::Any
|
||||
value.each do |v|
|
||||
ary << new(v)
|
||||
end
|
||||
new ary
|
||||
|
||||
when Array(CBOR::Type)
|
||||
ary = [] of CBOR::Any
|
||||
value.each do |v|
|
||||
ary << new(v)
|
||||
end
|
||||
new ary
|
||||
|
||||
when Hash(String, JSON::Any)
|
||||
hash = {} of String => CBOR::Any
|
||||
value.each do |key, v|
|
||||
hash[key] = new(v)
|
||||
end
|
||||
new hash
|
||||
|
||||
when Hash(CBOR::Type, CBOR::Type)
|
||||
hash = {} of CBOR::Any => CBOR::Any
|
||||
value.each do |key, v|
|
||||
hash[new(key)] = new(v)
|
||||
end
|
||||
new hash
|
||||
|
||||
when Hash(String, Type)
|
||||
hash = {} of String => CBOR::Any
|
||||
value.each do |key, v|
|
||||
hash[key] = new(v)
|
||||
end
|
||||
new hash
|
||||
else
|
||||
raise "Unknown value type: #{value.class}"
|
||||
end
|
||||
end
|
||||
|
||||
# Reads a `CBOR::Any` from a Decoder.
|
||||
def self.new(decoder : CBOR::Decoder)
|
||||
new decoder.read_value
|
||||
end
|
||||
|
||||
# Reads a `CBOR::Any` from a buffer.
|
||||
def self.new(input : Slice(UInt8))
|
||||
new CBOR::Decoder.new(input)
|
||||
end
|
||||
|
||||
# Returns the raw underlying value.
|
||||
getter raw : Type
|
||||
|
||||
# Creates a `CBOR::Any` that wraps the given value.
|
||||
def initialize(@raw : Type)
|
||||
end
|
||||
|
||||
# Assumes the underlying value is an `Array` or `Hash` and returns its size.
|
||||
# Raises if the underlying value is not an `Array` or `Hash`.
|
||||
def size : Int
|
||||
case object = @raw
|
||||
when Array
|
||||
object.size
|
||||
when Hash
|
||||
object.size
|
||||
else
|
||||
raise "Expected Array or Hash for #size, not #{object.class}"
|
||||
end
|
||||
end
|
||||
|
||||
# Assumes the underlying value is an `Array` and returns the element
|
||||
# at the given index.
|
||||
# Raises if the underlying value is not an `Array`.
|
||||
def [](index : Int) : CBOR::Any
|
||||
case object = @raw
|
||||
when Array
|
||||
object[index]
|
||||
else
|
||||
raise "Expected Array for #[](index : Int), not #{object.class}"
|
||||
end
|
||||
end
|
||||
|
||||
# Assumes the underlying value is an `Array` and returns the element
|
||||
# at the given index, or `nil` if out of bounds.
|
||||
# Raises if the underlying value is not an `Array`.
|
||||
def []?(index : Int) : CBOR::Any?
|
||||
case object = @raw
|
||||
when Array
|
||||
object[index]?
|
||||
else
|
||||
raise "Expected Array for #[]?(index : Int), not #{object.class}"
|
||||
end
|
||||
end
|
||||
|
||||
# Assumes the underlying value is a `Hash` and returns the element
|
||||
# with the given key.
|
||||
# Raises if the underlying value is not a `Hash`.
|
||||
def [](key : String) : CBOR::Any
|
||||
case object = @raw
|
||||
when Hash
|
||||
object[key]
|
||||
else
|
||||
raise "Expected Hash for #[](key : String), not #{object.class}"
|
||||
end
|
||||
end
|
||||
|
||||
# Assumes the underlying value is a `Hash` and returns the element
|
||||
# with the given key, or `nil` if the key is not present.
|
||||
# Raises if the underlying value is not a `Hash`.
|
||||
def []?(key : String) : CBOR::Any?
|
||||
case object = @raw
|
||||
when Hash
|
||||
object[key]?
|
||||
else
|
||||
raise "Expected Hash for #[]?(key : String), not #{object.class}"
|
||||
end
|
||||
end
|
||||
|
||||
# Traverses the depth of a structure and returns the value.
|
||||
# Returns `nil` if not found.
|
||||
def dig?(key : String | Int, *subkeys)
|
||||
if value = self[key]?
|
||||
value.dig?(*subkeys)
|
||||
end
|
||||
end
|
||||
|
||||
# :nodoc:
|
||||
def dig?(key : String | Int)
|
||||
case @raw
|
||||
when Hash, Array
|
||||
self[key]?
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
# Traverses the depth of a structure and returns the value, otherwise raises.
|
||||
def dig(key : String | Int, *subkeys)
|
||||
if (value = self[key]) && value.responds_to?(:dig)
|
||||
return value.dig(*subkeys)
|
||||
end
|
||||
raise "CBOR::Any value not diggable for key: #{key.inspect}"
|
||||
end
|
||||
|
||||
# :nodoc:
|
||||
def dig(key : String | Int)
|
||||
self[key]
|
||||
end
|
||||
|
||||
# Checks that the underlying value is `Nil`, and returns `nil`.
|
||||
# Raises otherwise.
|
||||
def as_nil : Nil
|
||||
@raw.as(Nil)
|
||||
end
|
||||
|
||||
# Checks that the underlying value is `Bool`, and returns its value.
|
||||
# Raises otherwise.
|
||||
def as_bool : Bool
|
||||
@raw.as(Bool)
|
||||
end
|
||||
|
||||
# Checks that the underlying value is `Bool`, and returns its value.
|
||||
# Returns `nil` otherwise.
|
||||
def as_bool? : Bool?
|
||||
as_bool if @raw.is_a?(Bool)
|
||||
end
|
||||
|
||||
# Checks that the underlying value is `Int`, and returns its value as an `Int32`.
|
||||
# Raises otherwise.
|
||||
def as_i : Int32
|
||||
@raw.as(Int).to_i
|
||||
end
|
||||
|
||||
# Checks that the underlying value is `Int`, and returns its value as an `Int32`.
|
||||
# Returns `nil` otherwise.
|
||||
def as_i? : Int32?
|
||||
as_i if @raw.is_a?(Int)
|
||||
end
|
||||
|
||||
# Checks that the underlying value is `Int`, and returns its value as an `Int64`.
|
||||
# Raises otherwise.
|
||||
def as_i64 : Int64
|
||||
@raw.as(Int).to_i64
|
||||
end
|
||||
|
||||
# Checks that the underlying value is `Int`, and returns its value as an `Int64`.
|
||||
# Returns `nil` otherwise.
|
||||
def as_i64? : Int64?
|
||||
as_i64 if @raw.is_a?(Int64)
|
||||
end
|
||||
|
||||
# Checks that the underlying value is `Float`, and returns its value as an `Float64`.
|
||||
# Raises otherwise.
|
||||
def as_f : Float64
|
||||
@raw.as(Float64)
|
||||
end
|
||||
|
||||
# Checks that the underlying value is `Float`, and returns its value as an `Float64`.
|
||||
# Returns `nil` otherwise.
|
||||
def as_f? : Float64?
|
||||
@raw.as?(Float64)
|
||||
end
|
||||
|
||||
# Checks that the underlying value is `Float`, and returns its value as an `Float32`.
|
||||
# Raises otherwise.
|
||||
def as_f32 : Float32
|
||||
@raw.as(Float).to_f32
|
||||
end
|
||||
|
||||
# Checks that the underlying value is `Float`, and returns its value as an `Float32`.
|
||||
# Returns `nil` otherwise.
|
||||
def as_f32? : Float32?
|
||||
as_f32 if @raw.is_a?(Float)
|
||||
end
|
||||
|
||||
# Checks that the underlying value is `String`, and returns its value.
|
||||
# Raises otherwise.
|
||||
def as_s : String
|
||||
@raw.as(String)
|
||||
end
|
||||
|
||||
# Checks that the underlying value is `String`, and returns its value.
|
||||
# Returns `nil` otherwise.
|
||||
def as_s? : String?
|
||||
as_s if @raw.is_a?(String)
|
||||
end
|
||||
|
||||
# Checks that the underlying value is `Array`, and returns its value.
|
||||
# Raises otherwise.
|
||||
def as_a : Array(Any)
|
||||
@raw.as(Array)
|
||||
end
|
||||
|
||||
# Checks that the underlying value is `Array`, and returns its value.
|
||||
# Returns `nil` otherwise.
|
||||
def as_a? : Array(Any)?
|
||||
as_a if @raw.is_a?(Array)
|
||||
end
|
||||
|
||||
# Checks that the underlying value is `Hash`, and returns its value.
|
||||
# Raises otherwise.
|
||||
def as_h : Hash(String, Any)
|
||||
@raw.as(Hash)
|
||||
end
|
||||
|
||||
# Checks that the underlying value is `Hash`, and returns its value.
|
||||
# Returns `nil` otherwise.
|
||||
def as_h? : Hash(String, Any)?
|
||||
as_h if @raw.is_a?(Hash)
|
||||
end
|
||||
|
||||
# :nodoc:
|
||||
def inspect(io : IO) : Nil
|
||||
@raw.inspect(io)
|
||||
end
|
||||
|
||||
# :nodoc:
|
||||
def to_s(io : IO) : Nil
|
||||
@raw.to_s(io)
|
||||
end
|
||||
|
||||
# :nodoc:
|
||||
def pretty_print(pp)
|
||||
@raw.pretty_print(pp)
|
||||
end
|
||||
|
||||
# Returns `true` if both `self` and *other*'s raw object are equal.
|
||||
def ==(other : CBOR::Any)
|
||||
raw == other.raw
|
||||
end
|
||||
|
||||
# Returns `true` if the raw object is equal to *other*.
|
||||
def ==(other)
|
||||
raw == other
|
||||
end
|
||||
|
||||
# See `Object#hash(hasher)`
|
||||
def_hash raw
|
||||
|
||||
# :nodoc:
|
||||
def to_json(json : JSON::Builder)
|
||||
raw.to_json(json)
|
||||
end
|
||||
|
||||
def to_yaml(yaml : YAML::Nodes::Builder)
|
||||
raw.to_yaml(yaml)
|
||||
end
|
||||
|
||||
# Returns a new CBOR::Any instance with the `raw` value `dup`ed.
|
||||
def dup
|
||||
Any.new(raw.dup)
|
||||
end
|
||||
|
||||
# Returns a new CBOR::Any instance with the `raw` value `clone`ed.
|
||||
def clone
|
||||
Any.new(raw.clone)
|
||||
end
|
||||
end
|
||||
|
||||
class Object
|
||||
def ===(other : CBOR::Any)
|
||||
self === other.raw
|
||||
end
|
||||
end
|
||||
|
||||
struct Value
|
||||
def ==(other : CBOR::Any)
|
||||
self == other.raw
|
||||
end
|
||||
end
|
||||
|
||||
class Reference
|
||||
def ==(other : CBOR::Any)
|
||||
self == other.raw
|
||||
end
|
||||
end
|
||||
|
||||
class Array
|
||||
def ==(other : CBOR::Any)
|
||||
self == other.raw
|
||||
end
|
||||
end
|
||||
|
||||
class Hash
|
||||
def ==(other : CBOR::Any)
|
||||
self == other.raw
|
||||
end
|
||||
end
|
||||
|
||||
class Regex
|
||||
def ===(other : CBOR::Any)
|
||||
value = self === other.raw
|
||||
$~ = $~
|
||||
value
|
||||
end
|
||||
end
|
|
@ -2,9 +2,17 @@ class CBOR::Decoder
|
|||
@lexer : Lexer
|
||||
getter current_token : Token::T?
|
||||
|
||||
def reset
|
||||
# Decode until a certain point in the history (use_cbor_discriminator helper).
|
||||
def reset(value : Int32 | Int64 = 0)
|
||||
@lexer.reset
|
||||
@current_token = @lexer.next_token
|
||||
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)
|
||||
|
@ -12,6 +20,34 @@ class CBOR::Decoder
|
|||
@current_token = @lexer.next_token
|
||||
end
|
||||
|
||||
# This is similar to read_value but with a focus on a key for a hash.
|
||||
def read_key : HashKeyType
|
||||
v = case token = @current_token
|
||||
when Token::StringT
|
||||
finish_token!
|
||||
token.value.as(String)
|
||||
when Token::IntT
|
||||
finish_token!
|
||||
token.value
|
||||
when Token::FloatT
|
||||
finish_token!
|
||||
token.value
|
||||
when Token::BytesT
|
||||
finish_token!
|
||||
token.value.as(Bytes)
|
||||
when Token::SimpleValueT
|
||||
finish_token!
|
||||
token.value.to_t
|
||||
else
|
||||
puts "hash key with a #{token.class.to_s} value"
|
||||
unexpected_token(token)
|
||||
end
|
||||
if v.nil?
|
||||
raise ""
|
||||
end
|
||||
v
|
||||
end
|
||||
|
||||
def read_value : Type
|
||||
case token = @current_token
|
||||
when Token::TagT
|
||||
|
@ -30,6 +66,7 @@ class CBOR::Decoder
|
|||
finish_token!
|
||||
token.value
|
||||
when Token::SimpleValueT
|
||||
finish_token!
|
||||
token.value.to_t
|
||||
when Token::ArrayT
|
||||
finish_token!
|
||||
|
@ -38,14 +75,25 @@ class CBOR::Decoder
|
|||
arr
|
||||
when Token::MapT
|
||||
finish_token!
|
||||
map = Hash(Type, Type).new
|
||||
consume_sequence(token.size) { map[read_value] = read_value }
|
||||
map = Hash(HashKeyType, Type).new
|
||||
consume_sequence(token.size) {
|
||||
key = read_key
|
||||
map[key] = read_value
|
||||
}
|
||||
map
|
||||
else
|
||||
unexpected_token(token)
|
||||
end
|
||||
end
|
||||
|
||||
def read_simple_value : Bool?
|
||||
case token = @current_token
|
||||
when Token::SimpleValueT
|
||||
finish_token!
|
||||
token.value.to_t
|
||||
end
|
||||
end
|
||||
|
||||
def read_string : String
|
||||
case token = @current_token
|
||||
when Token::StringT
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
require "json"
|
||||
|
||||
class CBOR::Encoder
|
||||
def self.new(io : IO = IO::Memory.new)
|
||||
packer = new(io)
|
||||
|
@ -8,6 +10,14 @@ class CBOR::Encoder
|
|||
def initialize(@io : IO = IO::Memory.new)
|
||||
end
|
||||
|
||||
def write(cbor : CBOR::Any)
|
||||
write cbor.raw
|
||||
end
|
||||
|
||||
def write(j : JSON::Any)
|
||||
write j.raw
|
||||
end
|
||||
|
||||
def write(value : Nil | Nil.class, use_undefined : Bool = false)
|
||||
write(use_undefined ? SimpleValue::Undefined : SimpleValue::Null)
|
||||
end
|
||||
|
@ -34,6 +44,12 @@ class CBOR::Encoder
|
|||
write(value.to_s)
|
||||
end
|
||||
|
||||
def write(value : Time)
|
||||
write(CBOR::Tag::RFC3339Time)
|
||||
write(value.to_rfc3339)
|
||||
end
|
||||
|
||||
|
||||
def write(value : Float32 | Float64)
|
||||
case value
|
||||
when Float32
|
||||
|
|
|
@ -137,20 +137,32 @@ end
|
|||
# specified by [Section 2.4.1 of RFC 7049][1].
|
||||
#
|
||||
# [1]: https://tools.ietf.org/html/rfc7049#section-2.4.1
|
||||
def Time.new(decoder : CBOR::Decoder)
|
||||
case tag = decoder.read_tag
|
||||
when CBOR::Tag::RFC3339Time
|
||||
def Time.new(decoder : CBOR::Decoder) : Time
|
||||
# In case Time is formatted as a JSON#to_json String.
|
||||
value = case decoder.current_token
|
||||
when CBOR::Token::StringT
|
||||
Time::Format::RFC_3339.parse(decoder.read_string)
|
||||
when CBOR::Tag::EpochTime
|
||||
case num = decoder.read_num
|
||||
when Int
|
||||
Time.unix(num)
|
||||
when Float
|
||||
Time.unix_ms((BigFloat.new(num) * 1_000).to_u64)
|
||||
end
|
||||
else
|
||||
raise CBOR::ParseError.new("Expected tag to have value 0 or 1, got #{tag.value}")
|
||||
case tag = decoder.read_tag
|
||||
when CBOR::Tag::RFC3339Time
|
||||
Time::Format::RFC_3339.parse(decoder.read_string)
|
||||
when CBOR::Tag::EpochTime
|
||||
case num = decoder.read_num
|
||||
when Int
|
||||
Time.unix(num)
|
||||
when Float
|
||||
Time.unix_ms((BigFloat.new(num) * 1_000).to_u64)
|
||||
end
|
||||
else
|
||||
raise CBOR::ParseError.new("Expected tag to have value 0 or 1, got #{tag.value}")
|
||||
end
|
||||
end
|
||||
|
||||
unless value
|
||||
raise CBOR::ParseError.new("could not parse time representation")
|
||||
end
|
||||
|
||||
value
|
||||
end
|
||||
|
||||
# Reads the CBOR value as a BigInt.
|
||||
|
@ -212,14 +224,21 @@ end
|
|||
def Union.new(decoder : CBOR::Decoder)
|
||||
{% begin %}
|
||||
case decoder.current_token
|
||||
{% if T.includes? Nil %}
|
||||
when CBOR::Token::SimpleValueT
|
||||
return decoder.read_nil
|
||||
{% end %}
|
||||
{% if T.includes? Bool %}
|
||||
when CBOR::Token::SimpleValueT
|
||||
return decoder.read_bool
|
||||
{% end %}
|
||||
# This value could be either a boolean or nil.
|
||||
value = decoder.read_simple_value
|
||||
case value
|
||||
{% if T.includes? Bool %}
|
||||
when Bool
|
||||
return value
|
||||
{% end %}
|
||||
{% if T.includes? Nil %}
|
||||
when Nil
|
||||
return nil
|
||||
{% end %}
|
||||
else
|
||||
raise "value is neither Bool or Nil"
|
||||
end
|
||||
{% if T.includes? String %}
|
||||
when CBOR::Token::StringT
|
||||
return decoder.read_string
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
class CBOR::Lexer
|
||||
property io : IO
|
||||
def self.new(slice : Bytes)
|
||||
new IO::Memory.new(slice)
|
||||
end
|
||||
|
@ -8,8 +9,8 @@ class CBOR::Lexer
|
|||
def initialize(@io : IO)
|
||||
end
|
||||
|
||||
def reset
|
||||
@io.seek 0
|
||||
def reset(value : Int32 | Int64 = 0)
|
||||
@io.seek value
|
||||
@eof = false
|
||||
end
|
||||
|
||||
|
|
|
@ -353,8 +353,9 @@ module CBOR
|
|||
|
||||
# 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
|
||||
decoder.reset current_offset
|
||||
case v
|
||||
when Hash(CBOR::Type, CBOR::Type)
|
||||
discriminator_value = v[{{field.id.stringify}}]?
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
require "../cbor"
|
||||
require "uuid"
|
||||
|
||||
struct UUID
|
||||
# Creates UUID from CBOR using `CBOR::Decoder`.
|
||||
#
|
||||
# NOTE: `require "uuid/cbor"` is required to opt-in to this feature.
|
||||
#
|
||||
# ```
|
||||
# require "cbor"
|
||||
# require "uuid"
|
||||
# require "uuid/cbor"
|
||||
#
|
||||
# class Example
|
||||
# include CBOR::Serializable
|
||||
#
|
||||
# property id : UUID
|
||||
# end
|
||||
#
|
||||
# hash = {"id" => "ba714f86-cac6-42c7-8956-bcf5105e1b81"}
|
||||
# example = Example.from_cbor hash.to_cbor
|
||||
# example.id # => UUID(ba714f86-cac6-42c7-8956-bcf5105e1b81)
|
||||
# ```
|
||||
def self.new(pull : CBOR::Decoder)
|
||||
# Either the UUID was encoded as String or bytes (smaller).
|
||||
case pull.current_token
|
||||
when CBOR::Token::StringT
|
||||
new(pull.read_string)
|
||||
when CBOR::Token::BytesT
|
||||
new(pull.read_bytes)
|
||||
else
|
||||
raise "trying to get an UUID, but CBOR value isn't a string nor bytes: #{pull.current_token}"
|
||||
end
|
||||
end
|
||||
|
||||
# Returns UUID as CBOR value.
|
||||
#
|
||||
# NOTE: `require "uuid/cbor"` is required to opt-in to this feature.
|
||||
#
|
||||
# ```
|
||||
# uuid = UUID.new("87b3042b-9b9a-41b7-8b15-a93d3f17025e")
|
||||
# uuid.to_cbor
|
||||
# ```
|
||||
def to_cbor(cbor : CBOR::Encoder)
|
||||
cbor.write(@bytes.to_slice)
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue