marty 2.4.1 → 2.4.2

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: 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