Adding CBOR::Any.
parent
b8229f9dde
commit
2be80d9692
|
@ -0,0 +1,392 @@
|
|||
# `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)
|
||||
|
||||
# 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 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
|
|
@ -10,6 +10,11 @@ class CBOR::Encoder
|
|||
def initialize(@io : IO = IO::Memory.new)
|
||||
end
|
||||
|
||||
def write(cbor : CBOR::Any)
|
||||
# Test each possible value of CBOR::Any
|
||||
write cbor.raw
|
||||
end
|
||||
|
||||
def write(j : JSON::Any)
|
||||
write j.raw
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue