diff --git a/.gitignore b/.gitignore index a3ed95d..5ca1a3f 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,5 @@ nytprof.out *.bs /_eumm/ .DS_Store +/local/ +cpanfile.snapshot diff --git a/Changes b/Changes new file mode 100644 index 0000000..c34f9e9 --- /dev/null +++ b/Changes @@ -0,0 +1,6 @@ +Revision history for Perl extension graphql-perl + +{{$NEXT}} + + - original version + diff --git a/lib/GraphQL/Util.pm b/lib/GraphQL/Util.pm index eb96ea4..25dfc95 100644 --- a/lib/GraphQL/Util.pm +++ b/lib/GraphQL/Util.pm @@ -291,7 +291,7 @@ sub stringify { return ref($value) && $value == NULLISH ? 'null' - : ref($value) ? encode_json($value) + : ref($value) ? JSON->new->canonical(1)->encode($value) : qq'"$value"'; } diff --git a/lib/GraphQL/Validator/Rule/FieldsOnCorrectType.pm b/lib/GraphQL/Validator/Rule/FieldsOnCorrectType.pm index f4abf1c..9acc870 100644 --- a/lib/GraphQL/Validator/Rule/FieldsOnCorrectType.pm +++ b/lib/GraphQL/Validator/Rule/FieldsOnCorrectType.pm @@ -19,7 +19,7 @@ sub undefined_field_message { my $message = qq`Cannot query field "$field_name" on type "${ stringify_type($type) }".`; if ($suggested_type_names && @$suggested_type_names) { - my $suggestions = quoted_or_list($suggested_type_names); + my $suggestions = quoted_or_list([sort @$suggested_type_names]); $message .= " Did you mean to use an inline fragment on $suggestions?"; } elsif ($suggested_field_names && @$suggested_field_names) { diff --git a/minil.toml b/minil.toml new file mode 100644 index 0000000..86162c5 --- /dev/null +++ b/minil.toml @@ -0,0 +1,5 @@ +name = "GraphQL" + +# badges = ["travis"] +module_maker="ModuleBuildTiny" + diff --git a/t/graphql/execute-resolve.t b/t/graphql/execute-resolve.t index fb51f15..cd1451e 100644 --- a/t/graphql/execute-resolve.t +++ b/t/graphql/execute-resolve.t @@ -4,11 +4,12 @@ use warnings; use Test::More; use Test::Deep; -use JSON qw/encode_json/; -use GraphQL qw/graphql :types/; +use GraphQL qw/:types/; use GraphQL::Language::Parser qw/parse/; -use GraphQL::Execute qw/execute/; + +use lib "t/lib"; +use test_helper qw/graphql execute encode_json/; sub testSchema { my $testField = shift; @@ -103,7 +104,7 @@ subtest 'uses provided resolve function' => sub { }; is_deeply graphql($schema, '{ test(aInt: -123, aStr: "String!") }', 'Source!'), { - data => { test => '["Source!",{"aStr":"String!","aInt":-123}]' } + data => { test => '["Source!",{"aInt":-123,"aStr":"String!"}]' } }; }; diff --git a/t/graphql/execute-variables.t b/t/graphql/execute-variables.t index 5659367..d15a6fa 100644 --- a/t/graphql/execute-variables.t +++ b/t/graphql/execute-variables.t @@ -5,14 +5,15 @@ use warnings; use DDP; use Test::More; use Test::Deep; -use JSON qw/encode_json/; -use GraphQL qw/graphql :types/; +use GraphQL qw/:types/; use GraphQL::Nullish qw/NULLISH/; use GraphQL::Language::Parser qw/parse/; -use GraphQL::Execute qw/execute/; use GraphQL::Util qw/stringify/; +use lib "t/lib"; +use test_helper qw/graphql execute encode_json/; + my $TestComplexScalar = GraphQLScalarType( name => 'ComplexScalar', serialize => sub { @@ -362,10 +363,13 @@ EOQ # NOTE: Flunky because of unordered hashes cmp_deeply $e, noclass(superhashof({ locations => [{ line => 2, column => 19 }], - message => qq'Variable "\$input" got invalid value ${ \encode_json($params->{input}) }.' - . qq'\nIn field "nb": Expected "String!", found null.' - . qq'\nIn field "na": In field "c": Expected "String!", found null.' })); + + my $err_msgs = [sort split("\n", $e->{message})]; + is_deeply $err_msgs, [qq'In field "na": In field "c": Expected "String!", found null.', + qq'In field "nb": Expected "String!", found null.', + qq'Variable "\$input" got invalid value ${ \encode_json($params->{input}) }.']; + }; subtest 'errors on addition of unknown input field' => sub { diff --git a/t/graphql/type.t b/t/graphql/type.t index 0a6d855..8b5f14b 100644 --- a/t/graphql/type.t +++ b/t/graphql/type.t @@ -177,7 +177,7 @@ subtest 'defines an enum type with a value of `null` and `undefined`' => sub { }, ); - is_deeply $EnumTypeWithNullishValue->get_values, [ + is_deeply [sort {$a->{name} cmp $b->{name}} @{$EnumTypeWithNullishValue->get_values}], [sort {$a->{name} cmp $b->{name}} ( { name => 'NULL', description => undef, @@ -192,7 +192,7 @@ subtest 'defines an enum type with a value of `null` and `undefined`' => sub { deprecation_reason => undef, value => 'UNDEFINED', # XXX WAS value: undefined }, - ]; + )]; }; subtest 'defines an object type with deprecated field' => sub { diff --git a/t/graphql/type/introspection.t b/t/graphql/type/introspection.t index 62f8b04..0126004 100644 --- a/t/graphql/type/introspection.t +++ b/t/graphql/type/introspection.t @@ -1061,7 +1061,7 @@ subtest 'identifies deprecated fields'=> sub { } EOQ - is_deeply graphql($schema, $request), { + is_deeply sort_keys( graphql( $schema, $request ), [qw/fields/] ), { data => { __type => { name => 'TestType', @@ -1114,7 +1114,7 @@ subtest 'respects the includeDeprecated parameter for fields'=> sub { } EOQ - is_deeply graphql($schema, $request), { + is_deeply sort_keys( graphql( $schema, $request ), [qw/trueFields falseFields omittedFields/] ), { data => { __type => { name => 'TestType', @@ -1166,7 +1166,7 @@ subtest 'identifies deprecated enum values'=> sub { } EOQ - is_deeply graphql($schema, $request), { + is_deeply sort_keys( graphql( $schema, $request ), [qw/enum_values/] ), { data => { __type => { name => 'TestEnum', @@ -1229,7 +1229,7 @@ subtest 'respects the includeDeprecated parameter for enum values'=> sub { } EOQ - is_deeply graphql($schema, $request), { + is_deeply sort_keys( graphql( $schema, $request ), [qw/trueValues falseValues omittedValues/] ), { data => { __type => { name => 'TestEnum', @@ -1400,3 +1400,13 @@ EOQ }; done_testing; + +sub sort_keys { + my($result, $keys) = @_; + + foreach my $key (@$keys){ + $result->{data}->{__type}->{$key} = [sort {$b->{name} cmp $a->{name}} @{$result->{data}->{__type}->{$key}}]; + } + + return $result; +} \ No newline at end of file diff --git a/t/graphql/validator/rules/fields_on_correct_type.t b/t/graphql/validator/rules/fields_on_correct_type.t index 5f3d10f..00016ba 100644 --- a/t/graphql/validator/rules/fields_on_correct_type.t +++ b/t/graphql/validator/rules/fields_on_correct_type.t @@ -195,7 +195,7 @@ subtest 'Defined on implementors queried on union' => sub { name }', #TODO: NOTE: ORIG [undefined_field( 'name', 'CatOrDog', ['Being', 'Pet', 'Canine', 'Dog', 'Cat'], [], 3, 9)] - [undefined_field( 'name', 'CatOrDog', ['Pet', 'Being', 'Canine', 'Dog', 'Cat'], [], 3, 9)] + [undefined_field( 'name', 'CatOrDog', [sort ('Pet', 'Being', 'Canine', 'Dog', 'Cat')], [], 3, 9)] ); }; diff --git a/t/lib/test_helper.pm b/t/lib/test_helper.pm new file mode 100644 index 0000000..49bc717 --- /dev/null +++ b/t/lib/test_helper.pm @@ -0,0 +1,71 @@ +package test_helper; + +# this project's tests randomly fail comparisons due to the uncertain ordering of hash keys. +# test_helper is a shim between the existing tests and GraphQL-Perl as well as JSON to address this issue. +# +# the test_helper package wraps 'graphql' and the lower level 'execute' methods +# so that the response keys can be sorted prior to comparision testing. +# +# similarly 'encode_json' is provide so that JSON will sort before encoding. +# +# place the following in tests that need sorting support. +# +# use lib "t/lib"; +# use test_helper qw/graphql execute encode_json/; + +use strict; +use warnings; + +use Exporter qw/import/; + +use JSON qw//; +use GraphQL; +use GraphQL::Execute; + +our @ISA = qw(Exporter); +our @EXPORT_OK = ( + qw/ + graphql + execute + encode_json + / +); + +# enable sorted keys in JSON testing +my $json = JSON->new->canonical(1); + +sub encode_json { + $json->encode(shift); +} + +sub graphql { + return sort_response( GraphQL::graphql(@_) ); +} + +sub execute { + return sort_response( GraphQL::Execute::execute(@_) ); +} + +sub sort_response { + my $resp = shift; + + if ( 'HASH' eq ref $resp + && defined $resp->{data} + && 'HASH' eq ref $resp->{data} ) + { + foreach my $key ( keys %{ $resp->{data} } ) { + + # if data looks like json then lets try to parse and re-encode it sorted + if ( $resp->{data}->{$key} + && $resp->{data}->{$key} =~ m/^\{[^}]+\}/ ) + { + $resp->{data}->{$key} + = $json->encode( $json->decode( $resp->{data}->{$key} ) ); + } + } + } + + return $resp; +} + +1;