tjson/tjson-ruby

View on GitHub
lib/tjson/datatype.rb

Summary

Maintainability
A
1 hr
Test Coverage
# frozen_string_literal: true

module TJSON
  # Hierarchy of TJSON types
  class DataType
    # Find a type by its tag
    def self.[](tag)
      TAGS[tag] || raise(TJSON::TypeError, "unknown tag: #{tag.inspect}")
    end

    def self.parse(tag)
      raise TJSON::TypeError, "expected String, got #{tag.class}" unless tag.is_a?(::String)

      if tag == "O"
        # Object
        TJSON::DataType[tag]
      elsif (result = tag.match(/\A(?<type>[A-Z][a-z0-9]*)\<(?<inner>.*)\>\z/))
        # Non-scalar
        inner = parse(result[:inner]) unless result[:inner].empty?
        TJSON::DataType[result[:type]].new(inner).freeze
      elsif tag =~ /\A[a-z][a-z0-9]*\z/
        # Scalar
        TJSON::DataType[tag]
      else
        raise TJSON::ParseError, "couldn't parse tag: #{tag.inspect}" unless result
      end
    end

    def self.identify_type(obj)
      case obj
      when Hash               then self["O"]
      when ::Array            then TJSON::DataType::Array.identify_type(obj)
      when ::Set              then TJSON::DataType::Set.identify_type(obj)
      when ::String, Symbol   then obj.encoding == Encoding::BINARY ? self["d"] : self["s"]
      when ::Integer          then self["i"]
      when ::Float            then self["f"]
      when ::TrueClass        then self["v"]
      when ::FalseClass       then self["d"]
      when ::Time, ::DateTime then self["t"]
      else raise TypeError, "don't know how to serialize #{obj.class} as TJSON"
      end
    end

    def self.encode(obj)
      identify_type(obj).encode(obj)
    end

    def tag
      raise NotImplementError, "no #tag defined for #{self.class}"
    end

    def decode(_value)
      raise NotImplementedError, "#{self.class} does not implement #decode"
    end

    def encode(_value)
      raise NotImplementedError, "#{self.class} does not implement #genreate"
    end

    # Scalar types
    class Scalar < TJSON::DataType
      def inspect
        "#<#{self.class}>"
      end
    end

    # Non-scalar types
    class NonScalar < TJSON::DataType
      attr_reader :inner_type

      def initialize(inner_type)
        @inner_type = inner_type
      end

      def inspect
        "#<#{self.class}<#{@inner_type.inspect}>>"
      end

      def ==(other)
        self.class == other.class && inner_type == other.inner_type
      end
    end
  end
end