Skip to content

Commit

Permalink
Sanitize query property names in JSON/YAML.mapping (#5345)
Browse files Browse the repository at this point in the history
  • Loading branch information
Sija authored and RX14 committed Jan 5, 2018
1 parent 315d3c3 commit 890a355
Show file tree
Hide file tree
Showing 4 changed files with 152 additions and 48 deletions.
52 changes: 52 additions & 0 deletions spec/std/json/mapping_spec.cr
Expand Up @@ -159,6 +159,22 @@ private class JSONWithPresence
})
end

private class JSONWithQueryAttributes
JSON.mapping({
foo?: Bool,
bar?: {type: Bool, default: false, presence: true, key: "is_bar"},
})
end

private class JSONWithOverwritingQueryAttributes
property foo : Symbol?
property bar : Symbol?
JSON.mapping({
foo?: Bool,
bar?: {type: Bool, default: false, presence: true, key: "is_bar"},
})
end

describe "JSON mapping" do
it "parses person" do
person = JSONPerson.from_json(%({"name": "John", "age": 30}))
Expand Down Expand Up @@ -466,4 +482,40 @@ describe "JSON mapping" do
json.last_name_present?.should be_false
end
end

describe "with query attributes" do
it "defines query getter" do
json = JSONWithQueryAttributes.from_json(%({"foo": true}))
json.foo?.should be_true
json.bar?.should be_false
end

it "defines non-query setter and presence methods" do
json = JSONWithQueryAttributes.from_json(%({"foo": false}))
json.bar_present?.should be_false
json.bar = true
json.bar?.should be_true
end

it "maps non-query attributes" do
json = JSONWithQueryAttributes.from_json(%({"foo": false, "is_bar": false}))
json.bar_present?.should be_true
json.bar?.should be_false
json.bar = true
json.to_json.should eq(%({"foo":false,"is_bar":true}))
end

it "raises if non-nilable attribute is nil" do
ex = expect_raises JSON::ParseException, "Missing json attribute: foo" do
JSONWithQueryAttributes.from_json(%({"is_bar": true}))
end
ex.location.should eq({1, 1})
end

it "overwrites non-query attributes" do
json = JSONWithOverwritingQueryAttributes.from_json(%({"foo": true}))
typeof(json.@foo).should eq(Bool)
typeof(json.@bar).should eq(Bool)
end
end
end
52 changes: 52 additions & 0 deletions spec/std/yaml/mapping_spec.cr
Expand Up @@ -131,6 +131,22 @@ class YAMLRecursiveHash
})
end

private class YAMLWithQueryAttributes
YAML.mapping({
foo?: Bool,
bar?: {type: Bool, default: false, presence: true, key: "is_bar"},
})
end

private class YAMLWithOverwritingQueryAttributes
property foo : Symbol?
property bar : Symbol?
YAML.mapping({
foo?: Bool,
bar?: {type: Bool, default: false, presence: true, key: "is_bar"},
})
end

private class YAMLWithFinalize
YAML.mapping({
value: YAML::Any,
Expand Down Expand Up @@ -475,6 +491,42 @@ describe "YAML mapping" do
end
end

describe "with query attributes" do
it "defines query getter" do
yaml = YAMLWithQueryAttributes.from_yaml(%({"foo": true}))
yaml.foo?.should be_true
yaml.bar?.should be_false
end

it "defines non-query setter and presence methods" do
yaml = YAMLWithQueryAttributes.from_yaml(%({"foo": false}))
yaml.bar_present?.should be_false
yaml.bar = true
yaml.bar?.should be_true
end

it "maps non-query attributes" do
yaml = YAMLWithQueryAttributes.from_yaml(%({"foo": false, "is_bar": false}))
yaml.bar_present?.should be_true
yaml.bar?.should be_false
yaml.bar = true
yaml.to_yaml.should eq(%(---\nfoo: false\nis_bar: true\n))
end

it "raises if non-nilable attribute is nil" do
ex = expect_raises YAML::ParseException, "Missing yaml attribute: foo" do
YAMLWithQueryAttributes.from_yaml(%({"is_bar": true}))
end
ex.location.should eq({1, 1})
end

it "overwrites non-query attributes" do
yaml = YAMLWithOverwritingQueryAttributes.from_yaml(%({"foo": true}))
typeof(yaml.@foo).should eq(Bool)
typeof(yaml.@bar).should eq(Bool)
end
end

it "calls #finalize" do
assert_finalizes(:yaml) { YAMLWithFinalize.from_yaml("---\nvalue: 1\n") }
end
Expand Down
50 changes: 25 additions & 25 deletions src/json/mapping.cr
Expand Up @@ -67,25 +67,29 @@ module JSON
{% end %}

{% for key, value in _properties_ %}
@{{key.id}} : {{value[:type]}} {{ (value[:nilable] ? "?" : "").id }}
{% _properties_[key][:key_id] = key.id.gsub(/\?$/, "") %}
{% end %}

{% for key, value in _properties_ %}
@{{value[:key_id]}} : {{value[:type]}} {{ (value[:nilable] ? "?" : "").id }}

{% if value[:setter] == nil ? true : value[:setter] %}
def {{key.id}}=(_{{key.id}} : {{value[:type]}} {{ (value[:nilable] ? "?" : "").id }})
@{{key.id}} = _{{key.id}}
def {{value[:key_id]}}=(_{{value[:key_id]}} : {{value[:type]}} {{ (value[:nilable] ? "?" : "").id }})
@{{value[:key_id]}} = _{{value[:key_id]}}
end
{% end %}

{% if value[:getter] == nil ? true : value[:getter] %}
def {{key.id}}
@{{key.id}}
@{{value[:key_id]}}
end
{% end %}

{% if value[:presence] %}
@{{key.id}}_present : Bool = false
@{{value[:key_id]}}_present : Bool = false

def {{key.id}}_present?
@{{key.id}}_present
def {{value[:key_id]}}_present?
@{{value[:key_id]}}_present
end
{% end %}
{% end %}
Expand All @@ -103,7 +107,7 @@ module JSON
key = %pull.read_object_key
case key
{% for key, value in _properties_ %}
when {{value[:key] || key.id.stringify}}
when {{value[:key] || value[:key_id].stringify}}
%found{key.id} = true

%var{key.id} =
Expand Down Expand Up @@ -141,45 +145,41 @@ module JSON
{% for key, value in _properties_ %}
{% unless value[:nilable] || value[:default] != nil %}
if %var{key.id}.nil? && !%found{key.id} && !::Union({{value[:type]}}).nilable?
raise ::JSON::ParseException.new("Missing json attribute: {{(value[:key] || key).id}}", *%location)
raise ::JSON::ParseException.new("Missing json attribute: {{(value[:key] || value[:key_id]).id}}", *%location)
end
{% end %}
{% end %}

{% for key, value in _properties_ %}
{% if value[:nilable] %}
{% if value[:default] != nil %}
@{{key.id}} = %found{key.id} ? %var{key.id} : {{value[:default]}}
@{{value[:key_id]}} = %found{key.id} ? %var{key.id} : {{value[:default]}}
{% else %}
@{{key.id}} = %var{key.id}
@{{value[:key_id]}} = %var{key.id}
{% end %}
{% elsif value[:default] != nil %}
@{{key.id}} = %var{key.id}.nil? ? {{value[:default]}} : %var{key.id}
@{{value[:key_id]}} = %var{key.id}.nil? ? {{value[:default]}} : %var{key.id}
{% else %}
@{{key.id}} = (%var{key.id}).as({{value[:type]}})
@{{value[:key_id]}} = (%var{key.id}).as({{value[:type]}})
{% end %}
{% end %}

{% for key, value in _properties_ %}
{% if value[:presence] %}
@{{key.id}}_present = %found{key.id}
@{{value[:key_id]}}_present = %found{key.id}
{% end %}
{% end %}
end

def to_json(json : ::JSON::Builder)
json.object do
{% for key, value in _properties_ %}
_{{key.id}} = @{{key.id}}
_{{value[:key_id]}} = @{{value[:key_id]}}

{% unless value[:emit_null] %}
unless _{{key.id}}.nil?
unless _{{value[:key_id]}}.nil?
{% end %}

json.field({{value[:key] || key.id.stringify}}) do
json.field({{value[:key] || value[:key_id].stringify}}) do
{% if value[:root] %}
{% if value[:emit_null] %}
if _{{key.id}}.nil?
if _{{value[:key_id]}}.nil?
nil.to_json(json)
else
{% end %}
Expand All @@ -189,13 +189,13 @@ module JSON
{% end %}

{% if value[:converter] %}
if _{{key.id}}
{{ value[:converter] }}.to_json(_{{key.id}}, json)
if _{{value[:key_id]}}
{{ value[:converter] }}.to_json(_{{value[:key_id]}}, json)
else
nil.to_json(json)
end
{% else %}
_{{key.id}}.to_json(json)
_{{value[:key_id]}}.to_json(json)
{% end %}

{% if value[:root] %}
Expand Down
46 changes: 23 additions & 23 deletions src/yaml/mapping.cr
Expand Up @@ -73,25 +73,29 @@ module YAML
{% end %}

{% for key, value in _properties_ %}
@{{key.id}} : {{value[:type]}} {{ (value[:nilable] ? "?" : "").id }}
{% _properties_[key][:key_id] = key.id.gsub(/\?$/, "") %}
{% end %}

{% for key, value in _properties_ %}
@{{value[:key_id]}} : {{value[:type]}} {{ (value[:nilable] ? "?" : "").id }}

{% if value[:setter] == nil ? true : value[:setter] %}
def {{key.id}}=(_{{key.id}} : {{value[:type]}} {{ (value[:nilable] ? "?" : "").id }})
@{{key.id}} = _{{key.id}}
def {{value[:key_id]}}=(_{{value[:key_id]}} : {{value[:type]}} {{ (value[:nilable] ? "?" : "").id }})
@{{value[:key_id]}} = _{{value[:key_id]}}
end
{% end %}

{% if value[:getter] == nil ? true : value[:getter] %}
def {{key.id}}
@{{key.id}}
@{{value[:key_id]}}
end
{% end %}

{% if value[:presence] %}
@{{key.id}}_present : Bool = false
@{{value[:key_id]}}_present : Bool = false

def {{key.id}}_present?
@{{key.id}}_present
def {{value[:key_id]}}_present?
@{{value[:key_id]}}_present
end
{% end %}
{% end %}
Expand Down Expand Up @@ -133,7 +137,7 @@ module YAML

case key
{% for key, value in _properties_ %}
when {{value[:key] || key.id.stringify}}
when {{value[:key] || value[:key_id].stringify}}
%found{key.id} = true

%var{key.id} =
Expand Down Expand Up @@ -168,46 +172,42 @@ module YAML
{% for key, value in _properties_ %}
{% unless value[:nilable] || value[:default] != nil %}
if %var{key.id}.nil? && !%found{key.id} && !::Union({{value[:type]}}).nilable?
node.raise "Missing yaml attribute: {{(value[:key] || key).id}}"
node.raise "Missing yaml attribute: {{(value[:key] || value[:key_id]).id}}"
end
{% end %}
{% end %}

{% for key, value in _properties_ %}
{% if value[:nilable] %}
{% if value[:default] != nil %}
@{{key.id}} = %found{key.id} ? %var{key.id} : {{value[:default]}}
@{{value[:key_id]}} = %found{key.id} ? %var{key.id} : {{value[:default]}}
{% else %}
@{{key.id}} = %var{key.id}
@{{value[:key_id]}} = %var{key.id}
{% end %}
{% elsif value[:default] != nil %}
@{{key.id}} = %var{key.id}.nil? ? {{value[:default]}} : %var{key.id}
@{{value[:key_id]}} = %var{key.id}.nil? ? {{value[:default]}} : %var{key.id}
{% else %}
@{{key.id}} = %var{key.id}.as({{value[:type]}})
@{{value[:key_id]}} = %var{key.id}.as({{value[:type]}})
{% end %}
{% end %}

{% for key, value in _properties_ %}
{% if value[:presence] %}
@{{key.id}}_present = %found{key.id}
@{{value[:key_id]}}_present = %found{key.id}
{% end %}
{% end %}
end

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

unless _{{key.id}}.nil?
unless _{{value[:key_id]}}.nil?
# Key
{{value[:key] || key.id.stringify}}.to_yaml(%yaml)
{{value[:key] || value[:key_id].stringify}}.to_yaml(%yaml)

# Value
{% if value[:converter] %}
{{ value[:converter] }}.to_yaml(_{{key.id}}, %yaml)
{{ value[:converter] }}.to_yaml(_{{value[:key_id]}}, %yaml)
{% else %}
_{{key.id}}.to_yaml(%yaml)
_{{value[:key_id]}}.to_yaml(%yaml)
{% end %}
end
{% end %}
Expand Down

0 comments on commit 890a355

Please sign in to comment.