crystal-cbor/src/cbor/any.cr

396 lines
9.3 KiB
Crystal

# `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