-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This is a nearly complete rewrite of the library using the new postfix tag syntax described here: tjson/tjson-spec#30
- Loading branch information
Showing
24 changed files
with
465 additions
and
388 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
# 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 | ||
TJSON::DataType[result[:type]].new(parse(result[: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 | ||
end | ||
end | ||
|
||
require "tjson/datatype/nonscalar" | ||
require "tjson/datatype/scalar" | ||
|
||
require "tjson/datatype/binary" | ||
require "tjson/datatype/number" | ||
require "tjson/datatype/string" | ||
require "tjson/datatype/timestamp" | ||
|
||
# TJSON does not presently support user-extensible types | ||
TJSON::DataType::TAGS = { | ||
# Object (non-scalar with self-describing types) | ||
"O" => TJSON::DataType::Object.new(nil).freeze, | ||
|
||
# Non-scalars | ||
"A" => TJSON::DataType::Array, | ||
|
||
# Scalars | ||
"b" => TJSON::DataType::Binary64.new.freeze, | ||
"b16" => TJSON::DataType::Binary16.new.freeze, | ||
"b32" => TJSON::DataType::Binary32.new.freeze, | ||
"b64" => TJSON::DataType::Binary64.new.freeze, | ||
"f" => TJSON::DataType::Float.new.freeze, | ||
"i" => TJSON::DataType::SignedInt.new.freeze, | ||
"s" => TJSON::DataType::UnicodeString.new.freeze, | ||
"t" => TJSON::DataType::Timestamp.new.freeze, | ||
"u" => TJSON::DataType::UnsignedInt.new.freeze | ||
}.freeze |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
# frozen_string_literal: true | ||
|
||
module TJSON | ||
class DataType | ||
# Binary Data | ||
class Binary < Scalar; end | ||
|
||
# Base16-serialized binary data | ||
class Binary16 < Binary | ||
def convert(str) | ||
raise TJSON::TypeError, "expected String, got #{str.class}: #{str.inspect}" unless str.is_a?(::String) | ||
raise TJSON::ParseError, "base16 must be lower case: #{str.inspect}" if str =~ /[A-F]/ | ||
raise TJSON::ParseError, "invalid base16: #{str.inspect}" unless str =~ /\A[a-f0-9]*\z/ | ||
|
||
[str].pack("H*") | ||
end | ||
end | ||
|
||
# Base32-serialized binary data | ||
class Binary32 < Binary | ||
def convert(str) | ||
raise TJSON::TypeError, "expected String, got #{str.class}: #{str.inspect}" unless str.is_a?(::String) | ||
raise TJSON::ParseError, "base32 must be lower case: #{str.inspect}" if str =~ /[A-Z]/ | ||
raise TJSON::ParseError, "padding disallowed: #{str.inspect}" if str.include?("=") | ||
raise TJSON::ParseError, "invalid base32: #{str.inspect}" unless str =~ /\A[a-z2-7]*\z/ | ||
|
||
::Base32.decode(str.upcase).force_encoding(Encoding::BINARY) | ||
end | ||
end | ||
|
||
# Base64-serialized binary data | ||
class Binary64 < Binary | ||
def convert(str) | ||
raise TJSON::TypeError, "expected String, got #{str.class}: #{str.inspect}" unless str.is_a?(::String) | ||
raise TJSON::ParseError, "base64url only: #{str.inspect}" if str =~ %r{\+|\/} | ||
raise TJSON::ParseError, "padding disallowed: #{str.inspect}" if str.include?("=") | ||
raise TJSON::ParseError, "invalid base64url: #{str.inspect}" unless str =~ /\A[A-Za-z0-9\-_]*\z/ | ||
|
||
# Add padding, as older Rubies (< 2.3) require it | ||
str = str.ljust((str.length + 3) & ~3, "=") if (str.length % 4).nonzero? | ||
|
||
::Base64.urlsafe_decode64(str) | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
# frozen_string_literal: true | ||
|
||
module TJSON | ||
class DataType | ||
# 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 scalar? | ||
false | ||
end | ||
end | ||
|
||
# TJSON arrays | ||
class Array < NonScalar | ||
def convert(array) | ||
raise TJSON::TypeError, "expected Array, got #{array.class}" unless array.is_a?(::Array) | ||
array.map! { |o| @inner_type.convert(o) } | ||
end | ||
end | ||
|
||
# TJSON objects | ||
class Object < NonScalar | ||
def convert(obj) | ||
raise TJSON::TypeError, "expected TJSON::Object, got #{obj.class}" unless obj.is_a?(TJSON::Object) | ||
|
||
# Objects handle their own member conversions | ||
obj | ||
end | ||
|
||
def inspect | ||
"#<#{self.class}>" | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
# frozen_string_literal: true | ||
|
||
module TJSON | ||
class DataType | ||
# Numbers | ||
class Number < Scalar; end | ||
class Integer < Scalar; end | ||
|
||
# Floating point type | ||
class Float < Number | ||
def convert(float) | ||
raise TJSON::TypeError, "expected Float, got #{float.class}" unless float.is_a?(::Numeric) | ||
float.to_f | ||
end | ||
end | ||
|
||
# Signed 64-bit integer | ||
class SignedInt < Integer | ||
def convert(str) | ||
raise TJSON::TypeError, "expected String, got #{str.class}: #{str.inspect}" unless str.is_a?(::String) | ||
raise TJSON::ParseError, "invalid integer: #{str.inspect}" unless str =~ /\A\-?(0|[1-9][0-9]*)\z/ | ||
|
||
result = Integer(str, 10) | ||
raise TJSON::ParseError, "oversized integer: #{result}" if result > 9_223_372_036_854_775_807 | ||
raise TJSON::ParseError, "undersized integer: #{result}" if result < -9_223_372_036_854_775_808 | ||
|
||
result | ||
end | ||
end | ||
|
||
# Unsigned 64-bit integer | ||
class UnsignedInt < Integer | ||
def convert(str) | ||
raise TJSON::TypeError, "expected String, got #{str.class}: #{str.inspect}" unless str.is_a?(::String) | ||
raise TJSON::ParseError, "invalid integer: #{str.inspect}" unless str =~ /\A(0|[1-9][0-9]*)\z/ | ||
|
||
result = Integer(str, 10) | ||
raise TJSON::ParseError, "oversized integer: #{result}" if result > 18_446_744_073_709_551_615 | ||
|
||
result | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
# frozen_string_literal: true | ||
|
||
module TJSON | ||
class DataType | ||
# Scalar types | ||
class Scalar < TJSON::DataType | ||
def scalar? | ||
true | ||
end | ||
|
||
def inspect | ||
"#<#{self.class}>" | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
# frozen_string_literal: true | ||
|
||
module TJSON | ||
class DataType | ||
# Unicode String type | ||
class UnicodeString < Scalar | ||
def convert(str) | ||
raise TJSON::TypeError, "expected String, got #{str.class}: #{str.inspect}" unless str.is_a?(::String) | ||
raise TJSON::EncodingError, "expected UTF-8, got #{str.encoding.inspect}" unless str.encoding == Encoding::UTF_8 | ||
str | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
# frozen_string_literal: true | ||
|
||
module TJSON | ||
class DataType | ||
# RFC3339 timestamp (Z-normalized) | ||
class Timestamp < Scalar | ||
def convert(str) | ||
raise TJSON::TypeError, "expected String, got #{str.class}: #{str.inspect}" unless str.is_a?(::String) | ||
raise TJSON::ParseError, "invalid timestamp: #{str.inspect}" unless str =~ /\A\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z\z/ | ||
|
||
::Time.iso8601(str) | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.