sparsam 0.1.4 → 0.1.5

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3ab4bfb28dc0761f1ef66ea93e239408e4bdb308
4
- data.tar.gz: 78cd96b406e6260832478236d53e7c20a166d628
3
+ metadata.gz: 82b851a7cec00fa3805571f1cd89d43077b86ed5
4
+ data.tar.gz: c4617c449ccd26bb73896e49ceb430b15f6f973c
5
5
  SHA512:
6
- metadata.gz: 9c5cfdaed8ebc55ded0e937139d21d3133459be09b3787c6fc0625fdf09be2452d615969dbcd9aa7f7eb9510ec2bd1dd65c2314b9d979cabb043349f113cbc94
7
- data.tar.gz: 4a72dbf9b165aff8c419beaa7f870ed3cdb00e1d858201325707cdd5248d7e6ce6126896e9e4ae1e381c5e055003f4c8d348f95297e8143c270e86fe2bd198eb
6
+ metadata.gz: 73aff3bcae386d83adb457c63ef35ec0d0281e2bab33436b4f05c7fa8b79f80e5110d7600cfa3427cfbd5eeb222f01cf997ce8f312cd3be72dbb6f7d6fbebdc1
7
+ data.tar.gz: 6f060447c4d06c8abd9f014dcdbafe5e3e1e8ffc8ce0a23c999c7c445138ac019a0257eccf48bdfc5af4cf722ccd64e44155574190c65f67dadc026b1f223317
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # Sparsam [![Build Status](https://travis-ci.org/airbnb/sparsam.svg?branch=master)](https://travis-ci.org/airbnb/sparsam) [![Coverage Status](https://coveralls.io/repos/github/airbnb/sparsam/badge.svg?branch=master)](https://coveralls.io/github/airbnb/sparsam?branch=master)
1
+ # Sparsam [![Build Status](https://travis-ci.org/airbnb/sparsam.svg?branch=master)](https://travis-ci.org/airbnb/sparsam) [![Coverage Status](https://coveralls.io/repos/github/airbnb/sparsam/badge.svg?branch=master)](https://coveralls.io/github/airbnb/sparsam?branch=master) [![Gem Version](https://badge.fury.io/rb/sparsam.svg)](https://badge.fury.io/rb/sparsam)
2
2
  New Thrift bindings and generator for Ruby!
3
3
 
4
4
  ## Super basic Example
data/ext/serializer.cpp CHANGED
@@ -136,6 +136,18 @@ static inline VALUE make_ruby_binary(const string &val) {
136
136
  return rb_str_new(val.c_str(), val.size());
137
137
  }
138
138
 
139
+ static void raise_exc_with_struct_and_field_names(
140
+ VALUE exc_class,
141
+ VALUE msg,
142
+ VALUE outer_struct_class,
143
+ VALUE field_sym) {
144
+ VALUE struct_name = rb_class_name(outer_struct_class);
145
+ VALUE field_name = rb_sym_to_s(field_sym);
146
+ VALUE args[3] = {msg, struct_name, field_name};
147
+ VALUE e = rb_class_new_instance(3, args, exc_class);
148
+ rb_exc_raise(e);
149
+ }
150
+
139
151
  static inline VALUE make_ruby_bool(bool val) { return val ? Qtrue : Qfalse; }
140
152
 
141
153
  void ThriftSerializer::skip_n_type(uint32_t n, TType ttype) {
@@ -301,8 +313,11 @@ VALUE ThriftSerializer::readStruct(VALUE klass) {
301
313
  typeId = fieldInfo->ftype;
302
314
 
303
315
  if (typeId != fieldBegin.ftype) {
304
- rb_raise(SparsamTypeMismatchError, "Type Mismatch. Defenition: %d, Actual: %d",
305
- fieldBegin.ftype, typeId);
316
+ raise_exc_with_struct_and_field_names(
317
+ SparsamTypeMismatchError,
318
+ rb_sprintf("Type Mismatch. Defenition: %d, Actual: %d", fieldBegin.ftype, typeId),
319
+ klass,
320
+ fieldInfo->symName);
306
321
  }
307
322
 
308
323
  VALUE rb_value = this->readAny(fieldBegin.ftype, iter->second);
@@ -387,6 +402,8 @@ void ThriftSerializer::writeAny(TType ttype, FieldInfo *field_info,
387
402
  HANDLE_TYPE(BYTE, Byte, NUM2SHORT)
388
403
 
389
404
  case protocol::T_STRING: {
405
+ Check_Type(actual, T_STRING);
406
+
390
407
  string data = string(StringValuePtr(actual), RSTRING_LEN(actual));
391
408
  if (field_info->isBinaryString) {
392
409
  this->tprot->writeBinary(data);
@@ -397,6 +414,8 @@ void ThriftSerializer::writeAny(TType ttype, FieldInfo *field_info,
397
414
  }
398
415
 
399
416
  case protocol::T_LIST: {
417
+ Check_Type(actual, T_ARRAY);
418
+
400
419
  long length = RARRAY_LEN(actual);
401
420
  TType elem = field_info->elementType->ftype;
402
421
  this->tprot->writeListBegin(elem, static_cast<size_t>(length));
@@ -420,6 +439,8 @@ void ThriftSerializer::writeAny(TType ttype, FieldInfo *field_info,
420
439
  }
421
440
 
422
441
  case protocol::T_MAP: {
442
+ Check_Type(actual, T_HASH);
443
+
423
444
  TType keyTType = field_info->keyType->ftype,
424
445
  valueTType = field_info->elementType->ftype;
425
446
  this->tprot->writeMapBegin(keyTType, valueTType,
@@ -454,6 +475,11 @@ void ThriftSerializer::writeStruct(VALUE klass, VALUE data) {
454
475
  FieldBegin fieldBegin;
455
476
  FieldInfo *fieldInfo;
456
477
  auto fields = FindOrCreateFieldInfoMap(klass);
478
+
479
+ if (!validateStruct(klass, data, false, false)) {
480
+ return;
481
+ }
482
+
457
483
  for (auto const & entry : *fields) {
458
484
  fieldBegin.fid = entry.first;
459
485
  fieldInfo = entry.second;
@@ -490,15 +516,23 @@ VALUE serializer_readStruct(VALUE self, VALUE klass) {
490
516
  catch_thrift_and_reraise();
491
517
  }
492
518
 
493
- static inline void raise_type_mismatch() {
494
- rb_raise(SparsamTypeMismatchError, "Type mismatch in field data");
519
+ static void raise_type_mismatch(VALUE outer_struct, VALUE field_sym) {
520
+ raise_exc_with_struct_and_field_names(
521
+ SparsamTypeMismatchError,
522
+ rb_str_new2("Type mismatch in field data"),
523
+ CLASS_OF(outer_struct),
524
+ field_sym);
495
525
  }
496
526
 
497
- bool validateArray(FieldInfo *type, VALUE arr, bool recursive) {
527
+ bool validateArray(
528
+ FieldInfo *type,
529
+ VALUE arr,
530
+ bool recursive,
531
+ VALUE outer_struct,
532
+ VALUE field_sym) {
498
533
  long length = RARRAY_LEN(arr);
499
534
  for (long i = 0; i < length; i++) {
500
- if (!validateAny(type, rb_ary_entry(arr, i), recursive)) {
501
- rb_raise(SparsamTypeMismatchError, "Type mismatch in container element");
535
+ if (!validateAny(type, rb_ary_entry(arr, i), recursive, outer_struct, field_sym)) {
502
536
  return false;
503
537
  }
504
538
  }
@@ -507,7 +541,7 @@ bool validateArray(FieldInfo *type, VALUE arr, bool recursive) {
507
541
 
508
542
  #define TEST_RB_VAL_FOR_CLASS(VAL, KLASS) \
509
543
  if (!RTEST(rb_obj_is_kind_of(VAL, KLASS))) { \
510
- raise_type_mismatch(); \
544
+ raise_type_mismatch(outer_struct, field_sym); \
511
545
  ret = false; \
512
546
  }
513
547
 
@@ -517,7 +551,7 @@ bool validateArray(FieldInfo *type, VALUE arr, bool recursive) {
517
551
  break; \
518
552
  }
519
553
 
520
- bool validateAny(FieldInfo *type, VALUE val, bool recursive) {
554
+ bool validateAny(FieldInfo *type, VALUE val, bool recursive, VALUE outer_struct, VALUE field_sym) {
521
555
  bool ret = true;
522
556
  switch (type->ftype) {
523
557
 
@@ -528,6 +562,14 @@ bool validateAny(FieldInfo *type, VALUE val, bool recursive) {
528
562
  HANDLE_TYPE(DOUBLE, klass_for_float)
529
563
  HANDLE_TYPE(STRING, klass_for_string)
530
564
 
565
+ case protocol::T_BOOL: {
566
+ if ( !(val == Qtrue || val == Qfalse) ) {
567
+ raise_type_mismatch(outer_struct, field_sym);
568
+ ret = false;
569
+ }
570
+ break;
571
+ }
572
+
531
573
  case protocol::T_STRUCT: {
532
574
  TEST_RB_VAL_FOR_CLASS(val, type->klass)
533
575
  if (ret && recursive) {
@@ -540,14 +582,14 @@ bool validateAny(FieldInfo *type, VALUE val, bool recursive) {
540
582
  TEST_RB_VAL_FOR_CLASS(val, klass_for_set)
541
583
  if (ret) {
542
584
  VALUE ary = rb_funcall(val, intern_for_to_a, 0);
543
- ret = validateArray(type->elementType, ary, recursive);
585
+ ret = validateArray(type->elementType, ary, recursive, outer_struct, field_sym);
544
586
  }
545
587
  break;
546
588
  }
547
589
  case protocol::T_LIST: {
548
590
  TEST_RB_VAL_FOR_CLASS(val, klass_for_array)
549
591
  if (ret) {
550
- ret = validateArray(type->elementType, val, recursive);
592
+ ret = validateArray(type->elementType, val, recursive, outer_struct, field_sym);
551
593
  }
552
594
  break;
553
595
  }
@@ -557,12 +599,13 @@ bool validateAny(FieldInfo *type, VALUE val, bool recursive) {
557
599
  if (ret) {
558
600
  bool flag = true;
559
601
  HASH_FOREACH_BEGIN(val, &flag, type->keyType, type->elementType,
560
- &recursive)
602
+ &recursive, &outer_struct, &field_sym)
561
603
  bool *flag = (bool *)argv[0], *recursive = (bool *)argv[3];
604
+ VALUE *outer_struct = (VALUE *)argv[4], *field_sym = (VALUE *)argv[5];
562
605
  FieldInfo *field_info_key = (FieldInfo *)argv[1];
563
606
  FieldInfo *field_info_value = (FieldInfo *)argv[2];
564
- if (!validateAny(field_info_key, k, *recursive) ||
565
- !validateAny(field_info_value, v, *recursive)) {
607
+ if (!validateAny(field_info_key, k, *recursive, *outer_struct, *field_sym) ||
608
+ !validateAny(field_info_value, v, *recursive, *outer_struct, *field_sym)) {
566
609
  *flag = false;
567
610
  HASH_FOREACH_ABORT()
568
611
  }
@@ -594,12 +637,17 @@ bool validateStruct(VALUE klass, VALUE data, bool validateContainerTypes,
594
637
  VALUE val = rb_ivar_get(data, entry.second->ivarName);
595
638
  if (NIL_P(val)) {
596
639
  if (!entry.second->isOptional) {
597
- rb_raise(SparsamMissingMandatory, "Missing: fieldID %d", entry.first);
640
+ raise_exc_with_struct_and_field_names(
641
+ SparsamMissingMandatory,
642
+ rb_sprintf("Missing: fieldID %d", entry.first),
643
+ klass,
644
+ entry.second->symName);
598
645
  return false;
599
646
  }
600
647
  continue;
601
648
  }
602
- if (validateContainerTypes && !validateAny(entry.second, val, recursive)) {
649
+ if (validateContainerTypes &&
650
+ !validateAny(entry.second, val, recursive, data, entry.second->symName)) {
603
651
  return false;
604
652
  }
605
653
  }
data/ext/serializer.h CHANGED
@@ -92,7 +92,7 @@ private:
92
92
 
93
93
  bool validateStruct(VALUE klass, VALUE data, bool validateContainerTypes,
94
94
  bool recursive);
95
- bool validateAny(FieldInfo *type, VALUE val, bool recursive);
95
+ bool validateAny(FieldInfo *type, VALUE val, bool recursive, VALUE outer_struct, VALUE field_sym);
96
96
  FieldInfoMap *FindOrCreateFieldInfoMap(VALUE klass);
97
97
  FieldInfo *CreateFieldInfo(VALUE field_map_entry);
98
98
  FieldInfoMap *CreateFieldInfoMap(VALUE klass);
@@ -73,7 +73,6 @@ module Sparsam
73
73
  end
74
74
 
75
75
  def serialize(prot = Sparsam::CompactProtocol)
76
- validate
77
76
  s = Sparsam::Serializer.new(prot, "")
78
77
  s.serialize(self.class, self)
79
78
  end
@@ -8,14 +8,22 @@ module Sparsam
8
8
  end
9
9
 
10
10
  class MissingMandatory < Exception
11
- def initialize(msg)
12
- super
11
+ attr_reader :struct_name, :field_name
12
+
13
+ def initialize(msg, struct_name = nil, field_name = nil)
14
+ @struct_name = struct_name
15
+ @field_name = field_name
16
+ super(msg)
13
17
  end
14
18
  end
15
19
 
16
20
  class TypeMismatch < Exception
17
- def initialize(msg)
18
- super
21
+ attr_reader :struct_name, :field_name
22
+
23
+ def initialize(msg, struct_name = nil, field_name = nil)
24
+ @struct_name = struct_name
25
+ @field_name = field_name
26
+ super(msg)
19
27
  end
20
28
  end
21
29
 
@@ -104,3 +104,25 @@ class Nothing < ::Sparsam::Struct
104
104
  init_thrift_struct(self)
105
105
  end
106
106
 
107
+ class EveryType < ::Sparsam::Struct
108
+ FIELDS = {
109
+ 1 => {:type => ::Sparsam::Types::BOOL, :name => 'a_bool', :optional => true},
110
+ 2 => {:type => ::Sparsam::Types::BYTE, :name => 'a_byte', :optional => true},
111
+ 3 => {:type => ::Sparsam::Types::I16, :name => 'an_i16', :optional => true},
112
+ 4 => {:type => ::Sparsam::Types::I32, :name => 'an_i32', :optional => true},
113
+ 5 => {:type => ::Sparsam::Types::I64, :name => 'an_i64', :optional => true},
114
+ 6 => {:type => ::Sparsam::Types::DOUBLE, :name => 'a_double', :optional => true},
115
+ 7 => {:type => ::Sparsam::Types::STRING, :name => 'a_binary', :binary => true, :optional => true},
116
+ 8 => {:type => ::Sparsam::Types::STRING, :name => 'a_string', :optional => true},
117
+ 9 => {:type => ::Sparsam::Types::LIST, :name => 'an_i64_list', :element => {:type => ::Sparsam::Types::I64}, :optional => true},
118
+ 10 => {:type => ::Sparsam::Types::SET, :name => 'an_i64_set', :element => {:type => ::Sparsam::Types::I64}, :optional => true},
119
+ 11 => {:type => ::Sparsam::Types::MAP, :name => 'an_i64_map', :key => {:type => ::Sparsam::Types::I64}, :value => {:type => ::Sparsam::Types::I64}, :optional => true},
120
+ 12 => {:type => ::Sparsam::Types::LIST, :name => 'a_list_of_i64_maps', :element => {:type => ::Sparsam::Types::MAP, :key => {:type => ::Sparsam::Types::I64}, :value => {:type => ::Sparsam::Types::I64}}, :optional => true},
121
+ 13 => {:type => ::Sparsam::Types::MAP, :name => 'a_map_of_i64_maps', :key => {:type => ::Sparsam::Types::MAP, :key => {:type => ::Sparsam::Types::I64}, :value => {:type => ::Sparsam::Types::I64}}, :value => {:type => ::Sparsam::Types::MAP, :key => {:type => ::Sparsam::Types::I64}, :value => {:type => ::Sparsam::Types::I64}}, :optional => true},
122
+ 14 => {:type => ::Sparsam::Types::STRUCT, :name => 'a_struct', :class => ::US, :optional => true},
123
+ 15 => {:type => ::Sparsam::Types::STRUCT, :name => 'a_union', :class => ::UN, :optional => true}
124
+ }
125
+
126
+ init_thrift_struct(self)
127
+ end
128
+
data/spec/sparsam_spec.rb CHANGED
@@ -92,6 +92,7 @@ describe 'Sparsam' do
92
92
  expect {
93
93
  Sparsam.validate(NotSS, data, Sparsam::STRICT)
94
94
  }.to raise_error(Sparsam::TypeMismatch)
95
+
95
96
  expect {
96
97
  Sparsam.validate(EasilyInvalid, data, Sparsam::STRICT)
97
98
  }.to raise_error(Sparsam::TypeMismatch)
@@ -151,6 +152,21 @@ describe 'Sparsam' do
151
152
  }.to raise_error(Sparsam::TypeMismatch)
152
153
  end
153
154
 
155
+ it "includes additional data in TypeMismatch errors" do
156
+ data = EasilyInvalid.new
157
+ data.id_i32 = "definitely a string"
158
+
159
+ e = nil
160
+ begin
161
+ Sparsam.validate(EasilyInvalid, data, Sparsam::STRICT)
162
+ rescue Sparsam::TypeMismatch => exception
163
+ e = exception
164
+ end
165
+
166
+ e.struct_name.should == EasilyInvalid.name
167
+ e.field_name.should == "id_i32"
168
+ end
169
+
154
170
  it "works with crazy thriftness" do
155
171
  data = EasilyInvalid.new
156
172
  data.sure = [{ Set.new([1]) => { 1 => Set.new([[{ EasilyInvalid.new => "sure" }]]) } }]
@@ -189,6 +205,20 @@ describe 'Sparsam' do
189
205
  expect { data.validate }.to raise_error(Sparsam::MissingMandatory)
190
206
  end
191
207
 
208
+ it "includes additional information on missing required fields in exception" do
209
+ data = MiniRequired.new
210
+
211
+ e = nil
212
+ begin
213
+ data.validate
214
+ rescue Sparsam::MissingMandatory => exception
215
+ e = exception
216
+ end
217
+
218
+ e.struct_name.should == MiniRequired.name
219
+ e.field_name.should == "id_i32"
220
+ end
221
+
192
222
  it "will throw errors when given junk data" do
193
223
  expect {
194
224
  Sparsam::Deserializer.deserialize(SS, "wolololololol")
@@ -300,5 +330,80 @@ describe 'Sparsam' do
300
330
  Sparsam::Deserializer.deserialize(SS, really_bad, Sparsam::BinaryProtocol)
301
331
  }.to raise_error(Sparsam::Exception)
302
332
  end
333
+
334
+ it "handles all sorts of type issues without crashing" do
335
+ field_map = {
336
+ a_bool: :boolean,
337
+ a_byte: :int,
338
+ an_i16: :int,
339
+ an_i32: :int,
340
+ an_i64: :int,
341
+ a_double: :float,
342
+ a_binary: :string,
343
+ a_string: :string,
344
+
345
+ an_i64_list: :int_list,
346
+ an_i64_set: :int_set,
347
+ an_i64_map: :int_map,
348
+
349
+ a_list_of_i64_maps: :int_map_list,
350
+ a_map_of_i64_maps: :int_map_map,
351
+
352
+ a_struct: :struct,
353
+ a_union: :union,
354
+ }
355
+
356
+ scalar_values = {
357
+ boolean: true,
358
+ int: 42,
359
+ float: 3.14,
360
+ string: "Hello",
361
+ struct: US.new(id_i32: 10),
362
+ union: UN.new(id_s: "woo"),
363
+ complex: Complex(1),
364
+ bigint: 2**128,
365
+ rational: Rational(2, 3),
366
+ }
367
+
368
+ simple_collection_values = scalar_values.each_with_object({}) do |(type, val), obj|
369
+ obj[:"#{type}_list"] = [val]
370
+ obj[:"#{type}_set"] = Set.new([val])
371
+ obj[:"#{type}_map"] = { val => val }
372
+ end
373
+
374
+ nested_collection_values =
375
+ simple_collection_values.each_with_object({}) do |(type, val), obj|
376
+ obj[:"#{type}_list"] = [val]
377
+ obj[:"#{type}_set"] = Set.new([val])
378
+ obj[:"#{type}_map"] = { val => val }
379
+ end
380
+
381
+ all_values = scalar_values.merge(simple_collection_values).merge(nested_collection_values)
382
+
383
+ field_map.each do |field, type|
384
+ all_values.each do |val_type, val|
385
+ next if val_type == type
386
+
387
+ s = EveryType.new
388
+ s.send(:"#{field}=", val)
389
+
390
+ # Validation doesn't do range checking, though serialization does
391
+ unless val_type.to_s =~ /bigint/
392
+ expect {
393
+ Sparsam.validate(s.class, s, Sparsam::STRICT)
394
+ }.to(
395
+ raise_error(Sparsam::TypeMismatch),
396
+ "assigning #{field} : #{type} a value of " \
397
+ "#{val.inspect} : #{val_type} did not raise TypeMismatch"
398
+ )
399
+ end
400
+
401
+ begin
402
+ s.serialize
403
+ rescue TypeError, RangeError, NoMethodError, Sparsam::TypeMismatch
404
+ end
405
+ end
406
+ end
407
+ end
303
408
  end
304
409
  end
data/spec/user.thrift CHANGED
@@ -60,3 +60,24 @@ struct NotSS_plus {
60
60
 
61
61
  struct nothing {
62
62
  }
63
+
64
+ struct EveryType {
65
+ 1: optional bool a_bool
66
+ 2: optional byte a_byte
67
+ 3: optional i16 an_i16
68
+ 4: optional i32 an_i32
69
+ 5: optional i64 an_i64
70
+ 6: optional double a_double
71
+ 7: optional binary a_binary
72
+ 8: optional string a_string
73
+
74
+ 9: optional list<i64> an_i64_list
75
+ 10: optional set<i64> an_i64_set
76
+ 11: optional map<i64, i64> an_i64_map
77
+
78
+ 12: optional list<map<i64, i64>> a_list_of_i64_maps
79
+ 13: optional map<map<i64, i64>, map<i64, i64>> a_map_of_i64_maps
80
+
81
+ 14: optional US a_struct
82
+ 15: optional UN a_union
83
+ }
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sparsam
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.4
4
+ version: 0.1.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Airbnb Thrift Developers
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-08-01 00:00:00.000000000 Z
11
+ date: 2017-08-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rspec
@@ -161,7 +161,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
161
161
  version: '0'
162
162
  requirements: []
163
163
  rubyforge_project: thrift
164
- rubygems_version: 2.6.12
164
+ rubygems_version: 2.6.11
165
165
  signing_key:
166
166
  specification_version: 4
167
167
  summary: Ruby bindings for Apache Thrift