Skip to content

Commit

Permalink
fix(Parser) support UTF-8 characters
Browse files Browse the repository at this point in the history
  • Loading branch information
rmosolgo committed Aug 15, 2015
1 parent 19ed3fb commit abbdd34
Show file tree
Hide file tree
Showing 5 changed files with 32 additions and 13 deletions.
13 changes: 11 additions & 2 deletions lib/graphql/language/parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,17 @@ class Parser < Parslet::Parser
rule(:value_input_object) { str("{") >> value_input_object_pair.repeat(1).as(:input_object) >> str("}") }
rule(:value_input_object_pair) { space? >> name.as(:input_object_name) >> space? >> str(":") >> space? >> value.as(:input_object_value) >> separator? }
rule(:value_int) { (value_sign? >> match('\d').repeat(1)).as(:int) }
# TODO: support unicode, escaped chars (match the spec)
rule(:value_string) { str('"') >> match('[^\"]').repeat.as(:string) >> str('"')}
rule(:value_string) { str('"') >> value_string_char.repeat.as(:string) >> str('"')}
rule(:value_string_char) { value_string_escaped_char | value_string_escaped_unicode | value_string_source_char}
rule(:value_string_escaped_char) { str("\\") >> match('["\/bfnrt]') }
rule(:value_string_escaped_unicode) { str("\\") >> match('u[\dA-Fa-f]{4}')}
rule(:value_string_source_char) { (str('"') | str("\\") | value_string_line_terminator).absent? >> any }
rule(:value_string_line_terminator) {
str('\u000A') | # new line
str('\u000D') | # carriage return
str('\u2028') | # line separator
str('\u2029') # paragraph separator
}
rule(:value_enum) { name.as(:enum) }
rule(:value_variable) { str("$") >> name.as(:variable) }

Expand Down
12 changes: 11 additions & 1 deletion lib/graphql/language/transform.rb
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,17 @@ def self.optional_sequence(name)
rule(input_object_name: simple(:n), input_object_value: sequence(:v)) { create_node(:Argument, name: n.to_s, value: v, position_source: n)}
rule(int: simple(:v)) { v.to_i }
rule(float: simple(:v)) { v.to_f }
rule(string: simple(:v)) { v.to_s }

ESCAPES = /\\(["\\\/bfnrt])/
UTF_8 = /\\u[\da-f]{4}/i
UTF_8_REPLACE = -> (m) { [m[-4..-1].to_i(16)].pack('U') }

rule(string: simple(:v)) {
string = v.to_s
string.gsub!(ESCAPES, '\1')
string.gsub!(UTF_8, &UTF_8_REPLACE)
string
}
rule(variable: simple(:v)) { create_node(:VariableIdentifier, name: v.to_s, position_source: v) }
rule(enum: simple(:v)) { create_node(:Enum, name: v.to_s, position_source: v)}
end
Expand Down
10 changes: 6 additions & 4 deletions spec/graphql/language/parser_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -59,15 +59,15 @@

it 'parses directives' do
assert(parser.directives.parse_with_debug("@doSomething"), 'gets without argument')
assert(parser.directives.parse_with_debug('@doSomething(why: "forSomeReason")'), 'gets with argument')
assert(parser.directives.parse_with_debug('@doSomething(why: "\"forSomeReason\"")'), 'gets with argument')
assert(parser.directives.parse_with_debug('@myFlag, @doSomething(why: "forSomeReason")'), 'gets multiple')
end

it 'parses fields' do
assert(parser.field.parse_with_debug(%|myField { name, id }|), 'gets subselections')
assert(parser.field.parse_with_debug(%{myAlias: myField}), 'gets an alias')
assert(parser.field.parse_with_debug(%{myField(intKey: 1, floatKey: 1.1e5)}), 'gets arguments')
assert(parser.field.parse_with_debug(%{myAlias: myField(stringKey: "my_string", boolKey: false, objKey: {key : true})}), 'gets alias and arguments')
assert(parser.field.parse_with_debug('myAlias: myField(stringKey: "\"my_string\"", boolKey: false, objKey: {key : true})'), 'gets alias and arguments')
assert(parser.field.parse_with_debug(%|myField @withFlag, @skip(if: true) { name, id }|), 'gets with directive')
end

Expand Down Expand Up @@ -100,8 +100,10 @@
end

it 'gets strings' do
assert(parser.value.parse_with_debug('"my string"'))
assert(parser.value.parse_with_debug('""'))
assert(parser.value.parse_with_debug('"my string"'), "plain strings")
assert(parser.value.parse_with_debug('""'), "empty strings")
assert(parser.value.parse_with_debug('"\"Hi!\"\n"'), "escaped strings")
assert(parser.value.parse_with_debug('"\u0025\u0026"'), "escaped unicode")
end

it 'gets arrays' do
Expand Down
8 changes: 4 additions & 4 deletions spec/graphql/language/transform_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -99,20 +99,20 @@ def get_result(query_string, parse: nil, debug: false)
assert_equal("SO_COOL", res.arguments[1].value.name)
assert_equal({"nice" => {"very" => true}}, res.arguments[2].value.to_h)

res = get_result(%|me @flag, @include(if: "something") {name, id}|, parse: :field)
res = get_result('me @flag, @include(if: "\"something\"") {name, id}', parse: :field)
assert_equal("me", res.name)
assert_equal(nil, res.alias)
assert_equal(2, res.directives.length)
assert_equal("flag", res.directives.first.name)
assert_equal("something", res.directives.last.arguments.first.value)
assert_equal('"something"', res.directives.last.arguments.first.value)
assert_equal(2, res.selections.length)
end

it 'transforms directives' do
res = get_result("@doSomething(vigorously: true)", parse: :directive)
res = get_result('@doSomething(vigorously: "\"true\u0025\"")', parse: :directive)
assert_equal("doSomething", res.name, 'gets the name without @')
assert_equal("vigorously", res.arguments.first.name)
assert_equal(true, res.arguments.first.value)
assert_equal('"true%"', res.arguments.first.value)

res = get_result("@someFlag", parse: :directive)
assert_equal("someFlag", res.name)
Expand Down
2 changes: 0 additions & 2 deletions spec/support/dairy_app.rb
Original file line number Diff line number Diff line change
Expand Up @@ -140,8 +140,6 @@ def self.create(type:, data:, id_type: !GraphQL::INT_TYPE)
input_field :values, !types[!types.Int]
end

p ReplaceValuesInputType.input_fields.inspect

MutationType = GraphQL::ObjectType.define do
name "Mutation"
description "The root for mutations in this schema"
Expand Down

0 comments on commit abbdd34

Please sign in to comment.