ffi-yajl 0.1.7 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a73785d92badd552df56ecd0fd14f53537bc5c5b
4
- data.tar.gz: 6c7f329234428cd07ec9fbf73b1040c27d82fab3
3
+ metadata.gz: ad912796d62ff75534ab7056d42be6ddb3b340bd
4
+ data.tar.gz: 1368094d6809554303390c8dc38e33b837d6da5d
5
5
  SHA512:
6
- metadata.gz: 2938f35493a720cfcf72ee73dfcccd88e56ef2cc2b58bcd5859a6ec7dde34100891a69c3b1139edf7a75c0f7c181ade595a0de6cf3d37fb73336c76ee5be631e
7
- data.tar.gz: a40243af1ddc3095562a3af781bcf73c2cb04691ce4cb2a090ca85e4987508b0673e8e89f0176364dbed765e0369ba3d4185b3eafcba52f8e4056d41e37da487
6
+ metadata.gz: 7e7d9eaedc22292ea2817b75cba4fa04c90cf6ca7032675a0d8b400e4b1f7df8bc7120ff3b9a0952c55f8c7c3129d6279f92f5ae97671368979cf9f0af99c024
7
+ data.tar.gz: 690d495c1fe962fefae46cb73520e8690151ab40424cb2ebca0f162fe2b5bbcf1cac481be5b0ce73f408e4c03eaa676ae7284601e0b992e6b85c8e13449e1919
data/bin/ffi-yajl-bench CHANGED
@@ -33,4 +33,5 @@ if opts[:profile]
33
33
  FFI_Yajl::Benchmark::ParseProfileRubyProf.new().run()
34
34
  else
35
35
  FFI_Yajl::Benchmark::Parse.new().run()
36
+ FFI_Yajl::Benchmark::Encode.new().run()
36
37
  end
@@ -10,6 +10,7 @@ static VALUE mFFI_Yajl, mExt, mParser, cParseError;
10
10
 
11
11
  typedef struct {
12
12
  VALUE self;
13
+ int symbolizeKeys;
13
14
  } CTX;
14
15
 
15
16
  void set_value(CTX *ctx, VALUE val) {
@@ -74,7 +75,7 @@ int double_callback(void *ctx, double doubleVal) {
74
75
  }
75
76
 
76
77
  int number_callback(void *ctx, const char *numberVal, size_t numberLen) {
77
- char buf[numberLen+1];
78
+ char *buf = (char *)malloc(numberLen+1);
78
79
  buf[numberLen] = 0;
79
80
  memcpy(buf, numberVal, numberLen);
80
81
  if (memchr(buf, '.', numberLen) ||
@@ -84,21 +85,14 @@ int number_callback(void *ctx, const char *numberVal, size_t numberLen) {
84
85
  } else {
85
86
  set_value(ctx, rb_cstr2inum(buf, 10));
86
87
  }
88
+ free(buf);
87
89
  return 1;
88
90
  }
89
91
 
90
92
  int string_callback(void *ctx, const unsigned char *stringVal, size_t stringLen) {
91
- char buf[stringLen+1];
92
- VALUE str;
93
+ VALUE str = rb_str_new((const char *)stringVal, stringLen);
93
94
  #ifdef HAVE_RUBY_ENCODING_H
94
- rb_encoding *default_internal_enc;
95
- #endif
96
-
97
- buf[stringLen] = 0;
98
- memcpy(buf, stringVal, stringLen);
99
- str = rb_str_new2(buf);
100
- #ifdef HAVE_RUBY_ENCODING_H
101
- default_internal_enc = rb_default_internal_encoding();
95
+ rb_encoding *default_internal_enc = rb_default_internal_encoding();
102
96
  rb_enc_associate(str, utf8Encoding);
103
97
  if (default_internal_enc) {
104
98
  str = rb_str_export_to_enc(str, default_internal_enc);
@@ -114,23 +108,30 @@ int start_map_callback(void *ctx) {
114
108
  }
115
109
 
116
110
  int map_key_callback(void *ctx, const unsigned char *stringVal, size_t stringLen) {
117
- char buf[stringLen+1];
118
- VALUE str;
111
+ VALUE key;
119
112
  #ifdef HAVE_RUBY_ENCODING_H
120
113
  rb_encoding *default_internal_enc;
121
114
  #endif
122
115
 
123
- buf[stringLen] = 0;
124
- memcpy(buf, stringVal, stringLen);
125
- str = rb_str_new2(buf);
116
+ if ( ((CTX *)ctx)->symbolizeKeys ) {
126
117
  #ifdef HAVE_RUBY_ENCODING_H
127
- default_internal_enc = rb_default_internal_encoding();
128
- rb_enc_associate(str, utf8Encoding);
129
- if (default_internal_enc) {
130
- str = rb_str_export_to_enc(str, default_internal_enc);
131
- }
118
+ ID id = rb_intern3((const char *)stringVal, stringLen, utf8Encoding);
119
+ key = ID2SYM(id);
120
+ #else
121
+ VALUE str = rb_str_new((const char *)stringVal, stringLen);
122
+ key = rb_str_intern(str);
123
+ #endif
124
+ } else {
125
+ key = rb_str_new((const char *)stringVal, stringLen);
126
+ #ifdef HAVE_RUBY_ENCODING_H
127
+ default_internal_enc = rb_default_internal_encoding();
128
+ rb_enc_associate(key, utf8Encoding);
129
+ if (default_internal_enc) {
130
+ key = rb_str_export_to_enc(key, default_internal_enc);
131
+ }
132
132
  #endif
133
- set_key(ctx,str);
133
+ }
134
+ set_key(ctx, key);
134
135
  return 1;
135
136
  }
136
137
 
@@ -163,7 +164,15 @@ static yajl_callbacks callbacks = {
163
164
  end_array_callback,
164
165
  };
165
166
 
166
- static VALUE mParser_do_yajl_parse(VALUE self, VALUE str, VALUE opts) {
167
+ int get_opts_key(VALUE self, char *key) {
168
+ VALUE opts = rb_iv_get(self, "@opts");
169
+ if (TYPE(opts) != T_HASH) {
170
+ rb_raise(rb_eTypeError, "opts is not a valid hash");
171
+ }
172
+ return rb_hash_aref(opts, ID2SYM(rb_intern(key))) == Qtrue;
173
+ }
174
+
175
+ static VALUE mParser_do_yajl_parse(VALUE self, VALUE str, VALUE yajl_opts) {
167
176
  yajl_handle hand;
168
177
  yajl_status stat;
169
178
  unsigned char *err;
@@ -173,8 +182,26 @@ static VALUE mParser_do_yajl_parse(VALUE self, VALUE str, VALUE opts) {
173
182
  rb_ivar_set(self, rb_intern("key_stack"), rb_ary_new());
174
183
 
175
184
  ctx.self = self;
185
+ ctx.symbolizeKeys = get_opts_key(self, "symbolize_keys");
176
186
 
177
187
  hand = yajl_alloc(&callbacks, NULL, (void *)&ctx);
188
+
189
+ if (rb_hash_aref(yajl_opts, ID2SYM(rb_intern("yajl_allow_comments"))) == Qtrue) {
190
+ yajl_config(hand, yajl_allow_comments, 1);
191
+ }
192
+ if (rb_hash_aref(yajl_opts, ID2SYM(rb_intern("yajl_dont_validate_strings"))) == Qtrue) {
193
+ yajl_config(hand, yajl_dont_validate_strings, 1);
194
+ }
195
+ if (rb_hash_aref(yajl_opts, ID2SYM(rb_intern("yajl_allow_trailing_garbage"))) == Qtrue) {
196
+ yajl_config(hand, yajl_allow_trailing_garbage, 1);
197
+ }
198
+ if (rb_hash_aref(yajl_opts, ID2SYM(rb_intern("yajl_allow_multiple_values"))) == Qtrue) {
199
+ yajl_config(hand, yajl_allow_multiple_values, 1);
200
+ }
201
+ if (rb_hash_aref(yajl_opts, ID2SYM(rb_intern("yajl_allow_partial_values"))) == Qtrue) {
202
+ yajl_config(hand, yajl_allow_partial_values, 1);
203
+ }
204
+
178
205
  if ((stat = yajl_parse(hand, (unsigned char *)RSTRING_PTR(str), RSTRING_LEN(str))) != yajl_status_ok) {
179
206
  err = yajl_get_error(hand, 1, (unsigned char *)RSTRING_PTR(str), RSTRING_LEN(str));
180
207
  goto raise;
@@ -101,6 +101,11 @@ module FFI_Yajl
101
101
  JSON.generate(hash)
102
102
  }
103
103
  }
104
+ x.report("JSON.fast_generate") {
105
+ times.times {
106
+ JSON.fast_generate(hash)
107
+ }
108
+ }
104
109
  end
105
110
  if defined?(Psych)
106
111
  x.report("Psych.to_json") {
@@ -120,13 +125,13 @@ module FFI_Yajl
120
125
  }
121
126
  end
122
127
  end
123
- if defined?(ActiveSupport::JSON)
124
- x.report("ActiveSupport::JSON.encode") {
125
- times.times {
126
- ActiveSupport::JSON.encode(hash)
127
- }
128
- }
129
- end
128
+ # if defined?(ActiveSupport::JSON)
129
+ # x.report("ActiveSupport::JSON.encode") {
130
+ # times.times {
131
+ # ActiveSupport::JSON.encode(hash)
132
+ # }
133
+ # }
134
+ # end
130
135
  }
131
136
  end
132
137
  end
@@ -28,6 +28,10 @@ begin
28
28
  require 'active_support'
29
29
  rescue LoadError
30
30
  end
31
+ begin
32
+ require 'oj'
33
+ rescue LoadError
34
+ end
31
35
 
32
36
  class FFI_Yajl::Benchmark::Parse
33
37
 
@@ -79,6 +83,15 @@ class FFI_Yajl::Benchmark::Parse
79
83
  }
80
84
  }
81
85
  end
86
+ if defined?(Oj)
87
+ x.report {
88
+ puts "Oj.load"
89
+ times.times {
90
+ json.rewind
91
+ Oj.load(json.read)
92
+ }
93
+ }
94
+ end
82
95
  if defined?(JSON)
83
96
  x.report {
84
97
  puts "JSON.parse"
data/lib/ffi_yajl/ext.rb CHANGED
@@ -24,6 +24,7 @@ module FFI_Yajl
24
24
  elsif defined?(DL) && DL.respond_to?(:dlopen)
25
25
  ::DL.dlopen(libpath)
26
26
  else
27
+ extend ::FFI::Library
27
28
  ffi_lib libpath
28
29
  end
29
30
 
data/lib/ffi_yajl/ffi.rb CHANGED
@@ -87,7 +87,7 @@ module FFI_Yajl
87
87
  attach_function :yajl_free_error, [:yajl_handle, :ustring], :void
88
88
 
89
89
  #
90
- attach_function :yajl_config, [:yajl_handle, :yajl_option, :int], :int
90
+ attach_function :yajl_config, [:yajl_handle, :yajl_option, :varargs], :int
91
91
 
92
92
  attach_function :yajl_gen_config, [:yajl_gen, :yajl_gen_option, :varargs], :int
93
93
 
@@ -51,7 +51,7 @@ module FFI_Yajl
51
51
  s = stringval.slice(0,stringlen)
52
52
  s.force_encoding('UTF-8') if defined? Encoding
53
53
  # XXX: I can't think of a better way to do this right now. need to call to_f if and only if its a float.
54
- v = ( s =~ /\./ ) ? s.to_f : s.to_i
54
+ v = ( s =~ /[\.eE]/ ) ? s.to_f : s.to_i
55
55
  set_value(v)
56
56
  1
57
57
  end
@@ -73,7 +73,7 @@ module FFI_Yajl
73
73
  @map_key_callback = ::FFI::Function.new(:int, [:pointer, :string, :size_t]) do |ctx, key, keylen|
74
74
  s = key.slice(0,keylen)
75
75
  s.force_encoding('UTF-8') if defined? Encoding
76
- self.key = s
76
+ self.key = @opts[:symbolize_keys] ? s.to_sym : s
77
77
  1
78
78
  end
79
79
  @end_map_callback = ::FFI::Function.new(:int, [:pointer]) do |ctx|
@@ -94,7 +94,7 @@ module FFI_Yajl
94
94
  end
95
95
 
96
96
 
97
- def do_yajl_parse(str, opts = {})
97
+ def do_yajl_parse(str, yajl_opts = {})
98
98
  setup_callbacks
99
99
  callback_ptr = ::FFI::MemoryPointer.new(::FFI_Yajl::YajlCallbacks)
100
100
  callbacks = ::FFI_Yajl::YajlCallbacks.new(callback_ptr)
@@ -110,6 +110,24 @@ module FFI_Yajl
110
110
  callbacks[:yajl_start_array] = @start_array_callback
111
111
  callbacks[:yajl_end_array] = @end_array_callback
112
112
  yajl_handle = ::FFI_Yajl.yajl_alloc(callback_ptr, nil, nil)
113
+
114
+ # configure the yajl parser
115
+ if yajl_opts[:yajl_allow_comments]
116
+ ::FFI_Yajl.yajl_config(yajl_handle, :yajl_allow_comments, :int, 1)
117
+ end
118
+ if yajl_opts[:yajl_dont_validate_strings]
119
+ ::FFI_Yajl.yajl_config(yajl_handle, :yajl_dont_validate_strings, :int, 1)
120
+ end
121
+ if yajl_opts[:yajl_allow_trailing_garbage]
122
+ ::FFI_Yajl.yajl_config(yajl_handle, :yajl_allow_trailing_garbage, :int, 1)
123
+ end
124
+ if yajl_opts[:yajl_allow_multiple_values]
125
+ ::FFI_Yajl.yajl_config(yajl_handle, :yajl_allow_multiple_values, :int, 1)
126
+ end
127
+ if yajl_opts[:yajl_allow_partial_values]
128
+ ::FFI_Yajl.yajl_config(yajl_handle, :yajl_allow_partial_values, :int, 1)
129
+ end
130
+
113
131
  if ( stat = ::FFI_Yajl.yajl_parse(yajl_handle, str, str.bytesize) != :yajl_status_ok )
114
132
  # FIXME: dup the error and call yajl_free_error?
115
133
  error = ::FFI_Yajl.yajl_get_error(yajl_handle, 1, str, str.bytesize)
@@ -37,8 +37,7 @@ module JSON
37
37
  end
38
38
 
39
39
  def self.default_options
40
- #@default_options ||= {:symbolize_keys => false}
41
- @default_options ||= {}
40
+ @default_options ||= {:symbolize_keys => false}
42
41
  end
43
42
 
44
43
  def self.parse(str, opts=JSON.default_options)
@@ -25,16 +25,35 @@ module FFI_Yajl
25
25
  end
26
26
 
27
27
  def initialize(opts = {})
28
- @opts = opts
28
+ @opts = opts ? opts.dup : {}
29
+ # JSON gem uses 'symbolize_names' and ruby-yajl supports this as well
30
+ @opts[:symbolize_keys] = true if @opts[:symbolize_names]
29
31
  end
30
32
 
31
33
  def parse(str)
32
34
  # initialization that we can do in pure ruby
33
35
  yajl_opts = {}
34
36
 
37
+ if @opts[:check_utf8] == false && @opts[:dont_validate_strings] == false
38
+ raise ArgumentError, "options check_utf8 and dont_validate_strings are both false which conflict"
39
+ end
40
+ if @opts[:check_utf8] == true && @opts[:dont_validate_strings] == true
41
+ raise ArgumentError, "options check_utf8 and dont_validate_strings are both true which conflict"
42
+ end
43
+
44
+ yajl_opts[:yajl_allow_comments] = @opts[:allow_comments]
45
+ yajl_opts[:yajl_dont_validate_strings] = (@opts[:check_utf8] == false || @opts[:dont_validate_strings])
46
+ yajl_opts[:yajl_allow_trailing_garbage] = @opts[:allow_trailing_garbage]
47
+ yajl_opts[:yajl_allow_multiple_values] = @opts[:allow_multiple_values]
48
+ yajl_opts[:yajl_allow_partial_values] = @opts[:allow_partial_values]
49
+
35
50
  # XXX: bug-compat with ruby-yajl
36
51
  return nil if str == ""
37
52
 
53
+ if str.respond_to?(:read)
54
+ str = str.read()
55
+ end
56
+
38
57
  # call either the ext or ffi hook
39
58
  do_yajl_parse(str, yajl_opts)
40
59
  end
@@ -1,3 +1,3 @@
1
1
  module FFI_Yajl
2
- VERSION = "0.1.7"
2
+ VERSION = "0.2.0"
3
3
  end
@@ -11,7 +11,7 @@ describe "JSON Gem Compat API" do
11
11
 
12
12
  # Magic to make the before loading tests actually run before loading
13
13
  RSpec.configure do |config|
14
- config.order_groups_and_examples do |list|
14
+ config.register_ordering(:global) do |list|
15
15
  list.sort_by { |item| item.description }
16
16
  end
17
17
  end
@@ -19,39 +19,39 @@ describe "JSON Gem Compat API" do
19
19
  context "A: before loading the compat library" do
20
20
  it "should not mixin #to_json on random objects" do
21
21
  d = Dummy.new
22
- expect(d.respond_to?(:to_json)).to be_false
22
+ expect(d.respond_to?(:to_json)).to be false
23
23
  end
24
24
 
25
25
  it "should not mixin #to_json to a string" do
26
- expect("".respond_to?(:to_json)).to be_false
26
+ expect("".respond_to?(:to_json)).to be false
27
27
  end
28
28
 
29
29
  it "should not mixin #to_json to a fixnum" do
30
- expect(1.respond_to?(:to_json)).to be_false
30
+ expect(1.respond_to?(:to_json)).to be false
31
31
  end
32
32
 
33
33
  it "should not mixin #to_json on a float" do
34
- expect("1.5".to_f.respond_to?(:to_json)).to be_false
34
+ expect("1.5".to_f.respond_to?(:to_json)).to be false
35
35
  end
36
36
 
37
37
  it "should not mixin #to_json on an array" do
38
- expect([].respond_to?(:to_json)).to be_false
38
+ expect([].respond_to?(:to_json)).to be false
39
39
  end
40
40
 
41
41
  it "should not mixin #to_json on a hash" do
42
- expect({ :foo => "bar" }.respond_to?(:to_json)).to be_false
42
+ expect({ :foo => "bar" }.respond_to?(:to_json)).to be false
43
43
  end
44
44
 
45
45
  it "should not mixin #to_json on a trueclass" do
46
- expect(true.respond_to?(:to_json)).to be_false
46
+ expect(true.respond_to?(:to_json)).to be false
47
47
  end
48
48
 
49
49
  it "should not mixin #to_json on a falseclass" do
50
- expect(false.respond_to?(:to_json)).to be_false
50
+ expect(false.respond_to?(:to_json)).to be false
51
51
  end
52
52
 
53
53
  it "should not mixin #to_json on a nilclass" do
54
- expect(nil.respond_to?(:to_json)).to be_false
54
+ expect(nil.respond_to?(:to_json)).to be false
55
55
  end
56
56
  end
57
57
 
@@ -62,66 +62,60 @@ describe "JSON Gem Compat API" do
62
62
  end
63
63
 
64
64
  it "should define JSON class" do
65
- expect(defined?(JSON)).to be_true
65
+ expect(defined?(JSON)).to be_truthy
66
66
  end
67
67
 
68
68
  it "should implement JSON#parse" do
69
- expect(JSON.respond_to?(:parse)).to be_true
69
+ expect(JSON.respond_to?(:parse)).to be true
70
70
  end
71
71
 
72
72
  it "should implement JSON#generate" do
73
- expect(JSON.respond_to?(:generate)).to be_true
73
+ expect(JSON.respond_to?(:generate)).to be true
74
74
  end
75
75
 
76
76
  it "should implement JSON#pretty_generate" do
77
- expect(JSON.respond_to?(:pretty_generate)).to be_true
77
+ expect(JSON.respond_to?(:pretty_generate)).to be true
78
78
  end
79
79
 
80
80
  it "should implement JSON#load" do
81
- expect(JSON.respond_to?(:load)).to be_true
81
+ expect(JSON.respond_to?(:load)).to be true
82
82
  end
83
83
 
84
84
  it "should implement JSON#dump" do
85
- expect(JSON.respond_to?(:dump)).to be_true
85
+ expect(JSON.respond_to?(:dump)).to be true
86
86
  end
87
87
 
88
88
  context "when setting symbolize_keys via JSON.default_options" do
89
- before(:all) { @saved_default = JSON.default_options[:symbolize_keys] }
90
- after(:all) { JSON.default_options[:symbolize_keys] = @saved_default }
89
+ after(:each) { JSON.default_options[:symbolize_keys] = false }
91
90
 
92
91
  it "the default behavior should be to not symbolize keys" do
93
92
  expect(JSON.parse('{"foo": 1234}')).to eq( "foo" => 1234 )
94
93
  end
95
94
 
96
95
  it "changing the default_options should change the behavior to true" do
97
- skip("implement symbolize keys")
98
96
  JSON.default_options[:symbolize_keys] = true
99
97
  expect(JSON.parse('{"foo": 1234}')).to eq( :foo => 1234 )
100
98
  end
101
99
  end
102
100
 
103
101
  context "when setting symbolize_names via JSON.default_options" do
104
- before(:all) { @saved_default = JSON.default_options[:symbolize_names] }
105
- after(:all) { JSON.default_options[:symbolize_names] = @saved_default }
102
+ after { JSON.default_options.delete(:symbolize_names)}
106
103
 
107
104
  it "the default behavior should be to not symbolize keys" do
108
105
  expect(JSON.parse('{"foo": 1234}')).to eq( "foo" => 1234 )
109
106
  end
110
107
 
111
108
  it "changing the default_options should change the behavior to true" do
112
- skip("implement symbolize keys")
113
109
  JSON.default_options[:symbolize_names] = true
114
110
  expect(JSON.parse('{"foo": 1234}')).to eq( :foo => 1234 )
115
111
  end
116
112
  end
117
113
 
118
114
  it "should support passing symbolize_names to JSON.parse" do
119
- skip("implement symbolize keys")
120
115
  expect(JSON.parse('{"foo": 1234}', :symbolize_names => true)).to eq( :foo => 1234 )
121
116
  end
122
117
 
123
118
  it "should support passing symbolize_keys to JSON.parse" do
124
- skip("implement symbolize keys")
125
119
  expect(JSON.parse('{"foo": 1234}', :symbolize_keys => true)).to eq( :foo => 1234 )
126
120
  end
127
121
 
@@ -149,13 +143,13 @@ describe "JSON Gem Compat API" do
149
143
 
150
144
  context "JSON exception classes" do
151
145
  it "should define JSON::JSONError as a StandardError" do
152
- expect(JSON::JSONError.new.is_a?(StandardError)).to be_true
146
+ expect(JSON::JSONError.new.is_a?(StandardError)).to be true
153
147
  end
154
148
  it "should define JSON::ParserError as a JSON::JSONError" do
155
- expect(JSON::ParserError.new.is_a?(JSON::JSONError)).to be_true
149
+ expect(JSON::ParserError.new.is_a?(JSON::JSONError)).to be true
156
150
  end
157
151
  it "should define JSON::GeneratorError as a JSON::JSONError" do
158
- expect(JSON::GeneratorError.new.is_a?(JSON::JSONError)).to be_true
152
+ expect(JSON::GeneratorError.new.is_a?(JSON::JSONError)).to be true
159
153
  end
160
154
  it "should raise JSON::ParserError on a bad parse" do
161
155
  expect{ JSON.parse("blah") }.to raise_error(JSON::ParserError)
@@ -4,79 +4,473 @@ require 'spec_helper'
4
4
 
5
5
  describe "FFI_Yajl::Parser" do
6
6
 
7
- let(:parser) { FFI_Yajl::Parser.new }
7
+ shared_examples_for "correct json parsing" do
8
+ context "when json has 23456789012E666" do
9
+ let(:json) { '{"key": 23456789012E666}' }
8
10
 
9
- it "returns nil for an empty string (compatibility with yajl-ruby)" do
10
- json = ''
11
- expect( parser.parse(json) ).to be_nil
12
- end
11
+ it "should return infinity" do
12
+ infinity = (1.0/0)
13
+ expect(parser).to eq({"key" => infinity})
14
+ end
15
+ end
13
16
 
14
- it "throws an exception when trailing braces are missing" do
15
- json = '{{"foo": 1234}'
16
- expect { parser.parse(json) }.to raise_error(FFI_Yajl::ParseError)
17
- end
17
+ context "when json has comments" do
18
+ let(:json) { '{"key": /* this is a comment */ "value"}' }
18
19
 
19
- it "throws an exception when trailing brackets are missing" do
20
- json = '[["foo", "bar"]'
21
- expect { parser.parse(json) }.to raise_error(FFI_Yajl::ParseError)
22
- end
20
+ context "when allow_comments is false" do
21
+ let(:options) { { :allow_comments => false } }
23
22
 
24
- it "throws an exception when it has an extra brace" do
25
- json = '{{"foo": 1234}}}'
26
- expect { parser.parse(json) }.to raise_error(FFI_Yajl::ParseError)
27
- end
23
+ it "should not parse" do
24
+ expect{parser}.to raise_error(FFI_Yajl::ParseError)
25
+ end
26
+ end
28
27
 
29
- it "throws an exception when it has an extra bracket" do
30
- json = '[["foo", "bar"]]]'
31
- expect { parser.parse(json) }.to raise_error(FFI_Yajl::ParseError)
32
- end
28
+ context "when allow_comments is true" do
29
+ let(:options) { { :allow_comments => true } }
30
+
31
+ it "should parse" do
32
+ expect(parser).to eq({"key"=>"value"})
33
+ end
34
+ end
35
+ end
36
+
37
+ context "when json has multiline comments" do
38
+ let(:json) { %Q{{"key": \n/*\n this is a multiline comment \n*/\n "value"}} }
39
+
40
+ context "when allow_comments is false" do
41
+ let(:options) { { :allow_comments => false } }
42
+
43
+ it "should not parse" do
44
+ expect{parser}.to raise_error(FFI_Yajl::ParseError)
45
+ end
46
+ end
47
+
48
+ context "when allow_comments is true" do
49
+ let(:options) { { :allow_comments => true } }
50
+
51
+ it "should parse" do
52
+ expect(parser).to eq({"key"=>"value"})
53
+ end
54
+ end
55
+ end
56
+
57
+ context "when json has inline comments" do
58
+ let(:json) { %Q{{"key": \n// this is an inline comment\n "value"}} }
59
+
60
+ context "when allow_comments is false" do
61
+ let(:options) { { :allow_comments => false } }
62
+
63
+ it "should not parse" do
64
+ expect{parser}.to raise_error(FFI_Yajl::ParseError)
65
+ end
66
+ end
67
+
68
+ context "when allow_comments is true" do
69
+ let(:options) { { :allow_comments => true } }
70
+
71
+ it "should parse" do
72
+ expect(parser).to eq({"key"=>"value"})
73
+ end
74
+ end
75
+ end
76
+
77
+ context "when json is invalid UTF8" do
78
+ let(:json) { "[\"#{"\201\203"}\"]" }
79
+
80
+ it "should not parse by default" do
81
+ expect{parser}.to raise_error(FFI_Yajl::ParseError)
82
+ end
83
+
84
+ context "when :dont_validate_strings is set to true" do
85
+ let(:options) { { :dont_validate_strings => true } }
86
+
87
+ it "should parse" do
88
+ expect(parser).to eq(["\x81\x83"])
89
+ end
90
+ end
91
+
92
+ context "when :dont_validate_strings is set to false" do
93
+ let(:options) { { :dont_validate_strings => false } }
94
+
95
+ it "should not parse" do
96
+ expect{parser}.to raise_error(FFI_Yajl::ParseError)
97
+ end
98
+ end
99
+
100
+ context "when :check_utf8 is set to true" do
101
+ let(:options) { { :check_utf8 => true } }
102
+
103
+ it "should not parse" do
104
+ expect{parser}.to raise_error(FFI_Yajl::ParseError)
105
+ end
106
+
107
+ context "when :dont_validate_strings is set to true" do
108
+ let(:options) { { :check_utf8 => true, :dont_validate_strings => true } }
109
+
110
+ it "should raise an ArgumentError" do
111
+ expect{parser}.to raise_error(ArgumentError)
112
+ end
113
+ end
114
+
115
+ context "when :dont_validate_strings is set to false" do
116
+ let(:options) { { :check_utf8 => true, :dont_validate_strings => false } }
117
+
118
+ it "should not parse" do
119
+ expect{parser}.to raise_error(FFI_Yajl::ParseError)
120
+ end
121
+ end
122
+ end
123
+
124
+ context "when :check_utf8 is set to false" do
125
+ let(:options) { { :check_utf8 => false } }
126
+
127
+ it "should parse" do
128
+ expect(parser).to eq(["\x81\x83"])
129
+ end
130
+
131
+ context "when :dont_validate_strings is set to true" do
132
+ let(:options) { { :check_utf8 => false, :dont_validate_strings => true } }
133
+
134
+ it "should parse" do
135
+ expect(parser).to eq(["\x81\x83"])
136
+ end
137
+ end
138
+
139
+ context "when :dont_validate_strings is set to false" do
140
+ let(:options) { { :check_utf8 => false, :dont_validate_strings => false } }
141
+
142
+ it "should raise an ArgumentError" do
143
+ expect{parser}.to raise_error(ArgumentError)
144
+ end
145
+ end
146
+ end
147
+ end
148
+
149
+ context "when JSON is a StringIO" do
150
+ let(:json) { StringIO.new('{"key": 1234}') }
151
+
152
+ it "should parse" do
153
+ expect(parser).to eq({"key" => 1234})
154
+ end
155
+ end
156
+
157
+ context "when parsing a JSON string" do
158
+ let(:json) { '{"key": 1234}' }
159
+
160
+ it "should parse correctly" do
161
+ expect(parser).to eq({"key" => 1234})
162
+ end
163
+
164
+ context "when symbolize_keys is true" do
165
+ let(:options) { { :symbolize_keys => true } }
166
+
167
+ it "should symbolize keys correctly" do
168
+ expect(parser).to eq({:key => 1234})
169
+ end
170
+ end
171
+
172
+ context "when passing a block" do
173
+ it "should parse correctly" do
174
+ skip "handle blocks"
175
+ output = nil
176
+ parser do |obj|
177
+ output = obj
178
+ end
179
+ expect(output).to eq({"key" => 1234})
180
+ end
181
+ end
182
+ end
183
+
184
+ context "when parsing a JSON hash with only strings" do
185
+ let(:json) { '{"key": "value"}' }
186
+
187
+ if RUBY_VERSION.to_f >= 1.9
188
+ context "when Encoding.default_internal is nil" do
189
+ before do
190
+ @saved_encoding = Encoding.default_internal
191
+ Encoding.default_internal = nil
192
+ end
193
+ after do
194
+ Encoding.default_internal = @saved_encoding
195
+ end
196
+ it "encodes keys to UTF-8" do
197
+ expect(parser.keys.first.encoding).to eql(Encoding.find('utf-8'))
198
+ end
199
+ it "encodes values to UTF-8" do
200
+ expect(parser.values.first.encoding).to eql(Encoding.find('utf-8'))
201
+ end
202
+ end
203
+
204
+ %w{utf-8 us-ascii}.each do |encoding|
205
+ context "when Encoding.default_internal is #{encoding}" do
206
+ before do
207
+ @saved_encoding = Encoding.default_internal
208
+ Encoding.default_internal = nil
209
+ end
210
+ after do
211
+ Encoding.default_internal = @saved_encoding
212
+ end
213
+ it "encodes keys to #{encoding}" do
214
+ skip "fix us-ascii" if encoding == "us-ascii"
215
+ expect(parser.keys.first.encoding).to eql(Encoding.find(encoding))
216
+ end
217
+ it "encodes values to #{encoding}" do
218
+ skip "fix us-ascii" if encoding == "us-ascii"
219
+ expect(parser.values.first.encoding).to eql(Encoding.find(encoding))
220
+ end
221
+ end
222
+ end
223
+ end
224
+ end
225
+
226
+ context "when a parsed key has utf-8 multibyte characters" do
227
+ let(:json) { '{"日本語": 1234}' }
228
+
229
+ it "should parse correctly" do
230
+ expect(parser).to eq({"日本語" => 1234})
231
+ end
232
+
233
+ context "when symbolize_keys is true" do
234
+ let(:options) { { :symbolize_keys => true } }
235
+
236
+ it "should symbolize keys correctly" do
237
+ expect(parser).to eq({:"日本語" => 1234})
238
+ end
33
239
 
34
- context "when parsing floats" do
35
- it "parses simple floating point values" do
36
- json = '{"foo": 3.14159265358979}'
37
- expect(parser.parse(json)).to eql( "foo" => 3.14159265358979 )
240
+ if RUBY_VERSION.to_f >= 1.9
241
+ it "should parse non-ascii symbols in UTF-8" do
242
+ expect(parser.keys.fetch(0).encoding).to eq(Encoding::UTF_8)
243
+ end
244
+ end
245
+ end
38
246
  end
39
247
 
40
- it "parses simple negative floating point values" do
41
- json = '{"foo":-2.00231930436153}'
42
- expect(parser.parse(json)).to eql( "foo" => -2.00231930436153 )
248
+ context "when parsing 2147483649" do
249
+ let(:json) { "{\"id\": 2147483649}" }
250
+
251
+ it "should parse corectly" do
252
+ expect(parser).to eql({"id" => 2147483649})
253
+ end
254
+ end
255
+
256
+ context "when parsing 5687389800" do
257
+ let(:json) { "{\"id\": 5687389800}" }
258
+
259
+ it "should parse corectly" do
260
+ expect(parser).to eql({"id" => 5687389800})
261
+ end
262
+ end
263
+
264
+ context "when parsing 1046289770033519442869495707521600000000" do
265
+ let(:json) { "{\"id\": 1046289770033519442869495707521600000000}" }
266
+
267
+ it "should parse corectly" do
268
+ expect(parser).to eql({"id" => 1046289770033519442869495707521600000000})
269
+ end
270
+ end
271
+
272
+ # NOTE: we are choosing to be compatible with yajl-ruby here vs. JSON
273
+ # gem and libyajl C behavior (which is to throw an exception in this case)
274
+ context "when the JSON is empty string" do
275
+ let(:json) { '' }
276
+
277
+ it "returns nil" do
278
+ expect(parser).to be_nil
279
+ end
280
+ end
281
+
282
+ # NOTE: this fixes yajl-ruby being too permissive
283
+ context "when dealing with too much or too little input" do
284
+ context "when trailing braces are missing" do
285
+ let(:json) { '{"foo":{"foo": 1234}' }
286
+
287
+ it "raises an exception" do
288
+ expect { parser }.to raise_error(FFI_Yajl::ParseError)
289
+ end
290
+ end
291
+
292
+ context "when trailing brackets are missing" do
293
+ let(:json) { '[["foo", "bar"]' }
294
+
295
+ it "raises an exception" do
296
+ expect { parser }.to raise_error(FFI_Yajl::ParseError)
297
+ end
298
+ end
299
+
300
+ context "when an extra brace is present" do
301
+ let(:json) { '{"foo":{"foo": 1234}}}' }
302
+
303
+ it "raises an exception" do
304
+ expect { parser }.to raise_error(FFI_Yajl::ParseError)
305
+ end
306
+
307
+ context "with allow_trailing_garbage" do
308
+ let(:options) { { :allow_trailing_garbage => true } }
309
+ it "parses" do
310
+ expect(parser).to eq({"foo"=>{"foo"=>1234}})
311
+ end
312
+ end
313
+
314
+ end
315
+
316
+ context "when an extra bracket is present" do
317
+ let(:json) { '[["foo", "bar"]]]' }
318
+
319
+ it "raises an exception" do
320
+ expect { parser }.to raise_error(FFI_Yajl::ParseError)
321
+ end
322
+ end
323
+ end
324
+
325
+ context "when parsing heavy metal umlauts in keys" do
326
+ let(:json) { '{"München": "Bayern"}' }
327
+
328
+ it "correctly parses" do
329
+ expect(parser).to eql( "München" => "Bayern" )
330
+ end
331
+ end
332
+
333
+ context "when parsing floats" do
334
+ context "parses simple floating point values" do
335
+ let(:json) { '{"foo": 3.14159265358979}' }
336
+
337
+ it "correctly parses" do
338
+ expect(parser).to eql( "foo" => 3.14159265358979 )
339
+ end
340
+ end
341
+
342
+ context "parses simple negative floating point values" do
343
+ let(:json) { '{"foo":-2.00231930436153}' }
344
+
345
+ it "correctly parses" do
346
+ expect(parser).to eql( "foo" => -2.00231930436153 )
347
+ end
348
+ end
349
+
350
+ context "parses floats with negative exponents and a large E" do
351
+ let(:json) { '{"foo": 1.602176565E-19}' }
352
+
353
+ it "correctly parses" do
354
+ expect(parser).to eql( "foo" => 1.602176565e-19 )
355
+ end
356
+ end
357
+
358
+ context "parses floats with negative exponents and a small e" do
359
+ let(:json) { '{"foo": 6.6260689633e-34 }' }
360
+
361
+ it "correctly parses" do
362
+ expect(parser).to eql( "foo" => 6.6260689633e-34 )
363
+ end
364
+ end
365
+
366
+ context "parses floats with positive exponents and a large E" do
367
+ let(:json) { '{"foo": 6.0221413E+23}' }
368
+
369
+ it "correctly parses" do
370
+ expect(parser).to eql( "foo" => 6.0221413e+23 )
371
+ end
372
+ end
373
+
374
+ context "parses floats with positive exponents and a small e" do
375
+ let(:json) { '{"foo": 8.9875517873681764e+9 }' }
376
+
377
+ it "correctly parses" do
378
+ expect(parser).to eql( "foo" => 8.9875517873681764e+9 )
379
+ end
380
+ end
381
+
382
+ context "parses floats with an exponent without a sign and a large E" do
383
+ let(:json) { '{"foo": 2.99792458E8 }' }
384
+
385
+ it "correctly parses" do
386
+ expect(parser).to eql( "foo" => 2.99792458e+8 )
387
+ end
388
+ end
389
+
390
+ context "parses floats with an exponent without a sign and a small e" do
391
+ let(:json) { '{"foo": 1.0973731568539e7 }' }
392
+
393
+ it "correctly parses" do
394
+ expect(parser).to eql( "foo" => 1.0973731568539e+7 )
395
+ end
396
+ end
43
397
  end
44
398
 
45
- it "parses floats with negative exponents and a large E" do
46
- json = '{"foo": 1.602176565E-19}'
47
- expect(parser.parse(json)).to eql( "foo" => 1.602176565e-19 )
399
+ context "when parsing big floats", :ruby_gte_193 => true do
400
+ let(:json) { '[0.' + '1' * 2**23 + ']' }
401
+
402
+ it "parses" do
403
+ expect{ parser }.not_to raise_error
404
+ end
48
405
  end
49
406
 
50
- it "parses floats with negative exponents and a small e" do
51
- json = '{"foo": 6.6260689633e-34 }'
52
- expect(parser.parse(json)).to eql( "foo" => 6.6260689633e-34 )
407
+ context "when parsing long hash keys with symbolize_keys option", :ruby_gte_193 => true do
408
+ let(:json) { '{"' + 'a' * 2**23 + '": 0}' }
409
+ let(:options) { { :symbolize_keys => true } }
410
+
411
+ it "parses" do
412
+ expect{ parser }.not_to raise_error
413
+ end
53
414
  end
415
+ end
416
+
417
+ context "when options are set to empty hash" do
418
+ let(:options) { {} }
419
+
420
+ context "when using a parsing object" do
421
+ let(:parser) { FFI_Yajl::Parser.new(options).parse(json) }
54
422
 
55
- it "parses floats with positive exponents and a large E" do
56
- json = '{"foo": 6.0221413E+23}'
57
- expect(parser.parse(json)).to eql( "foo" => 6.0221413e+23 )
423
+ it_behaves_like "correct json parsing"
58
424
  end
59
425
 
60
- it "parses floats with positive exponents and a small e" do
61
- json = '{"foo": 8.9875517873681764e+9 }'
62
- expect(parser.parse(json)).to eql( "foo" => 8.9875517873681764e+9 )
426
+ context "when using the class method" do
427
+ let(:parser) { FFI_Yajl::Parser.parse(json, options) }
428
+
429
+ it_behaves_like "correct json parsing"
63
430
  end
431
+ end
432
+
433
+ context "when options are set to nil" do
434
+ let(:options) { nil }
64
435
 
65
- it "parses floats with an exponent without a sign and a large E" do
66
- json = '{"foo": 2.99792458E8 }'
67
- expect(parser.parse(json)).to eql( "foo" => 2.99792458e+8 )
436
+ context "when using a parsing object" do
437
+ let(:parser) { FFI_Yajl::Parser.new(options).parse(json) }
438
+
439
+ it_behaves_like "correct json parsing"
68
440
  end
69
441
 
70
- it "parses floats with an exponent without a sign and a small e" do
71
- json = '{"foo": 1.0973731568539e7 }'
72
- expect(parser.parse(json)).to eql( "foo" => 1.0973731568539e+7 )
442
+ context "when using the class method" do
443
+ let(:parser) { FFI_Yajl::Parser.parse(json, options) }
444
+
445
+ it_behaves_like "correct json parsing"
73
446
  end
74
447
  end
75
448
 
76
- context "when parsing unicode in hash keys" do
77
- it "handles heavy metal umlauts in keys" do
78
- json = '{"München": "Bayern"}'
79
- expect(parser.parse(json)).to eql( "München" => "Bayern" )
449
+ context "when options default to nothing" do
450
+ let(:options) { nil }
451
+
452
+ context "when using a parsing object" do
453
+ let(:parser) do
454
+ if options.nil?
455
+ FFI_Yajl::Parser.new.parse(json)
456
+ else
457
+ FFI_Yajl::Parser.new(options).parse(json)
458
+ end
459
+ end
460
+
461
+ it_behaves_like "correct json parsing"
462
+ end
463
+
464
+ context "when using the class method" do
465
+ let(:parser) do
466
+ if options.nil?
467
+ FFI_Yajl::Parser.parse(json)
468
+ else
469
+ FFI_Yajl::Parser.parse(json, options)
470
+ end
471
+ end
472
+
473
+ it_behaves_like "correct json parsing"
80
474
  end
81
475
  end
82
476
  end
data/spec/spec_helper.rb CHANGED
@@ -4,6 +4,7 @@ require 'ffi_yajl'
4
4
 
5
5
  RSpec.configure do |c|
6
6
  c.filter_run_excluding :ruby_gte_19 => true unless RUBY_VERSION.to_f >= 1.9
7
+ c.filter_run_excluding :ruby_gte_193 => true unless RUBY_VERSION.to_f >= 2.0 || RUBY_VERSION =~ /^1\.9\.3/
7
8
 
8
9
  c.order = 'random'
9
10
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ffi-yajl
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.7
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lamont Granquist
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-06-14 00:00:00.000000000 Z
11
+ date: 2014-06-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake