marty 2.4.1 → 2.4.2

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: c3db866b4afffd7cef3f93ecdaf8698dc45bf169
4
- data.tar.gz: f1ba792e54124bd28c8a50a5080a3ea556dfe4bb
3
+ metadata.gz: 4069f0be0ac4faa38738a0aa5f1a76fa56fcbed5
4
+ data.tar.gz: 832fed4b13056de3a94fbcaebb9fa463fd475f0b
5
5
  SHA512:
6
- metadata.gz: d40dc1b4535d794f22bd51916449cf48738030780b37a7392abf7ee95ed3ee7f890415090c5b189e271a6f26fa519bf60f8075e9912ff0861896deb73531a366
7
- data.tar.gz: 59c87e1a2e69ba55ad37a0b43ae5876487a3226ca1f34b060bf3b889b318f8616cfa3d10f511450a806223a31107d84b4fa5a142af089da7a206ebad1c0151a5
6
+ metadata.gz: 57cd24df5c095785c4a22d8c801db32a1e504e773bf0d0edce681d53edd71c1b22e55925bcd1fe931dc71457d88b720ad1ad04f1010d50d07df0dfe93caf52e1
7
+ data.tar.gz: bbcb53ecee094328f5271f5f60b90daa4f606729b196389d0e2316cb14cb53318ef57b603626fca446521aea19a9f734b9b61f1014ec5a7c45e8f4b4bb05bcba
@@ -35,6 +35,69 @@ module Marty
35
35
  end
36
36
 
37
37
  JSON::Validator.register_validator(self.new)
38
- end
39
38
 
39
+ def self.get_numbers(schema)
40
+ numbers = []
41
+
42
+ # traverse the schema, if we find a type: number, add to numbers []
43
+ trav = lambda { |tree, key, path=[]|
44
+ return tree.each do|k, v|
45
+ trav.call(v, k, path + [k])
46
+ end if tree.is_a?(Hash)
47
+ numbers << path[0..-2] if key == 'type' && tree == 'number'
48
+ }
49
+ trav.call(schema, nil, [])
50
+
51
+ # convert the array stuff [ie. "items", "properties"] to :array
52
+ numbers.map do |num|
53
+ num.delete("properties")
54
+ num.map{|n| n=="items" ? :array : n}
55
+ end
56
+ end
57
+
58
+ def self.fix_numbers(json, numbers)
59
+
60
+ # follow path to drill into json
61
+ drill = lambda {|tree, path|
62
+ return unless tree
63
+ key = path.first
64
+ val = val = tree.send(:[], key) unless key == :array
65
+ if key == :array
66
+ # if we are at an array of numbers, fix them
67
+ if path.length == 1
68
+ tree.each_with_index do |v, i|
69
+ tree[i] = v.to_f if v.is_a?(Numeric)
70
+ end
71
+ else
72
+ # this is an array of object so continue to drill down
73
+ tree.each {|sub| drill.call(sub, path[1..-1])}
74
+ end
75
+ elsif path.length == 1
76
+ # fix a non array field
77
+ tree.send(:[]=, key, val.to_f) if val.is_a?(Numeric)
78
+ else
79
+ # continue drilling
80
+ drill.call(val, path[1..-1])
81
+ end
82
+ }
83
+ numbers.each {|number| drill.call(json, number)}
84
+ end
85
+
86
+ def self.get_schema(tag, sname, node, attr)
87
+ begin
88
+ Marty::ScriptSet.new(tag).get_engine(sname+'Schemas').
89
+ evaluate(node, attr, {})
90
+ rescue => e
91
+ id = "#{sname}/#{node} attrs=#{attr}"
92
+
93
+ # the schema DL might not exist at all, or might not define the attr
94
+ # being requested
95
+ sch_not_found = ['No such script', "undefined method `#{attr}__D'",
96
+ "node #{node} is undefined"]
97
+ msg = sch_not_found.detect{|msg| e.message.starts_with?(msg)} ?
98
+ 'Schema not defined' : "Problem with schema: #{e.message}"
99
+ return "Schema error for #{id}: #{msg}"
100
+ end
101
+ end
102
+ end
40
103
  end
@@ -1,3 +1,3 @@
1
1
  module Marty
2
- VERSION = "2.4.1"
2
+ VERSION = "2.4.2"
3
3
  end
@@ -30,6 +30,9 @@ class Marty::Api::Base
30
30
  def self.after_evaluate api_params, result
31
31
  end
32
32
 
33
+ @@numbers = {}
34
+ @@schemas = {}
35
+
33
36
  def self.is_authorized? params
34
37
  is_secured = Marty::ApiAuth.where(
35
38
  script_name: params[:script],
@@ -47,16 +50,25 @@ class Marty::Api::Base
47
50
  # prevent script evaluation from modifying passed in params
48
51
  params = params.deep_dup
49
52
 
53
+ schema_key = [params[:tag], params[:script], params[:node], params[:attr]]
54
+ input_schema = nil
55
+ begin
56
+ # get_schema will either return a hash with the schema,
57
+ # or a string with the error
58
+ input_schema = @@schemas[schema_key] ||=
59
+ Marty::JsonSchema.get_schema(*schema_key)
60
+ rescue => e
61
+ return {error: e.message}
62
+ end
63
+
50
64
  # validate input schema
51
65
  if config[:input_validated]
52
- begin
53
- schema = SchemaValidator::get_schema(params)
54
- rescue => e
55
- return {error: e.message}
56
- end
66
+
67
+ # must fail if schema not found or some other error
68
+ return {"error": input_schema} if input_schema.is_a?(String)
57
69
 
58
70
  begin
59
- res = SchemaValidator::validate_schema(schema, params[:params])
71
+ res = SchemaValidator::validate_schema(input_schema, params[:params])
60
72
  rescue NameError
61
73
  return {error: "Unrecognized PgEnum for attribute #{params[:attr]}"}
62
74
  rescue => e
@@ -68,6 +80,19 @@ class Marty::Api::Base
68
80
  schema_errors
69
81
  end
70
82
 
83
+ # if schema was found
84
+ if input_schema.is_a?(Hash)
85
+ # fix numbers types
86
+ numbers = @@numbers[schema_key] ||=
87
+ Marty::JsonSchema.get_numbers(input_schema)
88
+
89
+ # modify params in place
90
+ Marty::JsonSchema.fix_numbers(params[:params], numbers)
91
+ elsif !input_schema.include?("Schema not defined")
92
+ # else if some error besides schema not defined, fail
93
+ return {error: input_schema}
94
+ end
95
+
71
96
  # get script engine
72
97
  begin
73
98
  engine = Marty::ScriptSet.new(params[:tag]).get_engine(params[:script])
@@ -36,6 +36,21 @@ B: A
36
36
  p =? 5
37
37
  eof
38
38
 
39
+ sample_script3err = <<eof
40
+ A:
41
+ a = 2
42
+ p =?
43
+ c = a * 2
44
+ pc = p + c
45
+ lc = [pc, pc]
46
+
47
+ C: A
48
+ p =? 3
49
+
50
+ B: A
51
+ p =? 5
52
+ eof
53
+
39
54
  sample_script4 = <<eof
40
55
  import M3
41
56
  A: M3::A
@@ -121,8 +136,37 @@ A:
121
136
  else 'req3'
122
137
 
123
138
  eof
124
-
139
+ sample_script11 = <<eof
140
+ A:
141
+ f1_string =?
142
+ f2_bool =?
143
+ f3_integer =?
144
+ f4_number =?
145
+ f5_number =?
146
+ f6_array =?
147
+ f7_array =?
148
+
149
+ v0 = _
150
+ v1 = {
151
+ "input": v0,
152
+ "calc1": f4_number / 7,
153
+ "calc1a": f5_number / 7,
154
+ "calc2": f3_integer / 7,
155
+ "calc3": [ f7 / 7 for f7 in f7_array],
156
+ "calc4": f6_array[0].f14_number / 7,
157
+ "calc5": f6_array[0].f13_integer / 7,
158
+ "calc6": [ f7 / 7 for f7 in f6_array[0].f16_array]
159
+ }
160
+ eof
125
161
  script3_schema = <<eof
162
+ A:
163
+ pc = { "properties" : {
164
+ "p" : { "type" : "integer" },
165
+ }
166
+ }
167
+ eof
168
+
169
+ script3err_schema = <<eof
126
170
  A:
127
171
  pc = { "properties : {
128
172
  "p" : { "type" : "integer" },
@@ -132,6 +176,7 @@ eof
132
176
 
133
177
  script4_schema = <<eof
134
178
  A:
179
+ a = {}
135
180
  d = { "properties" : {
136
181
  "p" : { "type" : "integer" },
137
182
  }
@@ -333,7 +378,57 @@ A:
333
378
  new_check
334
379
  ] }
335
380
  eof
336
-
381
+ script11_schema = <<eof
382
+ A:
383
+ properties = {
384
+ "f1_string" : {"type": "string" },
385
+ "f2_bool" : {"type": "boolean" },
386
+ "f3_integer" : {"type": "integer" },
387
+ "f4_number" : {"type": "number" },
388
+ "f5_number" : {"type": "number", "minimum": 0 },
389
+ "f6_array" : {"type": "array",
390
+ "items": {
391
+ "type" : "object",
392
+ "properties": {
393
+ "f11_string" : {"type": "string" },
394
+ "f12_bool" : {"type": "boolean" },
395
+ "f13_integer" : {"type": "integer" },
396
+ "f14_number" : {"type": "number" },
397
+ "f15_number" : {"type": "number", "minimum": 0 },
398
+ "f16_array": {
399
+ "type" : "array",
400
+ "uniqueItems" : true,
401
+ "minItems" : 0,
402
+ "maxItems" : 1,
403
+ "items": {
404
+ "type" : "number",
405
+ "minimum" : 0,
406
+ "multipleOf" : 0.001,
407
+ "not": {
408
+ "type": "integer"
409
+ }}}}}},
410
+ "f7_array": {
411
+ "type" : "array",
412
+ "uniqueItems" : true,
413
+ "minItems" : 0,
414
+ "maxItems" : 1,
415
+ "items": {
416
+ "type" : "number",
417
+ "minimum" : 0,
418
+ "multipleOf" : 0.001,
419
+ "not": {
420
+ "type": "integer"
421
+ }}},
422
+ "f8_object": {
423
+ "type": "object",
424
+ "properties": {
425
+ "f81_number": {"type": "number"},
426
+ "f82_array": {"type" : "array",
427
+ "items": {"type": "number"}}}}}
428
+
429
+ v1 = { "properties": properties}
430
+ v0 = { "properties": properties}
431
+ eof
337
432
  describe Marty::RpcController do
338
433
  before(:each) {
339
434
  @routes = Marty::Engine.routes
@@ -351,6 +446,7 @@ describe Marty::RpcController do
351
446
  "M1" => sample_script,
352
447
  "M2" => sample_script.gsub(/a/, "aa").gsub(/b/, "bb"),
353
448
  "M3" => sample_script3,
449
+ "M3err" => sample_script3err,
354
450
  "M4" => sample_script4,
355
451
  "M5" => sample_script5,
356
452
  "M6" => sample_script6,
@@ -358,7 +454,9 @@ describe Marty::RpcController do
358
454
  "M8" => sample_script8,
359
455
  "M9" => sample_script9,
360
456
  "M10" => sample_script10,
457
+ "M11" => sample_script11,
361
458
  "M3Schemas" => script3_schema,
459
+ "M3errSchemas" => script3err_schema,
362
460
  "M4Schemas" => script4_schema,
363
461
  "M5Schemas" => script5_schema,
364
462
  "M6Schemas" => script6_schema,
@@ -366,6 +464,7 @@ describe Marty::RpcController do
366
464
  "M8Schemas" => script8_schema,
367
465
  "M9Schemas" => script9_schema,
368
466
  "M10Schemas" => script10_schema,
467
+ "M11Schemas" => script11_schema,
369
468
  }, Date.today + 1.minute)
370
469
 
371
470
  @p1 = Marty::Posting.do_create("BASE", Date.today + 2.minute, 'a comment')
@@ -676,12 +775,12 @@ describe Marty::RpcController do
676
775
  attrs: attr,
677
776
  params: params
678
777
  }
679
- expect = "Schema error for M4/A attrs=h: Problem with schema"
680
- expect(response.body).to include("error,#{expect}")
778
+ expect = "error,Schema error for M4/A attrs=h: Schema not defined"
779
+ expect(response.body).to include(expect)
681
780
  end
682
781
 
683
782
  it "returns an error message on invalid schema" do
684
- Marty::ApiConfig.create!(script: "M3",
783
+ Marty::ApiConfig.create!(script: "M3err",
685
784
  node: "A",
686
785
  attr: nil,
687
786
  logged: false,
@@ -690,13 +789,13 @@ describe Marty::RpcController do
690
789
  params = {"p" => 5}.to_json
691
790
  get 'evaluate', params: {
692
791
  format: :csv,
693
- script: "M3",
792
+ script: "M3err",
694
793
  node: "A",
695
794
  attrs: attr,
696
795
  params: params
697
796
  }
698
- expect = "Schema error for M3/A attrs=pc: Problem with schema: "\
699
- "syntax error M3Schemas:2\r\n"
797
+ expect = "Schema error for M3err/A attrs=pc: Problem with schema: "\
798
+ "syntax error M3errSchemas:2\r\n"
700
799
  expect(response.body).to eq("error,#{expect}")
701
800
  end
702
801
 
@@ -1132,6 +1231,39 @@ describe Marty::RpcController do
1132
1231
  expect(response.body).to match(/"error":"Permission denied"/)
1133
1232
  end
1134
1233
 
1234
+ it "should convert incoming ints in number fields to float" do
1235
+ api = Marty::ApiAuth.new
1236
+ api.app_name = 'TestApp'
1237
+ api.script_name = 'M11'
1238
+ api.save!
1239
+ p = File.expand_path('../../fixtures/json', __FILE__)
1240
+ f = "%s/%s" % [p, "rpc_controller.json"]
1241
+ begin
1242
+ tests = JSON.parse(File.read(f))
1243
+ rescue => e
1244
+ puts "Error parsing #{f}: #{e.message}"
1245
+ raise
1246
+ end
1247
+ aggregate_failures "input coercing" do
1248
+ tests.each_with_index do |t, idx|
1249
+ get 'evaluate', params: {
1250
+ format: :json,
1251
+ script: "M11",
1252
+ node: "A",
1253
+ attrs: t['attr'],
1254
+ api_key: api.api_key,
1255
+ params: t['request'].to_json
1256
+ }
1257
+ resp = JSON.parse(response.body)
1258
+ comp = struct_compare(resp, t['result'], {"float_int_nomatch"=>true})
1259
+ (puts "TEST=#{idx}\n#{comp}"; binding.pry) if comp &&
1260
+ ENV['PRY'] == 'true'
1261
+
1262
+ expect(comp).to be_nil, "input conversion test #{idx}:\n#{comp}"
1263
+ end
1264
+ end
1265
+ end
1266
+
1135
1267
  context "conditional validation" do
1136
1268
  before(:all) do
1137
1269
  Marty::ApiConfig.create!(script: "M10",
@@ -1237,7 +1369,7 @@ describe Marty::RpcController do
1237
1369
 
1238
1370
  it 'returns engine/tag lookup error if script not found' do
1239
1371
  get :evaluate, params: {format: :json, script: 'M1', attrs: "e", tag: 'invalid'}
1240
- expect(response.body).to match(/"error":"Can't get engine:/)
1372
+ expect(response.body).to match(/bad tag identifier.*invalid/)
1241
1373
  get :evaluate, params: {format: :json, script: 'Invalid', attrs: "e", tag: t1.name}
1242
1374
  expect(response.body).to match(/"error":"Can't get engine:/)
1243
1375
  end
@@ -0,0 +1,175 @@
1
+ [
2
+ {"attr": "v1",
3
+ "request": {
4
+ "f1_string" : "hi",
5
+ "f2_bool" : false,
6
+ "f3_integer" : 24,
7
+ "f4_number" : 24,
8
+ "f5_number" : 24.0,
9
+ "f6_array" : [
10
+ {"f11_string" : "bye",
11
+ "f12_bool" : true,
12
+ "f13_integer" : 24,
13
+ "f14_number" : 24,
14
+ "f15_number" : 24.0,
15
+ "f16_array" : [24,24,24.0]},
16
+ {"f11_string" : "yes",
17
+ "f12_bool" : true,
18
+ "f13_integer" : 24,
19
+ "f14_number" : 24,
20
+ "f15_number" : 24.0,
21
+ "f16_array" : [24]}],
22
+ "f7_array" : [24,24,24.0],
23
+ "f8_object": {"f81_number": 5}
24
+ },
25
+ "result": {
26
+ "input": {
27
+ "f1_string" : "hi",
28
+ "f2_bool" : false,
29
+ "f3_integer": 24,
30
+ "f4_number" : 24.0,
31
+ "f5_number" : 24.0,
32
+ "f6_array": [
33
+ {
34
+ "f11_string": "bye",
35
+ "f12_bool": true,
36
+ "f13_integer": 24,
37
+ "f14_number": 24.0,
38
+ "f15_number": 24.0,
39
+ "f16_array": [
40
+ 24.0,
41
+ 24.0,
42
+ 24.0
43
+ ]
44
+ },
45
+ {
46
+ "f11_string": "yes",
47
+ "f12_bool": true,
48
+ "f13_integer": 24,
49
+ "f14_number": 24.0,
50
+ "f15_number": 24.0,
51
+ "f16_array": [
52
+ 24.0
53
+ ]
54
+ }
55
+ ],
56
+ "f7_array": [
57
+ 24.0,
58
+ 24.0,
59
+ 24.0
60
+ ],
61
+ "f8_object": {"f81_number": 5.0}
62
+ },
63
+ "calc1": 3.428571,
64
+ "calc1a": 3.428571,
65
+ "calc2": 3,
66
+ "calc3": [
67
+ 3.428571,
68
+ 3.428571,
69
+ 3.428571
70
+ ],
71
+ "calc4": 3.428571,
72
+ "calc5": 3,
73
+ "calc6": [
74
+ 3.428571,
75
+ 3.428571,
76
+ 3.428571
77
+ ]
78
+ }},
79
+ {"attr": "v1",
80
+ "request": {
81
+ "f1_string" : "hi",
82
+ "f2_bool" : false,
83
+ "f3_integer" : 1,
84
+ "f4_number" : 1,
85
+ "f5_number" : 1,
86
+ "f6_array" : [
87
+ {"f11_string" : "bye",
88
+ "f12_bool" : true,
89
+ "f13_integer" : 1,
90
+ "f14_number" : 1,
91
+ "f15_number" : 1,
92
+ "f16_array" : [1]}
93
+ ],
94
+ "f7_array" : [1],
95
+ "f8_object": {"f82_array": [1]}
96
+
97
+ },
98
+ "result": {
99
+ "input": {
100
+ "f1_string": "hi",
101
+ "f2_bool": false,
102
+ "f3_integer": 1,
103
+ "f4_number": 1.0,
104
+ "f5_number": 1.0,
105
+ "f6_array": [
106
+ {
107
+ "f11_string": "bye",
108
+ "f12_bool": true,
109
+ "f13_integer": 1,
110
+ "f14_number": 1.0,
111
+ "f15_number": 1.0,
112
+ "f16_array": [
113
+ 1.0
114
+ ]
115
+ }],
116
+ "f7_array": [
117
+ 1.0
118
+ ],
119
+ "f8_object": {"f82_array": [1.0]}
120
+ },
121
+ "calc1": 0.142857,
122
+ "calc1a": 0.142857,
123
+ "calc2": 0,
124
+ "calc3": [
125
+ 0.142857
126
+ ],
127
+ "calc4": 0.142857,
128
+ "calc5": 0,
129
+ "calc6": [
130
+ 0.142857
131
+ ]
132
+ }},
133
+ {"attr": "v0",
134
+ "request": {"f4_number": 123},
135
+ "result": {"f4_number": 123.0}
136
+ },
137
+ {"attr": "v0",
138
+ "request": {"f7_array": [1,2.0,3]},
139
+ "result": {"f7_array": [1.0,2.0,3.0]}
140
+ },
141
+ {"attr": "v0",
142
+ "request": {"f6_array": [{"f16_array": [1]}]},
143
+ "result": {"f6_array": [{"f16_array": [1.0]}]}
144
+ },
145
+ {"attr": "v0",
146
+ "request": {"f6_array": [{"f16_array": []}],
147
+ "f7_array": []},
148
+ "result": {"f6_array": [{"f16_array": []}],
149
+ "f7_array": []}
150
+ },
151
+ {"attr": "v0",
152
+ "request": {},
153
+ "result": {}
154
+ },
155
+ {"attr": "v0",
156
+ "request": {"f_bool":true},
157
+ "result": {"f_bool":true}
158
+ },
159
+ {"attr": "v0",
160
+ "request": {"f4_number": "bad"},
161
+ "result": {"f4_number": "bad"}
162
+ },
163
+ {"attr": "v0",
164
+ "request": {"f7_array": [1,"bad",3]},
165
+ "result": {"f7_array": [1.0,"bad",3.0]}
166
+ },
167
+ {"attr": "v0",
168
+ "request": {"f4_number": true},
169
+ "result": {"f4_number": true}
170
+ },
171
+ {"attr": "v0",
172
+ "request": {"f7_array": [1,true,3]},
173
+ "result": {"f7_array": [1.0,true,3.0]}
174
+ }
175
+ ]
@@ -0,0 +1,170 @@
1
+ [
2
+ { "example_number": "0",
3
+ "v1" : [1],
4
+ "v2" : [1],
5
+ "res" : false
6
+ },
7
+ { "example_number": "1",
8
+ "v1": [1],
9
+ "v2": [2],
10
+ "res": "path=[0] 1 != 2"
11
+ },
12
+ { "example_number": "2",
13
+ "v1" : [1,2],
14
+ "v2" : [1,3],
15
+ "res" : "path=[1] 2 != 3"
16
+ },
17
+ { "example_number": "3",
18
+ "v1": [1,[1,2]],
19
+ "v2": [1,[1]],
20
+ "res" : "path=[1] array size mismatch 2 != 1"
21
+ },
22
+ { "example_number": "4",
23
+ "v1": [[1,2], "foo", { "abc" : [1,2] }],
24
+ "v2": [[1,2], "foo", { "abc" : [1,2] }],
25
+ "res" : false
26
+ },
27
+ { "example_number": "5",
28
+ "v1": [[1,2], "foo", { "abc" : [1,2.0] }],
29
+ "v2": [[1,2], "foo", { "abc" : [1.000,2] }],
30
+ "res" : false
31
+ },
32
+ { "example_number": "6",
33
+ "v1": [[1,2], "foo", { "abc" : [1,{"a": 2}] }],
34
+ "v2": [[1,2], "foo", { "abc" : [1,{"a": 2}] }],
35
+ "res" : false
36
+ },
37
+ { "example_number": "7",
38
+ "v1": [[1,2], "foo", { "abc" : [1,{"a": 2}] }],
39
+ "v2": [[1,2], "foo", { "abc" : [1,{"a": 2, "b":3}] }],
40
+ "res" : "path=[2][\"abc\"][1] hash extra keys [\"b\"]"
41
+ },
42
+ { "example_number": "8",
43
+ "v1": [[1,2], "foo", { "abc" : [1,{"a": 2}] }],
44
+ "v2": [[1,2], "foo", { "abc" : [1,{"b": 2}] }],
45
+ "res" : "path=[2][\"abc\"][1] hash extra keys [\"a\"]"
46
+ },
47
+ { "example_number": "9",
48
+ "v1": [[[1,2,3], {"foo": "bar", "baz": true, "plugh": 2.434, "hash": { "value": [1,2] }}], 7, "value"],
49
+ "v2": [[[1,2,3], {"foo": "barz", "baz": true, "plugh": 2.434, "hash": { "value": [1,2] }}], 7, "value"],
50
+ "res": "path=[0][1][\"foo\"] bar != barz"
51
+ },
52
+ { "example_number": "10",
53
+ "v1": [[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[1]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]],
54
+ "v2": [[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[1]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]],
55
+ "res": false
56
+ },
57
+ { "example_number": "11",
58
+ "v1": [[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[1]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]],
59
+ "v2": [[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[["one"]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]],
60
+ "res": "path=[0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0] class mismatch Integer String"
61
+ },
62
+ { "example_number": "12",
63
+ "v1": [[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[1]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]],
64
+ "v2": [[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[1]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]],
65
+ "res": "path=[0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0] class mismatch Array Integer"
66
+ },
67
+ { "example_number": "13",
68
+ "v1": [{"a": 1, "b": 2, "c": 3, "d": 4},
69
+ {"a": 1, "b": 2, "c": 3, "d": 4},
70
+ {"a": 1, "b": 2, "c": 3, "d": 4},
71
+ {"a": 1, "b": 2, "c": 3, "d": 4}],
72
+ "v2": [{"a": 1, "b": 2, "c": 3, "d": 4},
73
+ {"a": 1, "b": 2, "c": 3, "d": 4},
74
+ {"a": 1, "b": 2, "c": 3, "d": 4}],
75
+ "res": "path= array size mismatch 4 != 3"
76
+ },
77
+ { "example_number": "14",
78
+ "v1": [{"a": 1, "b": 2, "c": 3, "d": 4},
79
+ {"a": 1, "b": 2, "c": 3, "d": 4},
80
+ {"a": 1, "b": 2, "c": 3, "d": 4},
81
+ {"a": 1, "b": 2, "c": 3, "d": 4}],
82
+ "v2": [{"a": 1, "b": 2, "c": 3, "d": 4},
83
+ {"a": 1, "b": 2, "c": 3, "d": 4},
84
+ {"a": 1, "b": 2, "d": 4},
85
+ {"a": 1, "b": 2, "c": 3, "d": 4}],
86
+ "res": "path=[2] hash extra keys [\"c\"]"
87
+ },
88
+ { "example_number": "15",
89
+ "v1": [{"a": [{"b": [{"c": [{"d": [1,2.00,3,4]}]}]}]}],
90
+ "v2": [{"a": [{"b": [{"c": [{"d": [1.00,2,3.00,4.00]}]}]}]}],
91
+ "res": false
92
+ },
93
+ { "example_number": "16",
94
+ "v1": [{"a": [{"b": [{"c": [{"d": [1,2,3,4]}]}]}]}],
95
+ "v2": [{"a": [{"b": [{"c": [{"d": [1.00,2.10,3.00,4.00]}]}]}]}],
96
+ "res": "path=[0][\"a\"][0][\"b\"][0][\"c\"][0][\"d\"][1] 2 != 2.1"
97
+ },
98
+ { "example_number": "17",
99
+ "v1": [{"a": [{"b": [{"c": [{"d": [1,2.10,3,4]}]}]}]}],
100
+ "v2": [{"a": [{"b": [{"c": [{"d": [1.00,2,3.00,4.00]}]}]}]}],
101
+ "res": "path=[0][\"a\"][0][\"b\"][0][\"c\"][0][\"d\"][1] 2.1 != 2"
102
+ },
103
+ { "example_number": "18",
104
+ "v1": {"pi": 3.1415926, "e": 2.718281828459, "phi": 1.618034},
105
+ "v2": {"pi": 3.1415926, "e": 2.718281811111, "phi": 1.618034},
106
+ "res": false
107
+ },
108
+ { "example_number": "19",
109
+ "v1": [[[1,2,3], {"foo": "bar", "baz": false, "plugh": 2.434, "hash": { "value": [1,2] }}], 7, "value"],
110
+ "v2": [[[1,2,3], {"foo": "bar", "baz": false, "plugh": 2.434, "hash": { "value": [1,2] }}], 7, "value"],
111
+ "res": false
112
+ },
113
+ { "example_number": "20",
114
+ "v1": [[[1,2,3], {"foo": "bar", "baz": null, "plugh": 2.434, "hash": { "value": [1,2] }}], 7, "value"],
115
+ "v2": [[[1,2,3], {"foo": "bar", "baz": null, "plugh": 2.434, "hash": { "value": [1,2] }}], 7, "value"],
116
+ "res": false
117
+ },
118
+ { "example_number": "21",
119
+ "v1": [[[1,2,3], {"foo": "bar", "baz": null, "plugh": 2.434, "hash": { "value": [1,2] }}], 7, "value"],
120
+ "v2": [[[1,2,3], {"foo": "bar", "baz": false, "plugh": 2.434, "hash": { "value": [1,2] }}], 7, "value"],
121
+ "res": "path=[0][1][\"baz\"] class mismatch NilClass FalseClass"
122
+ },
123
+ { "example_number": "22",
124
+ "v1": [[[1,2,3], {"foo": "bar", "baz": true, "plugh": 2.434, "hash": { "value": [1,false] }}], 7, "value"],
125
+ "v2": [[[1,2,3], {"foo": "bar", "baz": true, "plugh": 2.434, "hash": { "value": [1,null] }}], 7, "value"],
126
+ "res": "path=[0][1][\"hash\"][\"value\"][1] class mismatch FalseClass NilClass"
127
+ },
128
+ { "example_number": "23",
129
+ "v1": {"pi": 3.1415926, "e": 2.7182818, "phi": 1.618034},
130
+ "v2": {"pi": 3.1415926, "e": 2.7282818, "phi": 1.618034},
131
+ "res": "path=[\"e\"] 2.7182818 != 2.7282818"
132
+ },
133
+ { "example_number": "24",
134
+ "v1": {"error": "Some error message returned"},
135
+ "v2": {"pi": 3.1415926, "e": 2.7282818, "phi": 1.618034},
136
+ "res": "path= hash extra keys [\"error\"]"
137
+ },
138
+ { "example_number": "25",
139
+ "v1": {"pi": 3.1415926, "e": 2.7282818, "phi": 1.618034},
140
+ "v2": {"error": "Some error message returned"},
141
+ "res": "path= hash extra keys [\"pi\", \"e\", \"phi\"]"
142
+ },
143
+ { "example_number": "26",
144
+ "v1": {"error": "Another error message returned"},
145
+ "v2": [{"pi": 3.1415926, "e": 2.7282818, "phi": 1.618034}],
146
+ "res": "Another error message returned"
147
+ },
148
+ { "example_number": "27",
149
+ "v1": [{"pi": 3.1415926, "e": 2.7282818, "phi": 1.618034}],
150
+ "v2": {"error": "Yet another error message returned"},
151
+ "res": "Yet another error message returned"
152
+ },
153
+ { "example_number": "28",
154
+ "v1": {"three": 3},
155
+ "v2": {"three": 3.0},
156
+ "res": false
157
+ },
158
+ { "example_number": "29",
159
+ "v1": {"three": 3},
160
+ "v2": {"three": 3.0},
161
+ "res": "path=[\"three\"] class mismatch Integer Float",
162
+ "cmp_opts": {"float_int_nomatch": true}
163
+ },
164
+ { "example_number": "30",
165
+ "v1": {"three": 3, "dummy": "hi", "dummy2": 1},
166
+ "v2": {"three": 3.0, "dummy": "bye", "dummy2": 2},
167
+ "res": false,
168
+ "cmp_opts": {"float_int_nomatch": false, "ignore": ["dummy", "dummy2"]}
169
+ }
170
+ ]
@@ -0,0 +1,19 @@
1
+ require 'spec_helper'
2
+
3
+ describe "StructCompare" do
4
+ it "compares correctly" do
5
+ p = File.expand_path('../../fixtures/misc', __FILE__)
6
+ fname = "%s/%s" % [p, 'struct_compare_tests.txt']
7
+ aggregate_failures 'struct_compare' do
8
+ data = JSON.parse(File.read(fname))
9
+ data.each do |ex|
10
+ args = [ex["v1"], ex["v2"], ex['cmp_opts']].compact
11
+ comp = struct_compare(*args)
12
+ comparison = comp.nil? ? false : comp
13
+ num = ex["example_number"]
14
+ binding.pry if comparison != ex['res']
15
+ expect(comparison).to eq(ex["res"]), "Test ##{num} failed: #{comparison}"
16
+ end
17
+ end
18
+ end
19
+ end
@@ -1,5 +1,5 @@
1
1
  module Marty::RSpec::StructureCompare
2
- def self.struct_compare_all(v1raw, v2raw, path=[], errs=[])
2
+ def self.struct_compare_all(v1raw, v2raw, key=nil, cmp_opts={}, path=[], errs=[])
3
3
  pathstr = path.map(&:to_s).join
4
4
  v1,v2 = [v1raw, v2raw].map { |v| v.class == ActiveSupport::TimeWithZone ?
5
5
  DateTime.parse(v.to_s) : v }
@@ -12,18 +12,22 @@ module Marty::RSpec::StructureCompare
12
12
 
13
13
  return errs + ["path=#{pathstr} class mismatch #{v1.class} #{v2.class}"] unless
14
14
  v1.class == v2.class ||
15
- [v1,v2].map(&:class).to_set == Set.new([Integer, Float])
15
+ (!cmp_opts["float_int_nomatch"] &&
16
+ [v1,v2].map(&:class).to_set == Set.new([Integer, Float]))
16
17
 
18
+ override = (cmp_opts["ignore"]||[]).include?(key)
17
19
  case v1
18
20
  when String
19
21
  return errs + ["path=#{pathstr} #{v1} != #{v2}"] unless
20
22
  v1 == v2 ||
21
23
  Regexp.new('\A'+v1+'\z').match(v2) ||
22
- Regexp.new('\A'+v2+'\z').match(v1)
24
+ Regexp.new('\A'+v2+'\z').match(v1) ||
25
+ override
23
26
  when Integer, DateTime, TrueClass, FalseClass, NilClass
24
- return errs + ["path=#{pathstr} #{v1} != #{v2}"] if v1 != v2
27
+ return errs + ["path=#{pathstr} #{v1} != #{v2}"] if v1 != v2 && !override
25
28
  when Float
26
- return errs + ["path=#{pathstr} #{v1} != #{v2}"] if v1.round(6) != v2.round(6)
29
+ return errs + ["path=#{pathstr} #{v1} != #{v2}"] if
30
+ v1.round(6) != v2.round(6) && !override
27
31
  when Hash
28
32
  v1_v2, v2_v1 = v1.keys-v2.keys, v2.keys-v1.keys
29
33
 
@@ -31,13 +35,16 @@ module Marty::RSpec::StructureCompare
31
35
  errs.append("path=#{pathstr} hash extra keys #{v2_v1}") unless v2_v1.empty?
32
36
 
33
37
  return errs + v1.map do |childkey, childval|
34
- struct_compare_all(childval, v2[childkey], path + [[childkey]], [])
38
+ struct_compare_all(childval, v2[childkey], childkey, cmp_opts,
39
+ path + [[childkey]], [])
35
40
  end.flatten
36
41
  when Array
37
- errs.append("path=#{pathstr} array size mismatch #{v1.size} != #{v2.size}") if
42
+ errs.append(
43
+ "path=#{pathstr} array size mismatch #{v1.size} != #{v2.size}") if
38
44
  v1.size != v2.size
39
45
  return errs + v1.each_with_index.map do |childval,index|
40
- struct_compare_all(childval, v2[index], path + [[index]], [])
46
+ struct_compare_all(childval, v2[index], nil, cmp_opts, path + [[index]],
47
+ [])
41
48
  end.flatten
42
49
  else
43
50
  raise "unhandled #{v1.class}"
@@ -46,16 +53,18 @@ module Marty::RSpec::StructureCompare
46
53
  end
47
54
  end
48
55
 
49
- def struct_compare(v1raw, v2raw)
56
+ def struct_compare(v1raw, v2raw, cmp_opts={})
50
57
  begin
51
- res = Marty::RSpec::StructureCompare.struct_compare_all(v1raw, v2raw).first
58
+ res = Marty::RSpec::StructureCompare.struct_compare_all(v1raw, v2raw, nil,
59
+ cmp_opts).first
52
60
  rescue => e
53
61
  e.message
54
62
  end
55
63
  end
56
- def struct_compare_all(v1raw, v2raw)
64
+ def struct_compare_all(v1raw, v2raw, cmp_opts={})
57
65
  begin
58
- Marty::RSpec::StructureCompare.struct_compare_all(v1raw, v2raw)
66
+ Marty::RSpec::StructureCompare.struct_compare_all(v1raw, v2raw, nil,
67
+ cmp_opts)
59
68
  rescue => e
60
69
  e.message
61
70
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: marty
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.4.1
4
+ version: 2.4.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Arman Bostani
@@ -14,7 +14,7 @@ authors:
14
14
  autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
- date: 2018-11-12 00:00:00.000000000 Z
17
+ date: 2018-11-13 00:00:00.000000000 Z
18
18
  dependencies:
19
19
  - !ruby/object:Gem::Dependency
20
20
  name: pg
@@ -1651,6 +1651,8 @@ files:
1651
1651
  - spec/fixtures/csv/rule/DataGrid.csv
1652
1652
  - spec/fixtures/csv/rule/MyRule.csv
1653
1653
  - spec/fixtures/csv/rule/XyzRule.csv
1654
+ - spec/fixtures/json/rpc_controller.json
1655
+ - spec/fixtures/misc/struct_compare_tests.txt
1654
1656
  - spec/fixtures/scripts/load_tests/script1.dl
1655
1657
  - spec/fixtures/scripts/load_tests/script2.dl
1656
1658
  - spec/job_helper.rb
@@ -1662,6 +1664,7 @@ files:
1662
1664
  - spec/lib/mcfly_model_spec.rb
1663
1665
  - spec/lib/migrations/vw_marty_postings.sql.expected
1664
1666
  - spec/lib/migrations_spec.rb
1667
+ - spec/lib/struct_compare_spec.rb
1665
1668
  - spec/lib/xl_spec.rb
1666
1669
  - spec/lib/xl_styles_spec.rb
1667
1670
  - spec/models/api_auth_spec.rb