Skip to content

Commit 890a355

Browse files
SijaRX14
authored andcommittedJan 5, 2018
Sanitize query property names in JSON/YAML.mapping (#5345)
1 parent 315d3c3 commit 890a355

File tree

4 files changed

+152
-48
lines changed

4 files changed

+152
-48
lines changed
 

Diff for: ‎spec/std/json/mapping_spec.cr

+52
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,22 @@ private class JSONWithPresence
159159
})
160160
end
161161

162+
private class JSONWithQueryAttributes
163+
JSON.mapping({
164+
foo?: Bool,
165+
bar?: {type: Bool, default: false, presence: true, key: "is_bar"},
166+
})
167+
end
168+
169+
private class JSONWithOverwritingQueryAttributes
170+
property foo : Symbol?
171+
property bar : Symbol?
172+
JSON.mapping({
173+
foo?: Bool,
174+
bar?: {type: Bool, default: false, presence: true, key: "is_bar"},
175+
})
176+
end
177+
162178
describe "JSON mapping" do
163179
it "parses person" do
164180
person = JSONPerson.from_json(%({"name": "John", "age": 30}))
@@ -466,4 +482,40 @@ describe "JSON mapping" do
466482
json.last_name_present?.should be_false
467483
end
468484
end
485+
486+
describe "with query attributes" do
487+
it "defines query getter" do
488+
json = JSONWithQueryAttributes.from_json(%({"foo": true}))
489+
json.foo?.should be_true
490+
json.bar?.should be_false
491+
end
492+
493+
it "defines non-query setter and presence methods" do
494+
json = JSONWithQueryAttributes.from_json(%({"foo": false}))
495+
json.bar_present?.should be_false
496+
json.bar = true
497+
json.bar?.should be_true
498+
end
499+
500+
it "maps non-query attributes" do
501+
json = JSONWithQueryAttributes.from_json(%({"foo": false, "is_bar": false}))
502+
json.bar_present?.should be_true
503+
json.bar?.should be_false
504+
json.bar = true
505+
json.to_json.should eq(%({"foo":false,"is_bar":true}))
506+
end
507+
508+
it "raises if non-nilable attribute is nil" do
509+
ex = expect_raises JSON::ParseException, "Missing json attribute: foo" do
510+
JSONWithQueryAttributes.from_json(%({"is_bar": true}))
511+
end
512+
ex.location.should eq({1, 1})
513+
end
514+
515+
it "overwrites non-query attributes" do
516+
json = JSONWithOverwritingQueryAttributes.from_json(%({"foo": true}))
517+
typeof(json.@foo).should eq(Bool)
518+
typeof(json.@bar).should eq(Bool)
519+
end
520+
end
469521
end

Diff for: ‎spec/std/yaml/mapping_spec.cr

+52
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,22 @@ class YAMLRecursiveHash
131131
})
132132
end
133133

134+
private class YAMLWithQueryAttributes
135+
YAML.mapping({
136+
foo?: Bool,
137+
bar?: {type: Bool, default: false, presence: true, key: "is_bar"},
138+
})
139+
end
140+
141+
private class YAMLWithOverwritingQueryAttributes
142+
property foo : Symbol?
143+
property bar : Symbol?
144+
YAML.mapping({
145+
foo?: Bool,
146+
bar?: {type: Bool, default: false, presence: true, key: "is_bar"},
147+
})
148+
end
149+
134150
private class YAMLWithFinalize
135151
YAML.mapping({
136152
value: YAML::Any,
@@ -475,6 +491,42 @@ describe "YAML mapping" do
475491
end
476492
end
477493

494+
describe "with query attributes" do
495+
it "defines query getter" do
496+
yaml = YAMLWithQueryAttributes.from_yaml(%({"foo": true}))
497+
yaml.foo?.should be_true
498+
yaml.bar?.should be_false
499+
end
500+
501+
it "defines non-query setter and presence methods" do
502+
yaml = YAMLWithQueryAttributes.from_yaml(%({"foo": false}))
503+
yaml.bar_present?.should be_false
504+
yaml.bar = true
505+
yaml.bar?.should be_true
506+
end
507+
508+
it "maps non-query attributes" do
509+
yaml = YAMLWithQueryAttributes.from_yaml(%({"foo": false, "is_bar": false}))
510+
yaml.bar_present?.should be_true
511+
yaml.bar?.should be_false
512+
yaml.bar = true
513+
yaml.to_yaml.should eq(%(---\nfoo: false\nis_bar: true\n))
514+
end
515+
516+
it "raises if non-nilable attribute is nil" do
517+
ex = expect_raises YAML::ParseException, "Missing yaml attribute: foo" do
518+
YAMLWithQueryAttributes.from_yaml(%({"is_bar": true}))
519+
end
520+
ex.location.should eq({1, 1})
521+
end
522+
523+
it "overwrites non-query attributes" do
524+
yaml = YAMLWithOverwritingQueryAttributes.from_yaml(%({"foo": true}))
525+
typeof(yaml.@foo).should eq(Bool)
526+
typeof(yaml.@bar).should eq(Bool)
527+
end
528+
end
529+
478530
it "calls #finalize" do
479531
assert_finalizes(:yaml) { YAMLWithFinalize.from_yaml("---\nvalue: 1\n") }
480532
end

Diff for: ‎src/json/mapping.cr

+25-25
Original file line numberDiff line numberDiff line change
@@ -67,25 +67,29 @@ module JSON
6767
{% end %}
6868

6969
{% for key, value in _properties_ %}
70-
@{{key.id}} : {{value[:type]}} {{ (value[:nilable] ? "?" : "").id }}
70+
{% _properties_[key][:key_id] = key.id.gsub(/\?$/, "") %}
71+
{% end %}
72+
73+
{% for key, value in _properties_ %}
74+
@{{value[:key_id]}} : {{value[:type]}} {{ (value[:nilable] ? "?" : "").id }}
7175

7276
{% if value[:setter] == nil ? true : value[:setter] %}
73-
def {{key.id}}=(_{{key.id}} : {{value[:type]}} {{ (value[:nilable] ? "?" : "").id }})
74-
@{{key.id}} = _{{key.id}}
77+
def {{value[:key_id]}}=(_{{value[:key_id]}} : {{value[:type]}} {{ (value[:nilable] ? "?" : "").id }})
78+
@{{value[:key_id]}} = _{{value[:key_id]}}
7579
end
7680
{% end %}
7781

7882
{% if value[:getter] == nil ? true : value[:getter] %}
7983
def {{key.id}}
80-
@{{key.id}}
84+
@{{value[:key_id]}}
8185
end
8286
{% end %}
8387

8488
{% if value[:presence] %}
85-
@{{key.id}}_present : Bool = false
89+
@{{value[:key_id]}}_present : Bool = false
8690

87-
def {{key.id}}_present?
88-
@{{key.id}}_present
91+
def {{value[:key_id]}}_present?
92+
@{{value[:key_id]}}_present
8993
end
9094
{% end %}
9195
{% end %}
@@ -103,7 +107,7 @@ module JSON
103107
key = %pull.read_object_key
104108
case key
105109
{% for key, value in _properties_ %}
106-
when {{value[:key] || key.id.stringify}}
110+
when {{value[:key] || value[:key_id].stringify}}
107111
%found{key.id} = true
108112

109113
%var{key.id} =
@@ -141,45 +145,41 @@ module JSON
141145
{% for key, value in _properties_ %}
142146
{% unless value[:nilable] || value[:default] != nil %}
143147
if %var{key.id}.nil? && !%found{key.id} && !::Union({{value[:type]}}).nilable?
144-
raise ::JSON::ParseException.new("Missing json attribute: {{(value[:key] || key).id}}", *%location)
148+
raise ::JSON::ParseException.new("Missing json attribute: {{(value[:key] || value[:key_id]).id}}", *%location)
145149
end
146150
{% end %}
147-
{% end %}
148151

149-
{% for key, value in _properties_ %}
150152
{% if value[:nilable] %}
151153
{% if value[:default] != nil %}
152-
@{{key.id}} = %found{key.id} ? %var{key.id} : {{value[:default]}}
154+
@{{value[:key_id]}} = %found{key.id} ? %var{key.id} : {{value[:default]}}
153155
{% else %}
154-
@{{key.id}} = %var{key.id}
156+
@{{value[:key_id]}} = %var{key.id}
155157
{% end %}
156158
{% elsif value[:default] != nil %}
157-
@{{key.id}} = %var{key.id}.nil? ? {{value[:default]}} : %var{key.id}
159+
@{{value[:key_id]}} = %var{key.id}.nil? ? {{value[:default]}} : %var{key.id}
158160
{% else %}
159-
@{{key.id}} = (%var{key.id}).as({{value[:type]}})
161+
@{{value[:key_id]}} = (%var{key.id}).as({{value[:type]}})
160162
{% end %}
161-
{% end %}
162163

163-
{% for key, value in _properties_ %}
164164
{% if value[:presence] %}
165-
@{{key.id}}_present = %found{key.id}
165+
@{{value[:key_id]}}_present = %found{key.id}
166166
{% end %}
167167
{% end %}
168168
end
169169

170170
def to_json(json : ::JSON::Builder)
171171
json.object do
172172
{% for key, value in _properties_ %}
173-
_{{key.id}} = @{{key.id}}
173+
_{{value[:key_id]}} = @{{value[:key_id]}}
174174

175175
{% unless value[:emit_null] %}
176-
unless _{{key.id}}.nil?
176+
unless _{{value[:key_id]}}.nil?
177177
{% end %}
178178

179-
json.field({{value[:key] || key.id.stringify}}) do
179+
json.field({{value[:key] || value[:key_id].stringify}}) do
180180
{% if value[:root] %}
181181
{% if value[:emit_null] %}
182-
if _{{key.id}}.nil?
182+
if _{{value[:key_id]}}.nil?
183183
nil.to_json(json)
184184
else
185185
{% end %}
@@ -189,13 +189,13 @@ module JSON
189189
{% end %}
190190

191191
{% if value[:converter] %}
192-
if _{{key.id}}
193-
{{ value[:converter] }}.to_json(_{{key.id}}, json)
192+
if _{{value[:key_id]}}
193+
{{ value[:converter] }}.to_json(_{{value[:key_id]}}, json)
194194
else
195195
nil.to_json(json)
196196
end
197197
{% else %}
198-
_{{key.id}}.to_json(json)
198+
_{{value[:key_id]}}.to_json(json)
199199
{% end %}
200200

201201
{% if value[:root] %}

Diff for: ‎src/yaml/mapping.cr

+23-23
Original file line numberDiff line numberDiff line change
@@ -73,25 +73,29 @@ module YAML
7373
{% end %}
7474

7575
{% for key, value in _properties_ %}
76-
@{{key.id}} : {{value[:type]}} {{ (value[:nilable] ? "?" : "").id }}
76+
{% _properties_[key][:key_id] = key.id.gsub(/\?$/, "") %}
77+
{% end %}
78+
79+
{% for key, value in _properties_ %}
80+
@{{value[:key_id]}} : {{value[:type]}} {{ (value[:nilable] ? "?" : "").id }}
7781

7882
{% if value[:setter] == nil ? true : value[:setter] %}
79-
def {{key.id}}=(_{{key.id}} : {{value[:type]}} {{ (value[:nilable] ? "?" : "").id }})
80-
@{{key.id}} = _{{key.id}}
83+
def {{value[:key_id]}}=(_{{value[:key_id]}} : {{value[:type]}} {{ (value[:nilable] ? "?" : "").id }})
84+
@{{value[:key_id]}} = _{{value[:key_id]}}
8185
end
8286
{% end %}
8387

8488
{% if value[:getter] == nil ? true : value[:getter] %}
8589
def {{key.id}}
86-
@{{key.id}}
90+
@{{value[:key_id]}}
8791
end
8892
{% end %}
8993

9094
{% if value[:presence] %}
91-
@{{key.id}}_present : Bool = false
95+
@{{value[:key_id]}}_present : Bool = false
9296

93-
def {{key.id}}_present?
94-
@{{key.id}}_present
97+
def {{value[:key_id]}}_present?
98+
@{{value[:key_id]}}_present
9599
end
96100
{% end %}
97101
{% end %}
@@ -133,7 +137,7 @@ module YAML
133137

134138
case key
135139
{% for key, value in _properties_ %}
136-
when {{value[:key] || key.id.stringify}}
140+
when {{value[:key] || value[:key_id].stringify}}
137141
%found{key.id} = true
138142

139143
%var{key.id} =
@@ -168,46 +172,42 @@ module YAML
168172
{% for key, value in _properties_ %}
169173
{% unless value[:nilable] || value[:default] != nil %}
170174
if %var{key.id}.nil? && !%found{key.id} && !::Union({{value[:type]}}).nilable?
171-
node.raise "Missing yaml attribute: {{(value[:key] || key).id}}"
175+
node.raise "Missing yaml attribute: {{(value[:key] || value[:key_id]).id}}"
172176
end
173177
{% end %}
174-
{% end %}
175178

176-
{% for key, value in _properties_ %}
177179
{% if value[:nilable] %}
178180
{% if value[:default] != nil %}
179-
@{{key.id}} = %found{key.id} ? %var{key.id} : {{value[:default]}}
181+
@{{value[:key_id]}} = %found{key.id} ? %var{key.id} : {{value[:default]}}
180182
{% else %}
181-
@{{key.id}} = %var{key.id}
183+
@{{value[:key_id]}} = %var{key.id}
182184
{% end %}
183185
{% elsif value[:default] != nil %}
184-
@{{key.id}} = %var{key.id}.nil? ? {{value[:default]}} : %var{key.id}
186+
@{{value[:key_id]}} = %var{key.id}.nil? ? {{value[:default]}} : %var{key.id}
185187
{% else %}
186-
@{{key.id}} = %var{key.id}.as({{value[:type]}})
188+
@{{value[:key_id]}} = %var{key.id}.as({{value[:type]}})
187189
{% end %}
188-
{% end %}
189190

190-
{% for key, value in _properties_ %}
191191
{% if value[:presence] %}
192-
@{{key.id}}_present = %found{key.id}
192+
@{{value[:key_id]}}_present = %found{key.id}
193193
{% end %}
194194
{% end %}
195195
end
196196

197197
def to_yaml(%yaml : ::YAML::Nodes::Builder)
198198
%yaml.mapping(reference: self) do
199199
{% for key, value in _properties_ %}
200-
_{{key.id}} = @{{key.id}}
200+
_{{value[:key_id]}} = @{{value[:key_id]}}
201201

202-
unless _{{key.id}}.nil?
202+
unless _{{value[:key_id]}}.nil?
203203
# Key
204-
{{value[:key] || key.id.stringify}}.to_yaml(%yaml)
204+
{{value[:key] || value[:key_id].stringify}}.to_yaml(%yaml)
205205

206206
# Value
207207
{% if value[:converter] %}
208-
{{ value[:converter] }}.to_yaml(_{{key.id}}, %yaml)
208+
{{ value[:converter] }}.to_yaml(_{{value[:key_id]}}, %yaml)
209209
{% else %}
210-
_{{key.id}}.to_yaml(%yaml)
210+
_{{value[:key_id]}}.to_yaml(%yaml)
211211
{% end %}
212212
end
213213
{% end %}

0 commit comments

Comments
 (0)
Please sign in to comment.