Add CBOR::Any.
parent
3c82ed329f
commit
0f25c5da9f
|
@ -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)
|
def initialize(@io : IO = IO::Memory.new)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def write(cbor : CBOR::Any)
|
||||||
|
# Test each possible value of CBOR::Any
|
||||||
|
write cbor.raw
|
||||||
|
end
|
||||||
|
|
||||||
def write(j : JSON::Any)
|
def write(j : JSON::Any)
|
||||||
write j.raw
|
write j.raw
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in New Issue