transit-ruby 0.8.552-java

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,77 @@
1
+ # Copyright 2014 Cognitect. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS-IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ module Transit
16
+ # Transit::Writer marshals Ruby objects as transit values to an output stream.
17
+ # @see https://github.com/cognitect/transit-format
18
+ class Writer
19
+
20
+ # @param [Symbol] format required :json, :json_verbose, or :msgpack
21
+ # @param [IO] io required
22
+ # @param [Hash] opts optional
23
+ #
24
+ # Creates a new Writer configured to write to <tt>io</tt> in
25
+ # <tt>format</tt> (<tt>:json</tt>, <tt>:json_verbose</tt>,
26
+ # <tt>:msgpack</tt>).
27
+ #
28
+ # Use opts to register custom write handlers, associating each one
29
+ # with its type.
30
+ #
31
+ # @example
32
+ # json_writer = Transit::Writer.new(:json, io)
33
+ # json_verbose_writer = Transit::Writer.new(:json_verbose, io)
34
+ # msgpack_writer = Transit::Writer.new(:msgpack, io)
35
+ # writer_with_custom_handlers = Transit::Writer.new(:json, io,
36
+ # :handlers => {Point => PointWriteHandler})
37
+ #
38
+ # @see Transit::WriteHandlers
39
+ def initialize(format, io, opts={})
40
+ @marshaler = case format
41
+ when :json
42
+ Marshaler::Json.new(io,
43
+ {:prefer_strings => true,
44
+ :verbose => false,
45
+ :handlers => {},
46
+ :oj_opts => {:indent => -1}}.merge(opts))
47
+ when :json_verbose
48
+ Marshaler::VerboseJson.new(io,
49
+ {:prefer_strings => true,
50
+ :verbose => true,
51
+ :handlers => {}}.merge(opts))
52
+ else
53
+ Marshaler::MessagePack.new(io,
54
+ {:prefer_strings => false,
55
+ :verbose => false,
56
+ :handlers => {}}.merge(opts))
57
+ end
58
+ end
59
+
60
+ # Converts a Ruby object to a transit value and writes it to this
61
+ # Writer's output stream.
62
+ #
63
+ # @param obj the value to write
64
+ # @example
65
+ # writer = Transit::Writer.new(:json, io)
66
+ # writer.write(Date.new(2014,7,22))
67
+ if Transit::jruby?
68
+ def write(obj)
69
+ @marshaler.write(obj)
70
+ end
71
+ else
72
+ def write(obj)
73
+ @marshaler.marshal_top(obj)
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,85 @@
1
+ # Copyright 2014 Cognitect. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS-IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ unless File.exist?('../transit-format/examples/0.8/simple')
16
+ puts <<-MSG
17
+ Before you can run the rspec examples, you need to install the
18
+ the https://github.com/cognitect/transit-format repo in a sibling
19
+ directory, e.g.
20
+
21
+ cd ..
22
+ git clone https://github.com/cognitect/transit-format
23
+
24
+ That repo contains exemplars used by transit-ruby's rspec examples
25
+ (in ../transit-format/examples/0.8/simple), so then you can:
26
+
27
+ cd transit-ruby
28
+ rspec
29
+
30
+ MSG
31
+ exit
32
+ end
33
+
34
+ require 'json'
35
+ require 'rspec'
36
+ require 'wrong/adapters/rspec'
37
+ require 'transit'
38
+ require 'spec_helper-local' if File.exist?(File.expand_path('../spec_helper-local.rb', __FILE__))
39
+
40
+ RSpec.configure do |c|
41
+ c.alias_example_to :fit, :focus => true
42
+ c.filter_run_including :focus => true, :focused => true
43
+ c.run_all_when_everything_filtered = true
44
+ c.mock_with :nothing
45
+ end
46
+
47
+ ALPHA_NUM = 'abcdefghijklmnopqrstuvwxyzABCDESFHIJKLMNOPQRSTUVWXYZ_0123456789'
48
+
49
+ def random_alphanum
50
+ ALPHA_NUM[rand(ALPHA_NUM.size)]
51
+ end
52
+
53
+ def random_string(max_length=10)
54
+ l = rand(max_length) + 1
55
+ (Array.new(l).map {|x| random_alphanum}).join
56
+ end
57
+
58
+ def random_strings(max_length=10, n=100)
59
+ Array.new(n).map {random_string(max_length)}
60
+ end
61
+
62
+ def random_symbol(max_length=10)
63
+ random_string(max_length).to_sym
64
+ end
65
+
66
+ def ints_centered_on(m, n=5)
67
+ ((m-n)..(m+n)).to_a
68
+ end
69
+
70
+ def array_of_symbols(m, n=m)
71
+ seeds = (0...m).map {|i| ("key%04d" % i).to_sym}
72
+ seeds.cycle.take(n)
73
+ end
74
+
75
+ def hash_of_size(n)
76
+ Hash[array_of_symbols(n).zip((0..n).to_a)]
77
+ end
78
+
79
+ Person = Struct.new("Person", :first_name, :last_name, :birthdate)
80
+
81
+ class PersonHandler
82
+ def tag(_) "person"; end
83
+ def rep(p) {:first_name => p.first_name, :last_name => p.last_name, :birthdate => p.birthdate} end
84
+ def string_rep(p) nil end
85
+ end
@@ -0,0 +1,65 @@
1
+ # Copyright 2014 Cognitect. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS-IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ require 'spec_helper'
16
+
17
+ module Transit
18
+ describe DateTimeUtil do
19
+ describe "[to|from]_millis" do
20
+ it "round trips properly" do
21
+ 100.times do
22
+ n = DateTime.now
23
+ a = Transit::DateTimeUtil.to_millis(n)
24
+ b = Transit::DateTimeUtil.from_millis(a)
25
+ c = Transit::DateTimeUtil.to_millis(b)
26
+ d = Transit::DateTimeUtil.from_millis(c)
27
+ assert { a == c }
28
+ assert { b == d }
29
+ sleep(0.0001)
30
+ end
31
+ end
32
+ end
33
+
34
+ describe "to_millis" do
35
+ let(:time) { Time.at(1388631845) + 0.678 }
36
+
37
+ it "supports DateTime" do
38
+ assert { Transit::DateTimeUtil.to_millis(time.to_datetime) == 1388631845678 }
39
+ end
40
+
41
+ it "supports Time" do
42
+ assert { Transit::DateTimeUtil.to_millis(time) == 1388631845678 }
43
+ end
44
+
45
+ it "supports Date" do
46
+ assert { Transit::DateTimeUtil.to_millis(Date.new(2014,1,2)) == 1388620800000 }
47
+ end
48
+ end
49
+
50
+ describe "from_millis" do
51
+ it "converts to utc" do
52
+ t = DateTime.now
53
+ m = Transit::DateTimeUtil.to_millis(t)
54
+ f = Transit::DateTimeUtil.from_millis(m)
55
+ assert { f.zone == '+00:00' }
56
+ end
57
+
58
+ it "handles millis properly" do
59
+ assert { Transit::DateTimeUtil.from_millis(1388631845674) == DateTime.new(2014,1,2,3,4,5.674).new_offset(0) }
60
+ assert { Transit::DateTimeUtil.from_millis(1388631845675) == DateTime.new(2014,1,2,3,4,5.675).new_offset(0) }
61
+ assert { Transit::DateTimeUtil.from_millis(1388631845676) == DateTime.new(2014,1,2,3,4,5.676).new_offset(0) }
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,60 @@
1
+ # Copyright 2014 Cognitect. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS-IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ require 'spec_helper'
16
+
17
+ module Transit
18
+ describe Decoder do
19
+ def decode(o)
20
+ Decoder.new.decode(o)
21
+ end
22
+
23
+ describe "caching" do
24
+ it "decodes cacheable map keys" do
25
+ assert { decode([{"this" => "a"},{"^0" => "b"}]) == [{"this" => "a"},{"this" => "b"}] }
26
+ end
27
+
28
+ it "does not cache non-map-keys" do
29
+ assert { decode([{"a" => "~^!"},{"b" => "~^?"}]) == [{"a" => "^!"},{"b" => "^?"}] }
30
+ end
31
+ end
32
+
33
+ describe "formats" do
34
+ describe "JSON_M" do
35
+ it "converts an array starting with '^ ' to a map" do
36
+ assert { decode(["^ ", :a, :b, :c, :d]) == {:a => :b, :c => :d} }
37
+ end
38
+ end
39
+ end
40
+
41
+ describe "unrecognized input" do
42
+ it "decodes an unrecognized string to a TaggedValue" do
43
+ assert { decode("~Unrecognized") == TaggedValue.new("U", "nrecognized") }
44
+ end
45
+ end
46
+
47
+ describe "ints" do
48
+ it "decodes n as an Integer" do
49
+ 1.upto(64).each do |pow|
50
+ assert { decode("~n#{2**pow}").kind_of? Integer }
51
+ end
52
+ end
53
+ it "decodes i as an Integer" do
54
+ 1.upto(63).each do |pow|
55
+ assert { decode("~i#{2**pow - 1}").kind_of? Integer }
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,150 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Copyright 2014 Cognitect. All Rights Reserved.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS-IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ # -*- coding: utf-8 -*-
17
+ require 'spec_helper'
18
+
19
+ # Assumes that the examples are in the simple_examples dir at the top.
20
+
21
+ TOP_DIR=File.dirname(File.dirname(File.dirname(__FILE__)))
22
+
23
+ ARRAY_SIMPLE = [1, 2, 3]
24
+ ARRAY_MIXED = [0, 1, 2.0, true, false, 'five', :six, Transit::Symbol.new(:seven), '~eight', nil]
25
+ ARRAY_NESTED = [ARRAY_SIMPLE, ARRAY_MIXED]
26
+ SMALL_STRINGS = ["","a","ab","abc","abcd","abcde","abcdef"]
27
+ POWERS_OF_TWO = (0..65).map {|x| 2**x}
28
+ INTERESTING_INTS = (POWERS_OF_TWO.map {|x| ints_centered_on(x, 2)}).flatten
29
+
30
+ UUIDS = [
31
+ Transit::UUID.new('5a2cbea3-e8c6-428b-b525-21239370dd55'),
32
+ Transit::UUID.new('d1dc64fa-da79-444b-9fa4-d4412f427289'),
33
+ Transit::UUID.new('501a978e-3a3e-4060-b3be-1cf2bd4b1a38'),
34
+ Transit::UUID.new('b3ba141a-a776-48e4-9fae-a28ea8571f58')]
35
+
36
+ URIS = [
37
+ Addressable::URI.parse('http://example.com'),
38
+ Addressable::URI.parse('ftp://example.com'),
39
+ Addressable::URI.parse('file:///path/to/file.txt'),
40
+ Addressable::URI.parse('http://www.詹姆斯.com/')]
41
+
42
+ DATES = [-6106017600000, 0, 946728000000, 1396909037000].map {|x| Transit::DateTimeUtil.from_millis(x)}
43
+
44
+ SYMBOLS = [:a, :ab ,:abc ,:abcd, :abcde, :a1, :b2, :c3, :a_b]
45
+ TRANSIT_SYMBOLS = SYMBOLS.map {|x| Transit::Symbol.new(x)}
46
+
47
+ SET_SIMPLE = Set.new(ARRAY_SIMPLE)
48
+ SET_MIXED = Set.new(ARRAY_MIXED)
49
+ SET_NESTED= Set.new([SET_SIMPLE, SET_MIXED])
50
+
51
+ MAP_SIMPLE = {a: 1, b: 2, c: 3}
52
+ MAP_MIXED = {:a=>1, :b=>"a string", :c=>true}
53
+ MAP_NESTED = {simple: MAP_SIMPLE, mixed: MAP_MIXED}
54
+
55
+ Exemplar = Struct.new(:name, :expected_value)
56
+
57
+ EXEMPLARS = [
58
+ Exemplar.new('nil', nil),
59
+ Exemplar.new('true', true),
60
+ Exemplar.new('false', false),
61
+ Exemplar.new('zero', 0),
62
+ Exemplar.new('one', 1),
63
+ Exemplar.new('one_string', 'hello'),
64
+ Exemplar.new('one_keyword', :hello),
65
+ Exemplar.new('one_symbol', Transit::Symbol.new('hello')),
66
+ Exemplar.new('one_date', DateTime.new(2000,1,1,12)), # Transit::DateTimeUtil.from_millis(946728000000)),
67
+ Exemplar.new("vector_simple", ARRAY_SIMPLE),
68
+ Exemplar.new("vector_empty", []),
69
+ Exemplar.new("vector_mixed", ARRAY_MIXED),
70
+ Exemplar.new("vector_nested", ARRAY_NESTED),
71
+ Exemplar.new("small_strings", SMALL_STRINGS ),
72
+ Exemplar.new("strings_tilde", SMALL_STRINGS.map{|s| "~#{s}"}),
73
+ Exemplar.new("strings_hash", SMALL_STRINGS.map{|s| "##{s}"}),
74
+ Exemplar.new("strings_hat", SMALL_STRINGS.map{|s| "^#{s}"}),
75
+ Exemplar.new("small_ints", ints_centered_on(0)),
76
+ Exemplar.new("ints", (0...128).to_a),
77
+ Exemplar.new("ints_interesting", INTERESTING_INTS),
78
+ Exemplar.new("ints_interesting_neg", INTERESTING_INTS.map {|x| -1 * x}),
79
+ Exemplar.new("doubles_small", ints_centered_on(0).map {|x| Float(x)}),
80
+ Exemplar.new("doubles_interesting", [-3.14159, 3.14159, 4E11, 2.998E8, 6.626E-34]),
81
+ Exemplar.new('one_uuid', UUIDS.first),
82
+ Exemplar.new('uuids', UUIDS),
83
+ Exemplar.new('one_uri', URIS.first),
84
+ Exemplar.new('uris', URIS),
85
+ Exemplar.new('dates_interesting', DATES),
86
+ Exemplar.new('symbols', TRANSIT_SYMBOLS),
87
+ Exemplar.new('keywords', SYMBOLS),
88
+ Exemplar.new('list_simple', ARRAY_SIMPLE),
89
+ Exemplar.new('list_empty', []),
90
+ Exemplar.new('list_mixed', ARRAY_MIXED),
91
+ Exemplar.new('list_nested', [ARRAY_SIMPLE, ARRAY_MIXED]),
92
+ Exemplar.new('set_simple', SET_SIMPLE),
93
+ Exemplar.new("set_empty", Set.new),
94
+ Exemplar.new("set_mixed", SET_MIXED),
95
+ Exemplar.new("set_nested", SET_NESTED),
96
+ Exemplar.new('map_simple', MAP_SIMPLE),
97
+ Exemplar.new('map_mixed', MAP_MIXED),
98
+ Exemplar.new('map_nested', MAP_NESTED),
99
+ Exemplar.new('map_string_keys', {"first"=>1, "second"=>2, "third"=>3}),
100
+ Exemplar.new('map_numeric_keys', {1=>"one", 2=>"two"}),
101
+ Exemplar.new('map_vector_keys', {[1,1] => 'one', [2, 2] => 'two'}),
102
+ Exemplar.new('map_10_items', hash_of_size(10)),
103
+ Exemplar.new("maps_two_char_sym_keys", [{:aa=>1, :bb=>2}, {:aa=>3, :bb=>4}, {:aa=>5, :bb=>6}]),
104
+ Exemplar.new("maps_three_char_sym_keys", [{:aaa=>1, :bbb=>2}, {:aaa=>3, :bbb=>4}, {:aaa=>5, :bbb=>6}]),
105
+ Exemplar.new("maps_four_char_sym_keys", [{:aaaa=>1, :bbbb=>2}, {:aaaa=>3, :bbbb=>4}, {:aaaa=>5, :bbbb=>6}]),
106
+ Exemplar.new("maps_two_char_string_keys", [{'aa'=>1, 'bb'=>2}, {'aa'=>3, 'bb'=>4}, {'aa'=>5, 'bb'=>6}]),
107
+ Exemplar.new("maps_three_char_string_keys", [{'aaa'=>1, 'bbb'=>2}, {'aaa'=>3, 'bbb'=>4}, {'aaa'=>5, 'bbb'=>6}]),
108
+ Exemplar.new("maps_four_char_string_keys", [{'aaaa'=>1, 'bbbb'=>2}, {'aaaa'=>3, 'bbbb'=>4}, {'aaaa'=>5, 'bbbb'=>6}]),
109
+ Exemplar.new("maps_unrecognized_keys",
110
+ [Transit::TaggedValue.new("abcde", :anything), Transit::TaggedValue.new("fghij", :"anything-else")]),
111
+ Exemplar.new("map_unrecognized_vals", {key: "~Unrecognized"}),
112
+ Exemplar.new("vector_unrecognized_vals", ["~Unrecognized"]),
113
+ Exemplar.new("vector_1935_keywords_repeated_twice", array_of_symbols(1935, 3870)),
114
+ Exemplar.new("vector_1936_keywords_repeated_twice", array_of_symbols(1936, 3872)),
115
+ Exemplar.new("vector_1937_keywords_repeated_twice", array_of_symbols(1937, 3874)),
116
+ Exemplar.new("vector_special_numbers", [Float::NAN, Float::INFINITY, -Float::INFINITY])
117
+ ]
118
+
119
+ [10, 1935, 1936, 1937].each do |i|
120
+ EXEMPLARS << Exemplar.new( "map_#{i}_nested", {f: hash_of_size(i), s: hash_of_size(i)})
121
+ end
122
+
123
+ def verify_exemplar(exemplar, type, suffix)
124
+ path = "../transit-format/examples/0.8/simple/#{exemplar.name}#{suffix}"
125
+ it "reads what we expect from #{path}" do
126
+ raise "Can't open #{path}" unless File.exist?(path)
127
+ File.open(path) do |io|
128
+ actual_value = Transit::Reader.new(type, io).read
129
+ assert { exemplar.expected_value == actual_value }
130
+ end
131
+ end
132
+ end
133
+
134
+ module Transit
135
+ shared_examples "exemplars" do |type, suffix|
136
+ EXEMPLARS.each {|ex| verify_exemplar(ex, type, suffix)}
137
+ end
138
+
139
+ describe "JSON exemplars" do
140
+ include_examples "exemplars", :json, '.json'
141
+ end
142
+
143
+ describe "JSON-VERBOSE exemplars" do
144
+ include_examples "exemplars", :json_verbose, '.verbose.json'
145
+ end
146
+
147
+ describe "MessagePack exemplars" do
148
+ include_examples "exemplars", :msgpack, '.mp'
149
+ end
150
+ end
@@ -0,0 +1,165 @@
1
+ # Copyright 2014 Cognitect. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS-IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ require 'spec_helper'
16
+
17
+ module Transit
18
+ describe Reader do
19
+ shared_examples "read without a block" do |type|
20
+ it "reads a single top-level #{type} element" do
21
+ input = {:this => [1,2,3,{:that => "the other"}]}
22
+
23
+ io = StringIO.new('', 'w+')
24
+ writer = Transit::Writer.new(type, io)
25
+ writer.write(input)
26
+
27
+ reader = Transit::Reader.new(type, StringIO.new(io.string))
28
+ assert { reader.read == input }
29
+ end
30
+ end
31
+
32
+ describe "reading without a block" do
33
+ include_examples "read without a block", :json
34
+ include_examples "read without a block", :json_verbose
35
+ include_examples "read without a block", :msgpack
36
+ end
37
+
38
+ shared_examples "read with a block" do |type|
39
+ it "reads multiple top-level #{type} elements from a single IO" do
40
+ inputs = ["abc",
41
+ 123456789012345678901234567890,
42
+ [:this, :that],
43
+ {:this => [1,2,3,{:that => "the other"}]}]
44
+ outputs = []
45
+
46
+ io = StringIO.new('', 'w+')
47
+ writer = Transit::Writer.new(type, io)
48
+ inputs.each {|i| writer.write(i)}
49
+ reader = Transit::Reader.new(type, StringIO.new(io.string))
50
+ reader.read {|val| outputs << val}
51
+
52
+ assert { outputs == inputs }
53
+ end
54
+ end
55
+
56
+ describe "reading with a block" do
57
+ include_examples "read with a block", :json
58
+ include_examples "read with a block", :json_verbose
59
+ include_examples "read with a block", :msgpack
60
+ end
61
+
62
+ describe 'handler registration' do
63
+ describe 'overrides' do
64
+ describe 'ground types' do
65
+ Decoder::GROUND_TAGS.each do |ground|
66
+ it "prevents override of #{ground} handler" do
67
+ assert {
68
+ rescuing {
69
+ Reader.new(:json, StringIO.new, :handlers => {ground => Object.new})
70
+ }.message =~ /ground types/ }
71
+ end
72
+ end
73
+ end
74
+
75
+ it 'supports override of default string handlers' do
76
+ io = StringIO.new("[\"~rhttp://foo.com\"]","r+")
77
+ reader = Reader.new(:json, io, :handlers => {"r" => Class.new { def from_rep(v) "DECODED: #{v}" end}.new})
78
+ assert { reader.read == ["DECODED: http://foo.com"] }
79
+ end
80
+
81
+ it 'supports override of default hash handlers' do
82
+ my_uuid_class = Class.new(String)
83
+ my_uuid = my_uuid_class.new(UUID.new.to_s)
84
+ io = StringIO.new({"~#u" => my_uuid.to_s}.to_json)
85
+ reader = Reader.new(:json, io, :handlers => {"u" => Class.new { define_method(:from_rep) {|v| my_uuid_class.new(v)}}.new})
86
+ assert { reader.read == my_uuid }
87
+ end
88
+
89
+ it 'supports override of the default handler' do
90
+ io = StringIO.new("~Xabc".to_json)
91
+ reader = Reader.new(:json, io, :default_handler => Class.new { def from_rep(tag,val) raise "Unacceptable: #{s}" end}.new)
92
+ assert { rescuing {reader.read }.message =~ /Unacceptable/ }
93
+ end
94
+ end
95
+
96
+ describe 'extensions' do
97
+ it 'supports string-based extensions' do
98
+ io = StringIO.new("~D2014-03-15".to_json)
99
+ reader = Reader.new(:json, io, :handlers => {"D" => Class.new { def from_rep(v) Date.parse(v) end}.new})
100
+ assert { reader.read == Date.new(2014,3,15) }
101
+ end
102
+
103
+ it 'supports hash based extensions' do
104
+ io = StringIO.new({"~#Times2" => 44}.to_json)
105
+ reader = Reader.new(:json, io, :handlers => {"Times2" => Class.new { def from_rep(v) v * 2 end}.new})
106
+ assert { reader.read == 88 }
107
+ end
108
+
109
+ it 'supports hash based extensions that return nil' do
110
+ io = StringIO.new({"~#Nil" => :anything}.to_json)
111
+ reader = Reader.new(:json, io, :handlers => {"Nil" => Class.new { def from_rep(_) nil end}.new})
112
+ assert { reader.read == nil }
113
+ end
114
+
115
+ it 'supports hash based extensions that return false' do
116
+ io = StringIO.new({"~#False" => :anything}.to_json)
117
+ reader = Reader.new(:json, io, :handlers => {"False" => Class.new { def from_rep(_) false end}.new})
118
+ assert { reader.read == false }
119
+ end
120
+
121
+ it 'supports complex hash values' do
122
+ io = StringIO.new([
123
+ {"~#person"=>
124
+ {"~:first_name" => "Transit","~:last_name" => "Ruby","~:birthdate" => "~D2014-01-02"}},
125
+ {"^0"=>
126
+ {"^1" => "Transit","^2" => "Ruby","^3" => "~D2014-01-03"}}].to_json)
127
+
128
+ person_handler = Class.new do
129
+ def from_rep(v)
130
+ Person.new(v[:first_name],v[:last_name],v[:birthdate])
131
+ end
132
+ end
133
+ date_handler = Class.new do
134
+ def from_rep(v) Date.parse(v) end
135
+ end
136
+ reader = Reader.new(:json, io,
137
+ :handlers => {
138
+ "person" => person_handler.new,
139
+ "D" => date_handler.new})
140
+ expected = [Person.new("Transit", "Ruby", Date.new(2014,1,2)),
141
+ Person.new("Transit", "Ruby", Date.new(2014,1,3))]
142
+ assert { reader.read == expected }
143
+ end
144
+ end
145
+
146
+ describe 'Dates/Times' do
147
+ it "delivers a UTC DateTime for a non-UTC date string" do
148
+ io = StringIO.new(["~t2014-04-14T12:20:50.152-05:00"].to_json)
149
+ reader = Reader.new(:json, io)
150
+ expect(Transit::DateTimeUtil.to_millis(reader.read.first)).to eq(Transit::DateTimeUtil.to_millis(DateTime.new(2014,4,14,17,20,50.152,"Z")))
151
+ end
152
+ end
153
+
154
+ describe 'edge cases found in generative testing' do
155
+ it 'caches nested structures correctly' do
156
+ io = StringIO.new(["~#cmap",[["~#point",["~n10","~n11"]],"~:foobar",["^1",["~n10","~n13"]],"^2"]].to_json)
157
+ reader = Reader.new(:json, io)
158
+ expected = {TaggedValue.new("point", [10,11]) => :foobar,
159
+ TaggedValue.new("point", [10,13]) => :foobar}
160
+ assert { reader.read == expected }
161
+ end
162
+ end
163
+ end
164
+ end
165
+ end