transit-ruby 0.8.552-java

Sign up to get free protection for your applications and to get access to all the features.
@@ -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