GraphQL-0.53/0000755000175000017500000000000014170415415012667 5ustar osboxesosboxesGraphQL-0.53/META.json0000644000175000017500000000436214170415415014315 0ustar osboxesosboxes{ "abstract" : "Perl implementation of GraphQL", "author" : [ "Ed J " ], "dynamic_config" : 0, "generated_by" : "ExtUtils::MakeMaker version 7.44, CPAN::Meta::Converter version 2.150010", "license" : [ "artistic_2" ], "meta-spec" : { "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", "version" : 2 }, "name" : "GraphQL", "no_index" : { "directory" : [ "t", "inc" ] }, "prereqs" : { "build" : { "requires" : { "ExtUtils::MakeMaker" : "0" } }, "configure" : { "requires" : { "ExtUtils::MakeMaker" : "6.64" } }, "develop" : { "requires" : { "Pod::Markdown" : "0", "Test::Pod" : "1.22", "Test::Pod::Coverage" : "1.08" } }, "runtime" : { "requires" : { "Attribute::Handlers" : "0", "DateTime" : "0", "DateTime::Format::ISO8601" : "0", "Devel::StrictMode" : "0", "Function::Parameters" : "2.001001", "Import::Into" : "1.002003", "JSON::MaybeXS" : "1.003009", "JSON::PP" : "2.92", "Module::Runtime" : "0", "Moo" : "0", "MooX::Thunking" : "0.07", "Pegex" : "0.64", "Return::Type" : "0", "Type::Tiny" : "0", "curry" : "0", "perl" : "5.014" }, "suggests" : { "Cpanel::JSON::XS" : "3.0237", "JSON::XS" : "0" } }, "test" : { "requires" : { "Test::Deep" : "1.127", "Test::Exception" : "0.42", "Test::More" : "0.88" } } }, "release_status" : "stable", "resources" : { "bugtracker" : { "web" : "https://github.com/graphql-perl/graphql-perl/issues" }, "license" : [ "http://dev.perl.org/licenses/" ], "repository" : { "type" : "git", "web" : "https://github.com/graphql-perl/graphql-perl" }, "x_IRC" : "irc://irc.perl.org/#graphql-perl" }, "version" : "0.53", "x_serialization_backend" : "JSON::PP version 4.04" } GraphQL-0.53/Dockerfile.prereq0000644000175000017500000000070613212704510016152 0ustar osboxesosboxes# prereqs builder # from https://docs.docker.com/get-started/part2/#dockerfile # stuff from https://hub.docker.com/_/perl/ # + https://github.com/perl/docker-perl/blob/master/5.026.001-64bit/Dockerfile FROM perl:5.26.1 # Copy the current directory contents into the container ADD . /opt/graphql-prereq/ # Install any needed packages - -v so can see errors RUN cd /opt/graphql-prereq \ && perl Makefile.PL \ && cpanm -v --installdeps . \ && true GraphQL-0.53/graphql.pgx0000644000175000017500000001314414006154243015045 0ustar osboxesosboxes# A simple grammar for GraphQL %grammar graphql %version 0.01 %include pegex-atoms # canonical: https://github.com/facebook/graphql/blob/master/spec/Appendix%20B%20--%20Grammar%20Summary.md # IDL RFC: https://github.com/facebook/graphql/pull/90 # inspiration drawn from the slightly obsolete https://github.com/antlr/grammars-v4/blob/master/graphql/GraphQL.g4 # string and number from https://github.com/ingydotnet/json-pgx/blob/master/json.pgx # in order to capture comments *before* a given rule-group *into* it, # can't suck up whitespace *after* rule-groups as part of them graphql: definition+ definition: (operationDefinition | fragment | typeSystemDefinition | .ws2) operationDefinition: selectionSet | operationType .(-) name? .(-) variableDefinitions? .(-) directives? selectionSet operationType: /(query|mutation|subscription)/ selectionSet: .(/- LCURLY -/) ( selection+ | `Expected name` ) .(/- RCURLY -/) selection: (field | inline_fragment | fragment_spread) .(-) field: alias? name .(-) arguments? .(-) directives? selectionSet? alias: name .(/- COLON -/) arguments: .(/- LPAREN -/) argument+ .(/- RPAREN -/) argument: name .(/- COLON -/) value fragment_spread: .spread fragmentName .(-) directives? inline_fragment: .spread typeCondition? .(-) directives? selectionSet fragment: .(/- 'fragment' -/) fragmentName .(-) (typeCondition | `Expected "on"`) .(-) directives? selectionSet fragmentName: ('on' `Unexpected Name "on"` | name) typeCondition: .(/'on' -/) namedType value: (variable | float | int | string | boolean | null | enumValue | listValue | objectValue) .(-) value_const: (float | int | string | boolean | null | enumValue | listValue_const | objectValue_const) .(-) boolean: /(true|false)/ null: /(null)/ enumValue: (/(true|false|null)/ `Invalid enum value` | name) listValue: .(/- LSQUARE -/) value* .(/- RSQUARE -/) listValue_const: .(/- LSQUARE -/) value_const* .(/- RSQUARE -/) objectValue: .(/- LCURLY -/) ( objectField+ | `Expected name` ) .(/- RCURLY -/) objectValue_const: .(/- LCURLY -/) ( objectField_const+ | `Expected name or constant` ) .(/- RCURLY -/) objectField: name .(/- COLON -/) value .(-) objectField_const: name .(/- COLON -/) value_const variableDefinitions: .(/- LPAREN -/) ( variableDefinition+ | `Expected $argument: Type` ) .(/- RPAREN -/) variableDefinition: variable .(/- COLON -/) typedef defaultValue? .(-) variable: .(/- DOLLAR/) name defaultValue: .(/- EQUAL -/) value_const # not "type" as using that for "objectTypeDefinition" typedef: nonNullType | namedType | listType namedType: name .(-) listType: LSQUARE typedef RSQUARE nonNullType: namedType /- BANG/ | listType /- BANG/ directives: directiveactual+ directiveactual: .(/- AT/) name arguments? string: blockStringValue | stringValue stringValue: / DOUBLE ( (: BACK (: # Backslash escapes [ DOUBLE # Double Quote BACK # Back Slash SLASH # Foreward Slash 'b' # Back Space 'f' # Form Feed 'n' # New Line 'r' # Carriage Return 't' # Horizontal Tab ] | 'u' HEX{4} # Unicode octet pair ) | [^ DOUBLE CONTROLS BACK ] # Anything else )* ) DOUBLE / blockStringValue: / DOUBLE DOUBLE DOUBLE ( (: (: BACK DOUBLE DOUBLE DOUBLE ) | [^ CONTROLS DOUBLE ] | [ TAB NL CR ] | (: DOUBLE (?!"") ) )* ) DOUBLE DOUBLE DOUBLE / float: /( DASH? (: 0 | [1-9] DIGIT* ) (: # one or other or both. not neither (: DOT DIGIT+ ) (: [eE] [ DASH PLUS ]? DIGIT+ ) | (: DOT DIGIT+ ) | (: [eE] [ DASH PLUS ]? DIGIT+ ) ) )/ int: /( DASH? (: 0 | [1-9] DIGIT* ) )/ name: /([ UNDER ALPHAS ] [ WORDS ]*)/ spread: /\.{3}/ .(-) ws: / (: WS | \x{FEFF} | COMMA | comment ) / comment: / BLANK* HASH BLANK* [^\r\n]* (: EOL | CR !NL | EOS ) / # CR is because MacOS 9 description: .(/ [ WS NL ]* /) string .(/ [ WS NL ]* /) | .(-) typeSystemDefinition: description? (schema | typeDefinition | typeExtensionDefinition | directive) schema: .(/'schema' -/) directives? ( .(/- LCURLY -/) operationTypeDefinition+ .(/- RCURLY /) )? operationTypeDefinition: operationType .(/- COLON -/) namedType .(-) typeDefinition: scalar | type | interface | union | enumTypeDefinition | input # aka scalarTypeDefinition scalar: .(/'scalar' -/) name .(-) directives? # aka objectTypeDefinition type: .(/'type' -/) name .(-) implementsInterfaces? .(-) directives? ( .(/- LCURLY /) fieldDefinition+ .(/- RCURLY /) )? implementsInterfaces: .(/'implements' -/) .(/- AMP -/)? (namedType+ % .(/- AMP -/)) fieldDefinition: description? name .(-) argumentsDefinition? .(/- COLON -/) typedef .(-) directives? argumentsDefinition: .(/- LPAREN /) inputValueDefinition+ .(/- RPAREN /) inputValueDefinition: description? name .(/- COLON -/) typedef .(-) defaultValue? .(-) directives? .(-) interface: .(/'interface' -/) name .(-) directives? ( .(/- LCURLY /) fieldDefinition+ .(/- RCURLY /) )? union: .(/'union' -/) name .(-) directives? ( .(/- EQUAL -/) unionMembers )? unionMembers: .(/- PIPE -/)? namedType+ % .(/- PIPE -/) enumTypeDefinition: .(/'enum' -/) name .(-) directives? ( .(/- LCURLY /) enumValueDefinition+ .(/- RCURLY /) )? enumValueDefinition: description? enumValue (.(-) directives)? input: .(/'input' -/) name .(-) directives? ( .(/- LCURLY /) inputValueDefinition+ .(/- RCURLY /) )? typeExtensionDefinition: .(/'extend' -/) (schema | typeDefinition) directive: .(/'directive' - AT -/) name .(-) argumentsDefinition? .(/- 'on' -/) directiveLocations directiveLocations: .(/- PIPE -/)? name+ % .(/- PIPE -/) GraphQL-0.53/t/0000755000175000017500000000000014170415414013131 5ustar osboxesosboxesGraphQL-0.53/t/execution-lists.t0000644000175000017500000001076014006154243016457 0ustar osboxesosboxesuse lib 't/lib'; use GQLTest; my $JSON = JSON::MaybeXS->new->allow_nonref->canonical; BEGIN { use_ok( 'GraphQL::Schema' ) || print "Bail out!\n"; use_ok( 'GraphQL::Type::Object' ) || print "Bail out!\n"; use_ok( 'GraphQL::Type::Scalar', qw($String $Int) ) || print "Bail out!\n"; use_ok( 'GraphQL::Execution', qw(execute) ) || print "Bail out!\n"; } sub check { my ($test_type, $test_data, $expected) = @_; local $Test::Builder::Level = $Test::Builder::Level + 1; my $data = +{ test => $test_data }; my $data_type; $data_type = GraphQL::Type::Object->new( name => 'DataType', fields => sub { +{ test => { type => $test_type }, nest => { type => $data_type, resolve => sub { $data } }, } }, ); my $schema = GraphQL::Schema->new(query => $data_type); run_test([$schema, '{ nest { test } }', $data], $expected); } sub rsv { FakePromise->resolve(@_) } sub rj { FakePromise->reject(@_) } sub all_checks { my ($type, $expected) = @_; my %expected = %$expected; subtest 'Array' => sub { subtest 'Contains values' => sub { check($type, [1, 2], $expected{contains_values}); }; subtest 'Contains null' => sub { check($type, [1, undef, 2], $expected{contains_null}); }; subtest 'Returns null' => sub { check($type, undef, $expected{returns_null}); }; }; subtest 'Promise>' => sub { subtest 'Contains values' => sub { check($type, rsv([1, 2]), $expected{contains_values}); }; subtest 'Contains null' => sub { check($type, rsv([1, undef, 2]), $expected{contains_null}); }; subtest 'Returns null' => sub { check($type, rsv(undef), $expected{returns_null}); }; subtest 'Rejected' => sub { check($type, rj("bad\n"), $expected{rejected}); }; }; subtest 'Array>' => sub { subtest 'Contains values' => sub { check($type, [rsv(1), rsv(2)], $expected{contains_values}); }; subtest 'Contains null' => sub { check($type, [map rsv($_), 1, undef, 2], $expected{contains_null}); }; subtest 'Contains rejected' => sub { check($type, [rsv(1), rj("bad\n"), rsv(2)], $expected{contains_rejected}); }; }; } my $data_ok = { nest => { test => [1, 2] } }; my $data_null_ok = { nest => { test => [1, undef, 2] } }; my $data_null0 = { nest => undef }; my $data_null1 = { nest => { test => undef } }; my $errors_null0 = [ { message => 'Cannot return null for non-nullable field DataType.test.', locations => [{line=>1, column=>15}], path => [qw(nest test)], }, ]; my $errors_null1 = [ { message => 'Cannot return null for non-nullable field DataType.test.', locations => [{line=>1, column=>15}], path => [qw(nest test 1)], }, ]; my $errors_bad0 = [ { message => "bad\n", locations => [{line=>1, column=>15}], path => [qw(nest test)] }, ]; my $errors_bad1 = [ { message => "bad\n", locations => [{line=>1, column=>15}], path => [qw(nest test 1)] }, ]; subtest '[T]' => sub { all_checks( $Int->list, { contains_values => { data => $data_ok }, contains_null => { data => $data_null_ok }, returns_null => { data => $data_null1 }, rejected => { data => $data_null1, errors => $errors_bad0 }, contains_rejected => { data => $data_null_ok, errors => $errors_bad1 }, }, ); }; subtest '[T]!' => sub { all_checks( $Int->list->non_null, { contains_values => { data => $data_ok }, contains_null => { data => $data_null_ok }, returns_null => { data => $data_null0, errors => $errors_null0 }, rejected => { data => $data_null0, errors => $errors_bad0 }, contains_rejected => { data => $data_null_ok, errors => $errors_bad1 }, }, ); }; subtest '[T!]' => sub { all_checks( $Int->non_null->list, { contains_values => { data => $data_ok }, contains_null => { data => $data_null1, errors => $errors_null1 }, returns_null => { data => $data_null1 }, rejected => { data => $data_null1, errors => $errors_bad0 }, contains_rejected => { data => $data_null1, errors => $errors_bad1 }, }, ); }; subtest '[T!]!' => sub { all_checks( $Int->non_null->list->non_null, { contains_values => { data => $data_ok }, contains_null => { data => $data_null0, errors => $errors_null1 }, returns_null => { data => $data_null0, errors => $errors_null0 }, rejected => { data => $data_null0, errors => $errors_bad0 }, contains_rejected => { data => $data_null0, errors => $errors_bad1 }, }, ); }; done_testing; GraphQL-0.53/t/util-buildschema.t0000644000175000017500000002723514070317453016565 0ustar osboxesosboxesuse lib 't/lib'; use GQLTest; my $JSON = JSON::MaybeXS->new->allow_nonref->canonical; BEGIN { use_ok( 'GraphQL::Schema' ) || print "Bail out!\n"; use_ok( 'GraphQL::Execution', qw(execute) ) || print "Bail out!\n"; use_ok( 'GraphQL::Language::Parser', qw(parse) ) || print "Bail out!\n"; } subtest 'can use built schema for limited execution' => sub { my $ast = parse(<<'EOF'); schema { query: Query } type Query { str: String } EOF my $schema = GraphQL::Schema->from_ast($ast); run_test( [$schema, '{ str }', { str => 123 }], { data => { str => '123' } }, ); }; subtest 'can build a schema directly from the source' => sub { my $doc = <<'EOF'; schema { query: Query } type Query { add(x: Int, y: Int): Int } EOF my $schema = GraphQL::Schema->from_doc($doc); run_test( [$schema, '{ add(x: 34, y: 55) }', {add => sub {$_[0]->{x} + $_[0]->{y}}}], { data => { add => 89 } }, ); }; subtest 'Simple type' => sub { my $doc = <<'EOF'; schema { query: HelloScalars } type HelloScalars { bool: Boolean float: Float id: ID int: Int str: String } EOF is(GraphQL::Schema->from_doc($doc)->to_doc, $doc); }; subtest 'With directives' => sub { my $doc = <<'EOF'; schema { query: Hello } directive @foo(arg: Int) on FIELD type Hello { str: String } EOF is(GraphQL::Schema->from_doc($doc)->to_doc, $doc); }; subtest 'Supports descriptions' => sub { my $doc = <<'EOF'; schema { query: Hello } "This is a directive" directive @foo( "It has an argument" arg: Int ) on FIELD "With an enum" enum Color { BLUE "Not a creative color" GREEN RED } "What a great type" type Hello { "And a field to boot" str: String } EOF is(GraphQL::Schema->from_doc($doc)->to_doc, $doc); }; subtest 'Maintains @skip & @include' => sub { my $doc = <<'EOF'; schema { query: Hello } type Hello { str: String } EOF my $schema = GraphQL::Schema->from_doc($doc); is_deeply $schema->directives, \@GraphQL::Directive::SPECIFIED_DIRECTIVES; }; subtest 'Overriding directives excludes specified' => sub { my $doc = <<'EOF'; schema { query: Hello } directive @skip on FIELD directive @include on FIELD directive @deprecated on FIELD_DEFINITION type Hello { str: String } EOF my $schema = GraphQL::Schema->from_doc($doc); is keys %{ $schema->name2directive }, 3; isnt $schema->name2directive->{skip}, $GraphQL::Directive::SKIP; isnt $schema->name2directive->{include}, $GraphQL::Directive::INCLUDE; isnt $schema->name2directive->{deprecated}, $GraphQL::Directive::DEPRECATED; }; subtest 'Adding directives maintains @skip & @include' => sub { my $doc = <<'EOF'; schema { query: Hello } directive @foo(arg: Int) on FIELD type Hello { str: String } EOF my $schema = GraphQL::Schema->from_doc($doc); is keys %{ $schema->name2directive }, 4; is $schema->name2directive->{skip}, $GraphQL::Directive::SKIP; is $schema->name2directive->{include}, $GraphQL::Directive::INCLUDE; is $schema->name2directive->{deprecated}, $GraphQL::Directive::DEPRECATED; }; subtest 'Type modifiers' => sub { my $doc = <<'EOF'; schema { query: HelloScalars } type HelloScalars { listOfNonNullStrs: [String!] listOfStrs: [String] nonNullListOfNonNullStrs: [String!]! nonNullListOfStrs: [String]! nonNullStr: String! } EOF is(GraphQL::Schema->from_doc($doc)->to_doc, $doc); }; subtest 'Recursive type' => sub { my $doc = <<'EOF'; schema { query: Recurse } type Recurse { recurse: Recurse str: String } EOF is(GraphQL::Schema->from_doc($doc)->to_doc, $doc); }; subtest 'Two types circular' => sub { my $doc = <<'EOF'; schema { query: TypeOne } type TypeOne { str: String typeTwo: TypeTwo } type TypeTwo { str: String typeOne: TypeOne } EOF is(GraphQL::Schema->from_doc($doc)->to_doc, $doc); }; subtest 'Single argument field' => sub { my $doc = <<'EOF'; schema { query: Hello } type Hello { booleanToStr(bool: Boolean): String floatToStr(float: Float): String idToStr(id: ID): String str(int: Int): String strToStr(bool: String): String } EOF is(GraphQL::Schema->from_doc($doc)->to_doc, $doc); }; subtest 'Simple type with multiple arguments' => sub { my $doc = <<'EOF'; schema { query: Hello } type Hello { str(bool: Boolean, int: Int): String } EOF is(GraphQL::Schema->from_doc($doc)->to_doc, $doc); }; subtest 'Simple type with interface' => sub { my $doc = <<'EOF'; schema { query: Hello } type Hello implements WorldInterface { str: String } interface WorldInterface { str: String } EOF is(GraphQL::Schema->from_doc($doc)->to_doc, $doc); }; subtest 'Simple output enum' => sub { my $doc = <<'EOF'; schema { query: OutputEnumRoot } enum Hello { WORLD } type OutputEnumRoot { hello: Hello } EOF is(GraphQL::Schema->from_doc($doc)->to_doc, $doc); }; subtest 'Simple input enum' => sub { my $doc = <<'EOF'; schema { query: InputEnumRoot } enum Hello { WORLD } type InputEnumRoot { str(hello: Hello): String } EOF is(GraphQL::Schema->from_doc($doc)->to_doc, $doc); }; subtest 'Multiple value enum' => sub { my $doc = <<'EOF'; schema { query: OutputEnumRoot } enum Hello { RLD WO } type OutputEnumRoot { hello: Hello } EOF is(GraphQL::Schema->from_doc($doc)->to_doc, $doc); }; subtest 'Simple Union' => sub { my $doc = <<'EOF'; schema { query: Root } union Hello = World type Root { hello: Hello } type World { str: String } EOF is(GraphQL::Schema->from_doc($doc)->to_doc, $doc); }; subtest 'Multiple Union' => sub { my $doc = <<'EOF'; schema { query: Root } union Hello = WorldOne | WorldTwo type Root { hello: Hello } type WorldOne { str: String } type WorldTwo { str: String } EOF is(GraphQL::Schema->from_doc($doc)->to_doc, $doc); }; subtest 'Custom Scalar' => sub { my $doc = <<'EOF'; schema { query: Root } scalar CustomScalar type Root { customScalar: CustomScalar } EOF is(GraphQL::Schema->from_doc($doc)->to_doc, $doc); }; subtest 'Input Object' => sub { my $doc = <<'EOF'; schema { query: Root } input Input { int: Int } type Root { field(in: Input): String } EOF is(GraphQL::Schema->from_doc($doc)->to_doc, $doc); }; subtest 'Simple argument field with default' => sub { my $doc = <<'EOF'; schema { query: Hello } type Hello { str(int: Int = 2): String } EOF is(GraphQL::Schema->from_doc($doc)->to_doc, $doc); }; subtest 'Simple type with mutation' => sub { my $doc = <<'EOF'; schema { query: HelloScalars mutation: Mutation } type HelloScalars { bool: Boolean int: Int str: String } type Mutation { addHelloScalars(bool: Boolean, int: Int, str: String): HelloScalars } EOF is(GraphQL::Schema->from_doc($doc)->to_doc, $doc); }; # typo faithfully preserved subtest 'Simple type with subscription' => sub { my $doc = <<'EOF'; schema { query: HelloScalars subscription: Subscription } type HelloScalars { bool: Boolean int: Int str: String } type Subscription { sbscribeHelloScalars(bool: Boolean, int: Int, str: String): HelloScalars } EOF is(GraphQL::Schema->from_doc($doc)->to_doc, $doc); }; subtest 'Unreferenced type implementing referenced interface' => sub { my $doc = <<'EOF'; type Concrete implements Iface { key: String } interface Iface { key: String } type Query { iface: Iface } EOF is(GraphQL::Schema->from_doc($doc)->to_doc, $doc); }; subtest 'Unreferenced type implementing referenced union' => sub { my $doc = <<'EOF'; type Concrete { key: String } type Query { union: Union } union Union = Concrete EOF is(GraphQL::Schema->from_doc($doc)->to_doc, $doc); }; subtest 'Supports @deprecated' => sub { my $doc = <<'EOF'; enum MyEnum { OLD_VALUE @deprecated OTHER_VALUE @deprecated(reason: "Terrible reasons") VALUE } type Query { enum: MyEnum field1: String @deprecated field2: Int @deprecated(reason: "Because I said so") } EOF is(GraphQL::Schema->from_doc($doc)->to_doc, $doc); }; subtest 'Supports directives on field' => sub { my $doc = <<'EOF'; input DirectiveInput { field: String @directive } interface DirectiveInterface { field: String @directive } type Query { field1: String @directive1 field2: Int @directive2(arg: true) } EOF is(GraphQL::Schema->from_doc($doc)->to_doc, $doc); }; # except it doesn't - just round-trip subtest 'Correctly assign AST nodes' => sub { my $doc = <<'EOF'; directive @test(arg: Int) on FIELD type Query { testField(testArg: TestInput): TestUnion } enum TestEnum { TEST_VALUE } input TestInput { testInputField: TestEnum } interface TestInterface { interfaceField: String } type TestType implements TestInterface { interfaceField: String } union TestUnion = TestType EOF is(GraphQL::Schema->from_doc($doc)->to_doc, $doc); }; subtest 'Requires a schema definition or Query type' => sub { throws_ok { GraphQL::Schema->from_doc(<<'EOF') } qr/Must provide schema definition with query type or a type named Query./; type Hello { bar: Bar } EOF }; subtest 'Allows only a single schema definition' => sub { throws_ok { GraphQL::Schema->from_doc(<<'EOF') } qr/Must provide only one schema definition./; schema { query: Hello } schema { query: Hello } type Hello { bar: Bar } EOF }; subtest 'Requires a query type' => sub { throws_ok { GraphQL::Schema->from_doc(<<'EOF') } qr/Must provide schema definition with query type or a type named Query./; schema { mutation: Hello } type Hello { bar: Bar } EOF }; subtest 'Allows only a single query type' => sub { throws_ok { GraphQL::Schema->from_doc(<<'EOF') } qr/Must provide only one query type in schema/; schema { query: Hello query: Yellow } type Hello { bar: Bar } type Yellow { isColor: Boolean } EOF }; subtest 'Allows only a single mutation type' => sub { throws_ok { GraphQL::Schema->from_doc(<<'EOF') } qr/Must provide only one mutation type in schema/; schema { query: Query mutation: Hello mutation: Yellow } type Hello { bar: Bar } type Yellow { isColor: Boolean } EOF }; subtest 'Allows only a single subscription type' => sub { throws_ok { GraphQL::Schema->from_doc(<<'EOF') } qr/Must provide only one subscription type in schema/; schema { query: Query subscription: Hello subscription: Yellow } type Hello { bar: Bar } type Yellow { isColor: Boolean } EOF }; subtest 'Unknown type referenced' => sub { throws_ok { GraphQL::Schema->from_doc(<<'EOF') } qr/Unknown type 'Bar'/; schema { query: Hello } type Hello { bar: Bar } EOF }; subtest 'Unknown type in interface list' => sub { throws_ok { GraphQL::Schema->from_doc(<<'EOF') } qr/Unknown type 'Bar'/; schema { query: Hello } type Hello implements Bar { bar: String } EOF }; subtest 'Unknown type in union list' => sub { throws_ok { GraphQL::Schema->from_doc(<<'EOF') } qr/Unknown type 'Bar'/; schema { query: Hello } union TestUnion = Bar type Hello { testUnion: TestUnion } EOF }; subtest 'Unknown query type' => sub { throws_ok { GraphQL::Schema->from_doc(<<'EOF') } qr/Specified query type 'Wat' not found/; schema { query: Wat } type Hello { str: String } EOF }; subtest 'Unknown mutation|subscription type' => sub { throws_ok { GraphQL::Schema->from_doc(< sub { throws_ok { GraphQL::Schema->from_doc(<<'EOF') } qr/Specified query type 'Foo' not found/; schema { query: Foo } query Foo { field } EOF }; subtest 'Does not consider fragment names' => sub { throws_ok { GraphQL::Schema->from_doc(<<'EOF') } qr/Specified query type 'Foo' not found/; schema { query: Foo } fragment Foo on Type { field } EOF }; subtest 'Forbids duplicate type definitions' => sub { throws_ok { GraphQL::Schema->from_doc(<<'EOF') } qr/Type 'Repeated' was defined more than once/; schema { query: Repeated } type Repeated { id: Int } type Repeated { id: String } EOF }; done_testing; GraphQL-0.53/t/00-load.t0000644000175000017500000000027613326440541014460 0ustar osboxesosboxesuse 5.014; use strict; use warnings; use Test::More; plan tests => 1; BEGIN { use_ok( 'GraphQL' ) || print "Bail out!\n"; } diag( "Testing GraphQL $GraphQL::VERSION, Perl $], $^X" ); GraphQL-0.53/t/execution-sync.t0000644000175000017500000000266214006154243016277 0ustar osboxesosboxesuse lib 't/lib'; use strict; use warnings; use GQLTest; my $JSON = JSON::MaybeXS->new->allow_nonref->canonical; BEGIN { use_ok( 'GraphQL::Schema' ) || print "Bail out!\n"; use_ok( 'GraphQL::Execution', qw(execute) ) || print "Bail out!\n"; use_ok( 'GraphQL::Type::Object' ) || print "Bail out!\n"; use_ok( 'GraphQL::Type::Scalar', qw($String) ) || print "Bail out!\n"; } my $schema = GraphQL::Schema->new( query => GraphQL::Type::Object->new( name => 'Query', fields => { syncField => { type => $String, resolve => sub { $_[0]; } }, asyncField => { type => $String, resolve => sub { my ($root_value, $args, $context, $info) = @_; $info->{promise_code}{resolve}->($root_value); } }, } ), ); subtest 'does not return a Promise for initial errors' => sub { run_test([ $schema, "fragment Example on Query { syncField }", 'rootValue', ], +{ errors => [ { message => "No operations supplied.\n", } ] }, 0); }; subtest 'does not return a Promise if fields are all synchronous' => sub { run_test([ $schema, "query Example { syncField }", 'rootValue', ], +{ data => { syncField => 'rootValue' } }, 0); }; subtest 'returns a Promise if any field is asynchronous' => sub { run_test([ $schema, "query Example { asyncField }", 'rootValue', ], +{ data => { asyncField => 'rootValue' } }, 1); }; done_testing; GraphQL-0.53/t/execution-execute.t0000644000175000017500000004741214070317276016777 0ustar osboxesosboxesuse lib 't/lib'; use GQLTest; use Devel::StrictMode; my $JSON = JSON::MaybeXS->new->allow_nonref->canonical; BEGIN { use_ok( 'GraphQL::Schema' ) || print "Bail out!\n"; use_ok( 'GraphQL::Type::Object' ) || print "Bail out!\n"; use_ok( 'GraphQL::Type::Scalar', qw($String $Int $Boolean) ) || print "Bail out!\n"; use_ok( 'GraphQL::Execution', qw(execute) ) || print "Bail out!\n"; use_ok( 'GraphQL::Language::Parser', qw(parse) ) || print "Bail out!\n"; } subtest 'throws if no document is provided' => sub { plan skip_all => 'Type check disabled' unless STRICT; my $schema = GraphQL::Schema->new( query => GraphQL::Type::Object->new( name => 'Type', fields => { a => { type => $String }, } ) ); throws_ok { execute($schema, undef) } qr/Undef did not pass type constraint/; }; subtest 'executes arbitrary code' => sub { my ($deep_data, $data); $data = { a => sub { 'Apple' }, b => sub { 'Banana' }, c => sub { 'Cookie' }, d => sub { 'Donut' }, e => sub { 'Egg' }, f => 'Fish', pic => sub { my $size = shift; return 'Pic of size: ' . ($size || 50); }, deep => sub { $deep_data }, promise => sub { FakePromise->resolve($data) }, }; $deep_data = { a => sub { 'Already Been Done' }, b => sub { 'Boring' }, c => sub { ['Contrived', undef, 'Confusing'] }, deeper => sub { [$data, undef, $data] } }; my ($DeepDataType, $DataType); $DataType = GraphQL::Type::Object->new( name => 'DataType', fields => sub { { a => { type => $String }, b => { type => $String }, c => { type => $String }, d => { type => $String }, e => { type => $String }, f => { type => $String }, pic => { args => { size => { type => $Int } }, type => $String, resolve => sub { my ($obj, $args) = @_; return $obj->{pic}->($args->{size}); } }, deep => { type => $DeepDataType }, promise => { type => $DataType }, } } ); $DeepDataType = GraphQL::Type::Object->new( name => 'DeepDataType', fields => { a => { type => $String }, b => { type => $String }, c => { type => $String->list }, deeper => { type => $DataType->list }, } ); my $schema = GraphQL::Schema->new( query => $DataType ); my $doc = <<'EOF'; query Example($size: Int) { a, b, x: c ...c f ...on DataType { pic(size: $size) promise { a } } deep { a b c deeper { a b } } } fragment c on DataType { d e } EOF my $ast = parse($doc); run_test([$schema, $ast, $data, undef, { size => 100 }, 'Example'], { data => { a => 'Apple', b => 'Banana', x => 'Cookie', d => 'Donut', e => 'Egg', f => 'Fish', pic => 'Pic of size: 100', promise => { a => 'Apple' }, deep => { a => 'Already Been Done', b => 'Boring', c => ['Contrived', undef, 'Confusing'], deeper => [ { a => 'Apple', b => 'Banana' }, undef, { a => 'Apple', b => 'Banana' }, ], }, }, }); }; subtest 'merges parallel fragments' => sub{ my $ast = parse(' { a, ...FragOne, ...FragTwo } fragment FragOne on Type { b deep { b, deeper: deep { b } } } fragment FragTwo on Type { c deep { c, deeper: deep { c } } } '); my $Type; $Type = GraphQL::Type::Object->new( name => 'Type', fields => sub { { a => { type => $String, resolve => sub { 'Apple' } }, b => { type => $String, resolve => sub { 'Banana' } }, c => { type => $String, resolve => sub { 'Cherry' } }, deep => { type => $Type, resolve => sub { {} } }, } }, ); my $schema = GraphQL::Schema->new(query => $Type); run_test([$schema, $ast], { data => { a => 'Apple', b => 'Banana', c => 'Cherry', deep => { b => 'Banana', c => 'Cherry', deeper => { b => 'Banana', c => 'Cherry' } } } }); }; subtest 'provides info about current execution state' => sub { my $ast = parse('query ($var: String) { result: test }'); my $info; my $schema = GraphQL::Schema->new( query => GraphQL::Type::Object->new( name => 'Test', fields => { test => { type => $String, resolve => sub { my ($val, $args, $ctx, $_info) = @_; $info = $_info; }, }, }, ) ); my $rootValue = { root => 'val' }; execute($schema, $ast, $rootValue, undef, { var => 123 }); is_deeply [sort keys %$info], [qw/ field_name field_nodes fragments operation parent_type path promise_code return_type root_value schema variable_values /]; is $info->{field_name}, 'test'; is scalar(@{ $info->{field_nodes} }), 1; is_deeply $info->{field_nodes}[0], $ast->[0]{selections}[0]; is $info->{return_type}->name, $String->name; is $info->{parent_type}, $schema->query; is_deeply $info->{path}, [ 'result' ]; is $info->{schema}, $schema; is $info->{root_value}, $rootValue; is $info->{operation}, $ast->[0]; is_deeply $info->{variable_values}, { var => {type => $String, value => '123'} }; }; subtest 'threads root value context correctly' => sub { my $doc = 'query Example { a }'; my $data = { context_thing => 'thing', }; my $resolved_root_value; my $schema = GraphQL::Schema->new( query => GraphQL::Type::Object->new( name => 'Type', fields => { a => { type => $String, resolve => sub { my ($root_value) = @_; $resolved_root_value = $root_value; }, }, }, ) ); execute($schema, parse($doc), $data); is $resolved_root_value->{context_thing}, 'thing'; }; subtest 'correctly threads arguments' => sub { my $doc = <<'EOF'; query Example { b(num_arg: 123, string_arg: "foo") } EOF my $resolved_args; my $schema = GraphQL::Schema->new( query => GraphQL::Type::Object->new( name => 'Type', fields => { b => { args => { num_arg => { type => $Int }, string_arg => { type => $String } }, type => $String, resolve => sub { my (undef, $args) = @_; $resolved_args = $args; } } } ) ); execute($schema, parse($doc)); is $resolved_args->{num_arg}, 123; is $resolved_args->{string_arg}, 'foo'; }; subtest 'nulls out error subtrees' => sub { my $doc = '{ sync syncError syncRawError syncReturnError syncReturnErrorList async # asyncReject - no because Perl no "Error" exception class asyncRawReject # asyncEmptyReject - no because now FakePromise uses die more # asyncError - no because Perl no "Error" exception class asyncRawError # asyncReturnError - no because Perl no "Error" exception class }'; my $data = { sync => sub { 'sync' }, syncError => sub { die "Error getting syncError\n" }, syncRawError => sub { die "Error getting syncRawError\n" }, syncReturnError => sub { GraphQL::Error->coerce('Error getting syncReturnError') }, syncReturnErrorList => sub { [ 'sync0', GraphQL::Error->coerce('Error getting syncReturnErrorList1'), 'sync2', GraphQL::Error->coerce('Error getting syncReturnErrorList3') ]; }, async => sub { FakePromise->resolve('async') }, asyncRawError => sub { FakePromise->resolve('')->then(sub { die "Error getting asyncRawError\n" }) }, asyncRawReject => sub { FakePromise->reject("Error getting asyncRawReject\n") }, }; my $ast = parse($doc); my $schema = GraphQL::Schema->new( query => GraphQL::Type::Object->new( name => 'Type', fields => { sync => { type => $String }, syncError => { type => $String }, syncRawError => { type => $String }, syncReturnError => { type => $String }, syncReturnErrorList => { type => $String->list }, async => { type => $String }, asyncRawReject => { type => $String }, asyncRawError => { type => $String }, } ) ); run_test([$schema, $ast, $data], { data => { sync => 'sync', syncError => undef, syncRawError => undef, syncReturnError => undef, syncReturnErrorList => ['sync0', undef, 'sync2', undef], async => 'async', asyncRawError => undef, asyncRawReject => undef, }, errors => bag( { 'locations' => [{ 'column' => 3, 'line' => 14 }], 'message' => "Error getting asyncRawError\n", 'path' => [ 'asyncRawError' ] }, { 'locations' => [{ 'column' => 5, 'line' => 12 }], 'message' => "Error getting asyncRawReject\n", 'path' => [ 'asyncRawReject' ] }, { message => "Error getting syncError\n", locations => [{ line => 4, column => 5 }], path => ['syncError'] }, { message => "Error getting syncRawError\n", locations => [{ line => 5, column => 5 }], path => ['syncRawError'] }, { message => "Error getting syncReturnError", locations => [{ line => 6, column => 5 }], path => ['syncReturnError'] }, { message => "Error getting syncReturnErrorList1", locations => [{ line => 7, column => 5 }], path => ['syncReturnErrorList', 1] }, { message => "Error getting syncReturnErrorList3", locations => [{ line => 7, column => 5 }], path => ['syncReturnErrorList', 3] }, ), }); }; subtest 'nulls error subtree for promise rejection #1071' => sub { my $doc = '{ foods { name } }'; my $ast = parse($doc); my $schema = GraphQL::Schema->new( query => GraphQL::Type::Object->new( name => 'Query', fields => { foods => { type => GraphQL::Type::Object->new( name => 'Food', fields => { name => { type => $String } }, )->list, resolve => sub { FakePromise->reject("Dangit\n") }, }, }, ) ); my $got = run_test([ $schema, $ast ], { data => { foods => undef }, errors => [ { 'locations' => [{ 'column' => 3, 'line' => 5 }], 'message' => "Dangit\n", 'path' => [ 'foods' ] }, ] }); }; subtest 'Full response path is included for non-nullable fields' => sub { my $A; $A = GraphQL::Type::Object->new( name => 'A', fields => sub { { nullableA => { type => $A, resolve => sub { {} }, }, nonNullA => { type => $A->non_null, resolve => sub { {} }, }, throws => { type => $String->non_null, resolve => sub { die GraphQL::Error->coerce('Catch me if you can') }, }, } }, ); my $queryType = GraphQL::Type::Object->new( name => 'query', fields => sub { { nullableA => { type => $A, resolve => sub { {} }, } } }, ); my $schema = GraphQL::Schema->new( query => $queryType, ); my $query = < { nullableA => { aliasedA => undef } }, errors => [{ message => 'Catch me if you can', locations => [{ line => 7, column => 9 }], path => ['nullableA', 'aliasedA', 'nonNullA', 'anotherA', 'throws'], }], }); }; subtest 'uses the inline operation if no operation name is provided' => sub { my $doc = '{ a }'; my $data = { a => 'b' }; my $ast = parse($doc); my $schema = GraphQL::Schema->new( query => GraphQL::Type::Object->new( name => 'Type', fields => { a => { type => $String }, } ) ); my $result = execute($schema, $ast, $data); is_deeply $result, { data => { a => 'b' } }; }; subtest 'uses the only operation if no operation name is provided' => sub { my $doc = 'query Example { a }'; my $data = { a => 'b' }; my $ast = parse($doc); my $schema = GraphQL::Schema->new( query => GraphQL::Type::Object->new( name => 'Type', fields => { a => { type => $String }, } ) ); my $result = execute($schema, $ast, $data); is_deeply $result, { data => { a => 'b' } }; }; subtest 'uses the named operation if operation name is provided' => sub { my $doc = 'query Example { first: a } query OtherExample { second: a }'; my $data = { a => 'b' }; my $ast = parse($doc); my $schema = GraphQL::Schema->new( query => GraphQL::Type::Object->new( name => 'Type', fields => { a => { type => $String }, } ) ); my $result = execute($schema, $ast, $data, undef, undef, 'OtherExample'); is_deeply $result, { data => { second => 'b' } }; }; subtest 'throws if no operation is provided' => sub { my $doc = 'fragment Example on Type { a }'; my $data = { a => 'b' }; my $ast = parse($doc); my $schema = GraphQL::Schema->new( query => GraphQL::Type::Object->new( name => 'Type', fields => { a => { type => $String }, } ) ); run_test([$schema, $ast, $data], { errors => [ { message => "No operations supplied.\n", } ], }); }; subtest 'throws if no operation name is provided with multiple operations' => sub { my $doc = 'query Example { a } query OtherExample { a }'; my $data = { a => 'b' }; my $ast = parse($doc); my $schema = GraphQL::Schema->new( query => GraphQL::Type::Object->new( name => 'Type', fields => { a => { type => $String }, } ) ); run_test([$schema, $ast, $data], { errors => [ { message => "Must provide operation name if query contains multiple operations.\n", } ], }); }; subtest 'throws if unknown operation name is provided' => sub { my $doc = 'query Example { a } query OtherExample { a }'; my $data = { a => 'b' }; my $ast = parse($doc); my $schema = GraphQL::Schema->new( query => GraphQL::Type::Object->new( name => 'Type', fields => { a => { type => $String }, } ) ); run_test([$schema, $ast, $data, undef, undef, 'UnknownExample'], { errors => [ { message => qq{No operations matching 'UnknownExample' found.\n}, } ], }); }; subtest 'uses the query schema for queries' => sub { my $doc = 'query Q { a } mutation M { c } subscription S { a }'; my $data = { a => 'b', c => 'd' }; my $ast = parse($doc); my $schema = GraphQL::Schema->new( query => GraphQL::Type::Object->new( name => 'Q', fields => { a => { type => $String }, } ), mutation => GraphQL::Type::Object->new( name => 'M', fields => { c => { type => $String }, } ), subscription => GraphQL::Type::Object->new( name => 'S', fields => { a => { type => $String }, } ) ); my $result = execute($schema, $ast, $data, undef, {}, 'Q'); is_deeply $result, { data => { a => 'b' } }; }; subtest 'uses the mutation schema for mutations' => sub { my $doc = 'query Q { a } mutation M { c }'; my $data = { a => 'b', c => 'd' }; my $ast = parse($doc); my $schema = GraphQL::Schema->new( query => GraphQL::Type::Object->new( name => 'Q', fields => { a => { type => $String }, } ), mutation => GraphQL::Type::Object->new( name => 'M', fields => { c => { type => $String }, } ) ); my $mutationResult = execute($schema, $ast, $data, undef, {}, 'M'); is_deeply $mutationResult, { data => { c => 'd' } }; }; subtest 'uses the subscription schema for subscriptions' => sub { my $doc = 'query Q { a } subscription S { a }'; my $data = { a => 'b', c => 'd' }; my $ast = parse($doc); my $schema = GraphQL::Schema->new( query => GraphQL::Type::Object->new( name => 'Q', fields => { a => { type => $String }, } ), subscription => GraphQL::Type::Object->new( name => 'S', fields => { a => { type => $String }, } ) ); my $subscription_result = execute($schema, $ast, $data, undef, {}, 'S'); is_deeply $subscription_result, { data => { a => 'b' } }; }; subtest 'Avoids recursion' => sub { my $doc = ' query Q { a ...Frag ...Frag } fragment Frag on Type { a, ...Frag } '; my $data = { a => 'b' }; my $ast = parse($doc); my $schema = GraphQL::Schema->new( query => GraphQL::Type::Object->new( name => 'Type', fields => { a => { type => $String }, } ), ); my $queryResult = execute($schema, $ast, $data, undef, {}, 'Q'); is_deeply $queryResult, { data => { a => 'b' } }; }; subtest 'does not include illegal fields in output' => sub { my $doc = 'mutation M { thisIsIllegalDontIncludeMe }'; my $ast = parse($doc); my $schema = GraphQL::Schema->new( query => GraphQL::Type::Object->new( name => 'Q', fields => { a => { type => $String }, } ), mutation => GraphQL::Type::Object->new( name => 'M', fields => { c => { type => $String }, } ), ); run_test([$schema, $ast], { data => undef }); }; subtest 'does not include arguments that were not set' => sub { my $schema = GraphQL::Schema->new( query => GraphQL::Type::Object->new( name => 'Type', fields => { field => { type => $String, resolve => sub { my ($data, $args) = @_; return $JSON->encode($args); }, args => { a => { type => $Boolean }, b => { type => $Boolean }, c => { type => $Boolean }, d => { type => $Int }, e => { type => $Int }, }, } } ) ); my $query = parse('{ field(a: true, c: false, e: 0) }'); run_test([$schema, $query], { data => { field => '{"a":1,"c":0,"e":0}' } }); }; subtest 'fails when an is_type_of check is not met' => sub { { package Special; sub new { my ($class, $value) = @_; return bless { value => $value }, $class; } sub value { shift->{value} } package NotSpecial; sub new { my ($class, $value) = @_; return bless { value => $value }, $class; } sub value { shift->{value} } } my $SpecialType = GraphQL::Type::Object->new( name => 'SpecialType', is_type_of => sub { my $obj = shift; return $obj->isa('Special'); }, fields => { value => { type => $String } } ); my $schema = GraphQL::Schema->new( query => GraphQL::Type::Object->new( name => 'Query', fields => { specials => { type => $SpecialType->list, resolve => sub { my $root_value = shift; return $root_value->{specials}; } } } ) ); my $query = parse('{ specials { value } }'); my $value = { specials => [Special->new('foo'), NotSpecial->new('bar')] }; run_test([$schema, $query, $value], { data => { specials => [ { value => 'foo' }, undef, ], }, errors => [ { message => "Expected a value of type 'SpecialType' but received: 'NotSpecial'.", locations => [{ line => 1, column => 22 }], path => [ 'specials', 1 ], } ], }); }; subtest 'fails to execute a query containing a type definition' => sub { my $query = parse(' { foo } type Query { foo: String } '); my $schema = GraphQL::Schema->new( query => GraphQL::Type::Object->new( name => 'Query', fields => { foo => { type => $String } } ) ); run_test([$schema, $query], { errors => [ { message => "Can only execute document containing fragments or operations\n", } ], }); }; done_testing; GraphQL-0.53/t/execution-nonnull.t0000644000175000017500000002506714006154243017014 0ustar osboxesosboxesuse lib 't/lib'; use GQLTest; my $JSON = JSON::MaybeXS->new->allow_nonref->canonical; BEGIN { use_ok( 'GraphQL::Schema' ) || print "Bail out!\n"; use_ok( 'GraphQL::Type::Object' ) || print "Bail out!\n"; use_ok( 'GraphQL::Type::Scalar', qw($String $Int) ) || print "Bail out!\n"; use_ok( 'GraphQL::Execution', qw(execute) ) || print "Bail out!\n"; } my $throwing_data; $throwing_data = { sync => sub { die "error\n" }, promise => sub { FakePromise->resolve("error\n")->then(sub { die shift }) }, syncNonNull => sub { die "nonNullError\n" }, promiseNonNull => sub { FakePromise->resolve("nonNullError\n")->then(sub { die shift }) }, syncNest => sub { $throwing_data }, promiseNest => sub { FakePromise->resolve($throwing_data) }, syncNonNullNest => sub { $throwing_data }, promiseNonNullNest => sub { FakePromise->resolve($throwing_data) }, }; my $nulling_data; $nulling_data = { sync => sub { undef }, promise => sub { FakePromise->resolve(undef) }, syncNonNull => sub { undef }, promiseNonNull => sub { FakePromise->resolve(undef) }, syncNest => sub { $nulling_data }, promiseNest => sub { FakePromise->resolve($nulling_data) }, syncNonNullNest => sub { $nulling_data }, promiseNonNullNest => sub { FakePromise->resolve($nulling_data) }, }; my $data_type; $data_type = GraphQL::Type::Object->new( name => 'DataType', fields => sub { +{ sync => { type => $String }, promise => { type => $String }, syncNonNull => { type => $String->non_null }, promiseNonNull => { type => $String->non_null }, syncNest => { type => $data_type }, promiseNest => { type => $data_type }, syncNonNullNest => { type => $data_type->non_null }, promiseNonNullNest => { type => $data_type->non_null }, } }, ); my $schema = GraphQL::Schema->new(query => $data_type); # sync_only does not touch *Nest sub check { my ($doc, $sync_only, $expected_return, $expected_throw) = @_; local $Test::Builder::Level = $Test::Builder::Level + 1; my @descs = ([ { doc => $doc, words => 'returns null', data => $nulling_data, expected => $expected_return, sync => 'synchronously', }, { doc => $doc, words => 'throws', data => $throwing_data, expected => +{ %$expected_return, %$expected_throw }, sync => 'synchronously', }, ]); # if (!$sync_only) for my $d (@descs) { for my $d2 (@$d) { subtest "$d2->{words} $d2->{sync}" => sub { run_test([$schema, $d2->{doc}, $d2->{data}], $d2->{expected}); }; } } } subtest 'nulls a nullable field' => sub { check('query Q { sync }', 0, { data => { sync => undef }, }, { errors => [ { message => "error\n", locations => [{ line=>1, column=>16 }], path => [qw(sync)], } ], }); }; subtest 'nulls a synchronously returned object that contains a non-nullable field' => sub { check('query Q { syncNest { syncNonNull } }', 0, { data => { syncNest => undef }, errors => [ { message => 'Cannot return null for non-nullable field DataType.syncNonNull.', locations => [{ line => 1, column => 34 }], path => [qw(syncNest syncNonNull)], }, ], }, { errors => [ { message => "nonNullError\n", locations => [{ line=>1, column=>34 }], path => [qw(syncNest syncNonNull)], } ], }); }; subtest 'nulls an object returned in a promise that contains a non-nullable field' => sub { check('query Q { promiseNest { syncNonNull } }', 0, { data => { promiseNest => undef }, errors => [ { message => 'Cannot return null for non-nullable field DataType.syncNonNull.', locations => [{ line=>1, column=>37 }], path => [qw(promiseNest syncNonNull)], }, ], }, { errors => [ { message => "nonNullError\n", locations => [{ line=>1, column=>37 }], path => [qw(promiseNest syncNonNull)], } ], }); }; subtest 'nulls a complex tree of nullable fields' => sub { my $doc = <<'EOF'; query Q { syncNest { sync promise syncNest { sync promise } promiseNest { sync promise } } promiseNest { sync promise syncNest { sync promise } promiseNest { sync promise } } } EOF check($doc, 1, { data => { syncNest => { sync => undef, promise => undef, syncNest => { sync => undef, promise => undef, }, promiseNest => { sync => undef, promise => undef, }, }, promiseNest => { sync => undef, promise => undef, syncNest => { sync => undef, promise => undef, }, promiseNest => { sync => undef, promise => undef, }, }, }, }, { errors => bag( { message => "error\n", locations => [{ line => 17, column => 5 }], path => [qw(promiseNest promise)], }, { message => "error\n", locations => [{ line => 24, column => 5 }], path => [qw(promiseNest promiseNest promise)], }, { message => "error\n", locations => [{ line => 23, column => 7 }], path => [qw(promiseNest promiseNest sync)], }, { message => "error\n", locations => [{ line => 16, column => 5 }], path => [qw(promiseNest sync)], }, { message => "error\n", locations => [{ line => 20, column => 5 }], path => [qw(promiseNest syncNest promise)], }, { message => "error\n", locations => [{ line => 5, column => 5 }], path => [qw(syncNest promise)], }, { message => "error\n", locations => [{ line => 19, column => 7 }], path => [qw(promiseNest syncNest sync)], }, { message => "error\n", locations => [{ line => 12, column => 5 }], path => [qw(syncNest promiseNest promise)], }, { message => "error\n", locations => [{ line => 11, column => 7 }], path => [qw(syncNest promiseNest sync)], }, { message => "error\n", locations => [{ line => 4, column => 5 }], path => [qw(syncNest sync)], }, { message => "error\n", locations => [{ line => 8, column => 5 }], path => [qw(syncNest syncNest promise)], }, { message => "error\n", locations => [{ line => 7, column => 7 }], path => [qw(syncNest syncNest sync)], }, ), }); }; subtest 'nulls the first nullable object after a field in a long chain of non-null fields' => sub { my $doc = <<'EOF'; query Q { syncNest { syncNonNullNest { promiseNonNullNest { syncNonNullNest { promiseNonNullNest { syncNonNull } } } } } promiseNest { syncNonNullNest { promiseNonNullNest { syncNonNullNest { promiseNonNullNest { syncNonNull } } } } } anotherNest: syncNest { syncNonNullNest { promiseNonNullNest { syncNonNullNest { promiseNonNullNest { promiseNonNull } } } } } anotherPromiseNest: promiseNest { syncNonNullNest { promiseNonNullNest { syncNonNullNest { promiseNonNullNest { promiseNonNull } } } } } } EOF check($doc, 1, { data => { syncNest => undef, promiseNest => undef, anotherNest => undef, anotherPromiseNest => undef, }, errors => bag( { 'locations' => [ { 'column' => 11, 'line' => 30 } ], 'message' => 'Cannot return null for non-nullable field DataType.promiseNonNull.', 'path' => [ 'anotherNest', 'syncNonNullNest', 'promiseNonNullNest', 'syncNonNullNest', 'promiseNonNullNest', 'promiseNonNull' ] }, { 'locations' => [ { 'column' => 11, 'line' => 41 } ], 'message' => 'Cannot return null for non-nullable field DataType.promiseNonNull.', 'path' => [ 'anotherPromiseNest', 'syncNonNullNest', 'promiseNonNullNest', 'syncNonNullNest', 'promiseNonNullNest', 'promiseNonNull' ] }, { 'locations' => [ { 'column' => 11, 'line' => 19 } ], 'message' => 'Cannot return null for non-nullable field DataType.syncNonNull.', 'path' => [ 'promiseNest', 'syncNonNullNest', 'promiseNonNullNest', 'syncNonNullNest', 'promiseNonNullNest', 'syncNonNull' ] }, { 'locations' => [ { 'column' => 11, 'line' => 8 } ], 'message' => 'Cannot return null for non-nullable field DataType.syncNonNull.', 'path' => [ 'syncNest', 'syncNonNullNest', 'promiseNonNullNest', 'syncNonNullNest', 'promiseNonNullNest', 'syncNonNull' ], }, ), }, { errors => bag( { message => "nonNullError\n", locations => [{ line=>19, column=>11 }], path => [qw(promiseNest syncNonNullNest promiseNonNullNest syncNonNullNest promiseNonNullNest syncNonNull)], }, { message => "nonNullError\n", locations => [{ line => 41, column => 11 }], path => [qw(anotherPromiseNest syncNonNullNest promiseNonNullNest syncNonNullNest promiseNonNullNest promiseNonNull)], }, { message => "nonNullError\n", locations => [{ line => 8, column => 11 }], path => [qw(syncNest syncNonNullNest promiseNonNullNest syncNonNullNest promiseNonNullNest syncNonNull)], }, { message => "nonNullError\n", locations => [{ line => 30, column => 11 }], path => [qw(anotherNest syncNonNullNest promiseNonNullNest syncNonNullNest promiseNonNullNest promiseNonNull)], }, ), }); }; subtest 'nulls the top level if non-nullable field' => sub { check('query Q { syncNonNull }', 0, { data => undef, errors => [ { message => 'Cannot return null for non-nullable field DataType.syncNonNull.', locations => [{ line=>1, column=>23 }], path => [qw(syncNonNull)], } ], }, +{ errors => [ { message => "nonNullError\n", locations => [{ line=>1, column=>23 }], path => [qw(syncNonNull)], }, ], }); }; done_testing; GraphQL-0.53/t/execution-schema.t0000644000175000017500000001011213217121260016544 0ustar osboxesosboxesuse lib 't/lib'; use GQLTest; # differences from js version: # no try to treat true as string # no include bad field names as perl version violently rejects BEGIN { use_ok( 'GraphQL::Schema' ) || print "Bail out!\n"; use_ok( 'GraphQL::Type::Object' ) || print "Bail out!\n"; use_ok( 'GraphQL::Type::Scalar', qw($String $Int $Boolean $ID) ) || print "Bail out!\n"; use_ok( 'GraphQL::Execution', qw(execute) ) || print "Bail out!\n"; } my $BlogImage = GraphQL::Type::Object->new( name => 'Image', fields => { url => { type => $String }, width => { type => $Int }, height => { type => $Int }, }, ); my $BlogArticle; my $BlogAuthor = GraphQL::Type::Object->new( name => 'Author', fields => sub { { id => { type => $String }, name => { type => $String }, pic => { args => { width => { type => $Int }, height => { type => $Int } }, type => $BlogImage, resolve => sub { my ($obj, $args) = @_; $obj->{pic}->($args->{width}, $args->{height}); }, }, recentArticle => { type => $BlogArticle }, } }, ); $BlogArticle = GraphQL::Type::Object->new( name => 'Article', fields => { id => { type => $String->non_null }, isPublished => { type => $Boolean }, author => { type => $BlogAuthor }, title => { type => $String }, body => { type => $String }, keywords => { type => $String->list }, }, ); my $BlogQuery = GraphQL::Type::Object->new( name => 'Query', fields => { article => { type => $BlogArticle, args => { id => { type => $ID } }, resolve => sub { my (undef, $args) = @_; article($args->{id}); }, }, feed => { type => $BlogArticle->list, resolve => sub { [ map article($_), (1..10) ] }, }, }, ); my $schema = GraphQL::Schema->new(query => $BlogQuery); my $johnSmith = { id => 123, name => 'John Smith', pic => sub { get_pic(123, shift, shift) }, recentArticle => article(1), }; sub article { my ($id) = @_; { id => $id, isPublished => 1, author => $johnSmith, title => "My Article $id", body => 'This is a post', hidden => 'This data is not exposed in the schema', keywords => [ 'foo', 'bar', 1, undef ], }; } sub get_pic { my ($uid, $width, $height) = @_; { url => "cdn://$uid", width => $width, height => $height }; } my $doc = '{ feed { id, title }, article(id: "1") { ...articleFields, author { id, name, pic(width: 640, height: 480) { url, width, height }, recentArticle { ...articleFields, keywords } } } } fragment articleFields on Article { id, isPublished, title, body, }'; subtest 'executes using a schema', sub { run_test( [ $schema, $doc ], { data => { feed => [ { id => '1', title => 'My Article 1' }, { id => '2', title => 'My Article 2' }, { id => '3', title => 'My Article 3' }, { id => '4', title => 'My Article 4' }, { id => '5', title => 'My Article 5' }, { id => '6', title => 'My Article 6' }, { id => '7', title => 'My Article 7' }, { id => '8', title => 'My Article 8' }, { id => '9', title => 'My Article 9' }, { id => '10', title => 'My Article 10' } ], article => { id => '1', isPublished => JSON->true, title => 'My Article 1', body => 'This is a post', author => { id => '123', name => 'John Smith', pic => { url => 'cdn://123', width => 640, height => 480, }, recentArticle => { id => '1', isPublished => JSON->true, title => 'My Article 1', body => 'This is a post', keywords => [ 'foo', 'bar', '1', undef ], } } } } }, ); done_testing; }; done_testing; GraphQL-0.53/t/subscribe.t0000644000175000017500000002352514006154243015304 0ustar osboxesosboxesuse lib 't/lib'; use strict; use warnings; use GQLTest; my $JSON = JSON::MaybeXS->new->allow_nonref->canonical; BEGIN { use_ok( 'GraphQL::Schema' ) || print "Bail out!\n"; use_ok( 'GraphQL::Subscription', qw(subscribe) ) || print "Bail out!\n"; use_ok( 'GraphQL::Type::Object' ) || print "Bail out!\n"; use_ok( 'GraphQL::Type::Scalar', qw($String $Boolean $Int) ) || print "Bail out!\n"; use_ok( 'GraphQL::Language::Parser', qw(parse) ) || print "Bail out!\n"; use_ok( 'GraphQL::PubSub' ) || print "Bail out!\n"; use_ok( 'curry' ) || print "Bail out!\n"; } my $EmailType = GraphQL::Type::Object->new( name => 'Email', fields => { from => { type => $String }, subject => { type => $String }, message => { type => $String }, unread => { type => $Boolean }, }, ); my $InboxType = GraphQL::Type::Object->new( name => 'Inbox', fields => { total => { type => $Int, resolve => sub { scalar @{$_[0]->{emails}} }, }, unread => { type => $Int, resolve => sub { grep $_->{unread}, @{$_[0]->{emails}} }, }, emails => { type => $EmailType->list }, }, ); my $QueryType = GraphQL::Type::Object->new( name => 'Query', fields => { inbox => { type => $InboxType }, }, ); my $EmailEventType = GraphQL::Type::Object->new( name => 'EmailEvent', fields => { inbox => { type => $InboxType }, email => { type => $EmailType }, }, ); my $emailSchema = emailSchemaWithResolvers(); sub emailSchemaWithResolvers { my ($subscribeFn, $resolveFn) = @_; GraphQL::Schema->new( query => $QueryType, subscription => GraphQL::Type::Object->new( name => 'Subscription', fields => { importantEmail => { type => $EmailEventType, $resolveFn ? (resolve => $resolveFn) : (), $subscribeFn ? (subscribe => $subscribeFn) : (), args => { priority => { type => $Int }, }, }, } ), ); } my $defaultSubscriptionAST = parse(' subscription ($priority: Int = 0) { importantEmail(priority: $priority) { email { from subject } inbox { unread total } } } '); sub createSubscription { my ($pubsub, $schema, $document) = @_; $schema ||= $emailSchema; $document ||= $defaultSubscriptionAST; my $data = { inbox => { emails => [ { from => 'joe@graphql.org', subject => 'Hello', message => 'Hello World', unread => 0, }, ], }, importantEmail => sub { my $ai = fake_promise_iterator(); $pubsub->subscribe('importantEmail', $ai->curry::publish); $ai; }, }; return ( # subscription subscribe( $schema, $document, $data, (undef) x 4, fake_promise_code(), ), # sendImportantEmail sub { my ($newEmail) = @_; push @{$data->{inbox}{emails}}, $newEmail; return $pubsub->publish('importantEmail', { importantEmail => { email => $newEmail, inbox => $data->{inbox}, }, }); }, ); } my %FIXTURES = ( emailMessage => { from => 'yuzhi@graphql.org', subject => 'Alright', message => 'Tests are good', unread => 1, }, emailMessage2 => { from => 'hyo@graphql.org', subject => 'Alright 2', message => 'Tests are good 2', unread => 1, }, payloadAfterSend1 => { data => { importantEmail => { email => { from => 'yuzhi@graphql.org', subject => 'Alright' }, inbox => { unread => 1, total => 2 }, }, }, }, payloadAfterSend2 => { data => { importantEmail => { email => { from => 'hyo@graphql.org', subject => 'Alright 2' }, inbox => { unread => 2, total => 3 }, }, }, }, documentEmailSubject => ' subscription { importantEmail { email { subject } } } ', payloadSubjectHello => { data => { importantEmail => { email => { subject => 'Hello' } } }, }, ); subtest 'Subscription Initialization Phase' => sub { subtest 'accepts positional arguments' => sub { my $document = parse(' subscription { importantEmail } '); my $emptyAsyncIterator = sub { my $ai = fake_promise_iterator(); $ai->close_tap; $ai; }; my $ai = subscribe($emailSchema, $document, { importantEmail => $emptyAsyncIterator, }, (undef) x 4, fake_promise_code()); $ai = $ai->get; # get promised value my $next = $ai->next_p; is $next, undef; # exhaustion }; subtest 'resolves to an error for unknown subscription field' => sub { my $document = parse(' subscription { unknownField } '); my ($subscription) = createSubscription(undef, $emailSchema, $document); promise_test($subscription, [{ errors => [ { message => "The subscription field 'unknownField' is not defined\n", }, ], }], ''); }; subtest 'resolves to an error if variables were wrong type' => sub { my $res = subscribe( $emailSchema, $defaultSubscriptionAST, (undef) x 2, { priority => 'meow' }, (undef) x 2, fake_promise_code(), ); promise_test($res, [{ errors => [ { message => qq{Variable '\$priority' got invalid value "meow".\nNot an Int.\n}, }, ], }], ''); }; }; subtest 'Subscription Publish Phase' => sub { subtest 'produces a payload for multiple subscribe in same subscription' => sub { my $pubsub = GraphQL::PubSub->new; my ($sub1, $sendImportantEmail) = createSubscription($pubsub); $sub1 = $sub1->get; my ($sub2) = createSubscription($pubsub); $sub2 = $sub2->get; my $payload1_p = $sub1->next_p; my $payload2_p = $sub2->next_p; $sendImportantEmail->($FIXTURES{emailMessage}); promise_test($payload1_p, [$FIXTURES{payloadAfterSend1}], ''); promise_test($payload2_p, [$FIXTURES{payloadAfterSend1}], ''); }; subtest 'produces a payload per subscription event' => sub { my $pubsub = GraphQL::PubSub->new; my ($subscription, $sendImportantEmail) = createSubscription($pubsub); $subscription = $subscription->get; my $payload_p = $subscription->next_p; $sendImportantEmail->($FIXTURES{emailMessage}); promise_test($payload_p, [$FIXTURES{payloadAfterSend1}], ''); $sendImportantEmail->($FIXTURES{emailMessage2}); promise_test($subscription->next_p, [$FIXTURES{payloadAfterSend2}], ''); # TODO implement disconnection that upstream can act on }; subtest 'event order is correct for multiple publishes' => sub { my $pubsub = GraphQL::PubSub->new; my ($subscription, $sendImportantEmail) = createSubscription($pubsub); $subscription = $subscription->get; my $payload_p = $subscription->next_p; $sendImportantEmail->($FIXTURES{emailMessage}); $sendImportantEmail->($FIXTURES{emailMessage2}); # this is different from JS tests, which say the first payload should show unread 2, total 3 because they are pull-orientated (settle on lookup) not push (settle on resolve) promise_test($payload_p, [$FIXTURES{payloadAfterSend1}], ''); promise_test($subscription->next_p, [$FIXTURES{payloadAfterSend2}], ''); }; subtest 'should handle error during execution of source event' => sub { my $erroringEmailSchema = emailSchemaWithResolvers( sub { my $ai = fake_promise_iterator(); $ai->publish({ email => { subject => 'Hello' } }); $ai->publish({ email => { subject => 'Goodbye' } }); $ai->publish({ email => { subject => 'Bonjour' } }); $ai; }, sub { die "Never leave.\n" if $_[0]->{email}{subject} eq 'Goodbye'; $_[0]; }, ); my $subscription = subscribe( $erroringEmailSchema, $FIXTURES{documentEmailSubject}, (undef) x 5, fake_promise_code(), ); $subscription = $subscription->get; promise_test($subscription->next_p, [$FIXTURES{payloadSubjectHello}], ''); promise_test($subscription->next_p, [ { data => { importantEmail => undef }, errors => [ { message => "Never leave.\n", locations => [{ line => 8, column => 5 }], path => ['importantEmail'], }, ], }, ], ''); promise_test($subscription->next_p, [ { data => { importantEmail => { email => { subject => 'Bonjour' } } } }, ], ''); }; subtest 'should pass through error thrown in source event stream' => sub { my $erroringEmailSchema = emailSchemaWithResolvers( sub { my $ai = fake_promise_iterator(); $ai->publish({ email => { subject => 'Hello' } }); $ai->error("test error\n"); $ai; }, sub { $_[0] }, ); my $subscription = subscribe( $erroringEmailSchema, $FIXTURES{documentEmailSubject}, (undef) x 5, fake_promise_code(), ); $subscription = $subscription->get; promise_test($subscription->next_p, [$FIXTURES{payloadSubjectHello}], ''); promise_test($subscription->next_p, [], "test error\n"); }; subtest 'should resolve GraphQL error from source event stream' => sub { my $erroringEmailSchema = emailSchemaWithResolvers( sub { my $ai = fake_promise_iterator(); $ai->publish({ email => { subject => 'Hello' } }); $ai->error(GraphQL::Error->coerce('test error')); $ai->close_tap; $ai; }, sub { $_[0] }, ); my $subscription = subscribe( $erroringEmailSchema, $FIXTURES{documentEmailSubject}, (undef) x 5, fake_promise_code(), ); $subscription = $subscription->get; promise_test($subscription->next_p, [$FIXTURES{payloadSubjectHello}], ''); promise_test($subscription->next_p, [ { errors => [ { message => "test error", }, ], }, ], ''); is $subscription->next_p, undef; # exhaustion }; }; done_testing; GraphQL-0.53/t/language-block-string-value.t0000644000175000017500000000337613426211267020623 0ustar osboxesosboxesuse lib 't/lib'; use GQLTest; use GraphQL::Language::Parser qw(parse); blockstring_test([ '', ' Hello,', ' World!', '', ' Yours,', ' GraphQL.', ], ['Hello,', ' World!', '', 'Yours,', ' GraphQL.'], 'removes uniform indentation from a string'); blockstring_test([ '', '', ' Hello,', ' World!', '', ' Yours,', ' GraphQL.', '', '', ], ['Hello,', ' World!', '', 'Yours,', ' GraphQL.'], 'removes empty leading and trailing lines'); blockstring_test([ ' ', ' ', ' Hello,', ' World!', '', ' Yours,', ' GraphQL.', ' ', ' ', ], ['Hello,', ' World!', '', 'Yours,', ' GraphQL.'], 'removes blank leading and trailing lines'); blockstring_test([ ' Hello,', ' World!', '', ' Yours,', ' GraphQL.', ], [' Hello,', ' World!', '', 'Yours,', ' GraphQL.'], 'retains indentation from first line'); blockstring_test([ ' ', ' Hello, ', ' World! ', ' ', ' Yours, ', ' GraphQL. ', ' ', ], [ 'Hello, ', ' World! ', ' ', 'Yours, ', ' GraphQL. ', ], 'does not alter trailing spaces'); done_testing; sub blockstring_test { my ($raw, $expect, $msg) = @_; my $got = parse(string_make(join("\n", @$raw))); is string_lookup($got), join("\n", @$expect), $msg or diag explain $got; } sub string_make { my ($text) = @_; return query_make(sprintf '"""%s"""', $text); } sub query_make { my ($text) = @_; return sprintf 'query q { foo(name: %s) { id } }', $text; } sub string_lookup { my ($got) = @_; return query_lookup($got, 'string'); } sub query_lookup { my ($got, $type) = @_; return $got->[0]{selections}[0]{arguments}{name}; } GraphQL-0.53/t/execution-variables.t0000644000175000017500000005165714070317276017313 0ustar osboxesosboxesuse lib 't/lib'; use GQLTest; use Devel::StrictMode; BEGIN { use_ok( 'GraphQL::Type::Scalar', qw($String) ) || print "Bail out!\n"; use_ok( 'GraphQL::Type::InputObject' ) || print "Bail out!\n"; use_ok( 'GraphQL::Type::Object' ) || print "Bail out!\n"; use_ok( 'GraphQL::Schema' ) || print "Bail out!\n"; use_ok( 'GraphQL::Execution', qw(execute) ) || print "Bail out!\n"; } my $JSON = JSON::MaybeXS->new->allow_nonref->canonical; my $TestComplexScalar = GraphQL::Type::Scalar->new( name => 'ComplexScalar', serialize => sub { return 'SerializedValue' if $_[0]//'' eq 'DeserializedValue'; return; }, parse_value => sub { return 'DeserializedValue' if $_[0]//'' eq 'SerializedValue'; return; }, ); my $TestInputObject = GraphQL::Type::InputObject->new( name => 'TestInputObject', fields => { a => { type => $String }, b => { type => $String->list }, c => { type => $String->non_null }, d => { type => $TestComplexScalar }, }, ); my $TestNestedInputObject = GraphQL::Type::InputObject->new( name => 'TestNestedInputObject', fields => { na => { type => $TestInputObject->non_null }, nb => { type => $String->non_null }, }, ); my $TestType = GraphQL::Type::Object->new( name => 'TestType', fields => { fieldWithObjectInput => { type => $String, args => { input => { type => $TestInputObject } }, resolve => sub { $_[1]->{input} && $JSON->encode($_[1]->{input}) }, }, fieldWithNullableStringInput => { type => $String, args => { input => { type => $String } }, resolve => sub { $_[1]->{input} && $JSON->encode($_[1]->{input}) }, }, fieldWithNonNullableStringInput => { type => $String, args => { input => { type => $String->non_null } }, resolve => sub { $_[1]->{input} && $JSON->encode($_[1]->{input}) }, }, fieldWithDefaultArgumentValue => { type => $String, args => { input => { type => $String->non_null, default_value => 'Hello World' } }, resolve => sub { $_[1]->{input} && $JSON->encode($_[1]->{input}) }, }, # is correct as brings in type to schema. zap default_value as fails type fieldWithNestedInputObject => { type => $String, args => { input => { type => $TestNestedInputObject } }, resolve => sub { $_[1]->{input} && $JSON->encode($_[1]->{input}) }, }, list => { type => $String, args => { input => { type => $String->list } }, resolve => sub { $_[1]->{input} && $JSON->encode($_[1]->{input}) }, }, nnList => { type => $String, args => { input => { type => $String->list->non_null } }, resolve => sub { $_[1]->{input} && $JSON->encode($_[1]->{input}) }, }, listNN => { type => $String, args => { input => { type => $String->non_null->list } }, resolve => sub { $_[1]->{input} && $JSON->encode($_[1]->{input}) }, }, nnListNN => { type => $String, args => { input => { type => $String->non_null->list->non_null } }, resolve => sub { $_[1]->{input} && $JSON->encode($_[1]->{input}) }, }, }, ); my $schema = GraphQL::Schema->new(query => $TestType); subtest 'Handles objects and nullability', sub { subtest 'using inline structs', sub { subtest 'executes with complex input', sub { my $doc = '{ fieldWithObjectInput(input: {a: "foo", b: ["bar"], c: "baz"}) }'; run_test( [$schema, $doc], { data => { fieldWithObjectInput => '{"a":"foo","b":["bar"],"c":"baz"}' } }, ); }; subtest 'properly parses single value to list', sub { my $doc = '{ fieldWithObjectInput(input: {a: "foo", b: "bar", c: "baz"}) }'; run_test( [$schema, $doc], { data => { fieldWithObjectInput => '{"a":"foo","b":["bar"],"c":"baz"}' } }, ); }; subtest 'properly parses null value to null', sub { my $doc = '{ fieldWithObjectInput(input: {a: null, b: null, c: "C", d: null}) }'; run_test( [$schema, $doc], { data => { fieldWithObjectInput => '{"a":null,"b":null,"c":"C","d":null}' } }, ); }; subtest 'properly parses null value in list', sub { my $doc = '{ fieldWithObjectInput(input: {b: ["A",null,"C"], c: "C"}) }'; run_test( [$schema, $doc], { data => { fieldWithObjectInput => '{"b":["A",null,"C"],"c":"C"}' } }, ); }; subtest 'does not use incorrect value', sub { plan skip_all => 'Type check disabled' unless STRICT; my $doc = '{ fieldWithObjectInput(input: ["foo", "bar", "baz"]) }'; run_test( [$schema, $doc], { data => { fieldWithObjectInput => undef }, errors => [ { message => qq{Argument 'input' got invalid value ["foo","bar","baz"].\nExpected 'TestInputObject'.\nIn method graphql_to_perl: parameter 1 (\$item): found not an object.\n}, locations => [ { line => 3, column => 7 } ], path => [ 'fieldWithObjectInput' ], } ], }, ); }; subtest 'properly runs parseLiteral on complex scalar types', sub { my $doc = '{ fieldWithObjectInput(input: {c: "foo", d: "SerializedValue"}) }'; run_test( [$schema, $doc], { data => { fieldWithObjectInput => '{"c":"foo","d":"DeserializedValue"}' } }, ); }; done_testing; }; subtest 'using variables', sub { my $doc = ' query q($input: TestInputObject) { fieldWithObjectInput(input: $input) } '; subtest 'executes with complex input', sub { my $vars = { input => { a => 'foo', b => [ 'bar' ], c => 'baz' } }; run_test( [$schema, $doc, undef, undef, $vars], { data => { fieldWithObjectInput => '{"a":"foo","b":["bar"],"c":"baz"}' } }, ); }; subtest 'uses default value when not provided', sub { my $doc_with_default = ' query q($input: TestInputObject = {a: "foo", b: ["bar"], c: "baz"}) { fieldWithObjectInput(input: $input) } '; run_test( [$schema, $doc_with_default], { data => { fieldWithObjectInput => '{"a":"foo","b":["bar"],"c":"baz"}' } }, ); }; subtest 'properly parses single value to list', sub { my $vars = { input => { a => 'foo', b => 'bar', c => 'baz' } }; run_test( [$schema, $doc, undef, undef, $vars], { data => { fieldWithObjectInput => '{"a":"foo","b":["bar"],"c":"baz"}' } }, ); }; subtest 'executes with complex scalar input', sub { my $vars = { input => { c => 'foo', d => 'SerializedValue' } }; run_test( [$schema, $doc, undef, undef, $vars], { data => { fieldWithObjectInput => '{"c":"foo","d":"DeserializedValue"}' } }, ); }; subtest 'errors on null for nested non-null', sub { plan skip_all => 'Type check disabled' unless STRICT; my $vars = { input => { a => 'foo', b => 'bar', c => undef } }; run_test( [$schema, $doc, undef, undef, $vars], { errors => [ { message => q{Variable '$input' got invalid value {"a":"foo","b":"bar","c":null}.} ."\n".q{In field "c": String! given null value.}."\n" } ] }, ); }; subtest 'errors on incorrect type', sub { plan skip_all => 'Type check disabled' unless STRICT; my $vars = { input => 'foo bar' }; run_test( [$schema, $doc, undef, undef, $vars], { errors => [ { message => q{Variable '$input' got invalid value "foo bar". In method graphql_to_perl: parameter 1 ($item): found not an object. } } ] }, ); }; subtest 'errors on omission of nested non-null', sub { my $vars = { input => { a => 'foo', b => 'bar' } }; run_test( [$schema, $doc, undef, undef, $vars], { errors => [ { message => q{Variable '$input' got invalid value {"a":"foo","b":"bar"}.} ."\n".q{In field "c": String! given null value.}."\n" } ] }, ); }; subtest 'errors on deep nested errors and with many errors', sub { my $nested_doc = ' query q($input: TestNestedInputObject) { fieldWithNestedObjectInput(input: $input) } '; my $vars = { input => { na => { a => 'foo' } } }; run_test( [$schema, $nested_doc, undef, undef, $vars], { errors => [ { message => q{Variable '$input' got invalid value {"na":{"a":"foo"}}.}."\n" .q{In field "na": In field "c": String! given null value.}."\n" .q{In field "nb": String! given null value.}."\n" } ] }, ); }; subtest 'errors on addition of unknown input field', sub { my $vars = { input => { b => 'bar', c => 'baz', extra => 'dog' } }; run_test( [$schema, $doc, undef, undef, $vars], { errors => [ { message => q{Variable '$input' got invalid value {"b":"bar","c":"baz","extra":"dog"}.} ."\n".q{In field "extra": Unknown field.}."\n" } ] }, ); }; done_testing; }; subtest 'Handles nullable scalars', sub { subtest 'allows nullable inputs to be omitted', sub { my $doc = ' { fieldWithNullableStringInput } '; run_test( [$schema, $doc], { data => { fieldWithNullableStringInput => undef } }, ); }; subtest 'allows nullable inputs to be omitted in a variable', sub { my $doc = ' query SetsNullable($value: String) { fieldWithNullableStringInput(input: $value) } '; run_test( [$schema, $doc], { data => { fieldWithNullableStringInput => undef } }, ); }; subtest 'allows nullable inputs to be omitted in an unlisted variable', sub { my $doc = ' query SetsNullable { fieldWithNullableStringInput(input: $value) } '; run_test( [$schema, $doc], { data => { fieldWithNullableStringInput => undef } }, ); }; subtest 'allows nullable inputs to be set to null in a variable', sub { my $doc = ' query SetsNullable($value: String) { fieldWithNullableStringInput(input: $value) } '; my $vars = { value => undef }; run_test( [$schema, $doc, undef, undef, $vars], { data => { fieldWithNullableStringInput => undef } }, ); }; subtest 'allows nullable inputs to be set to a value in a variable', sub { my $doc = ' query SetsNullable($value: String) { fieldWithNullableStringInput(input: $value) } '; my $vars = { value => 'a' }; run_test( [$schema, $doc, undef, undef, $vars], { data => { fieldWithNullableStringInput => '"a"' } }, ); }; subtest 'allows nullable inputs to be set to a value directly', sub { my $doc = ' { fieldWithNullableStringInput(input: "a") } '; run_test( [$schema, $doc], { data => { fieldWithNullableStringInput => '"a"' } }, ); }; }; subtest 'Handles non-nullable scalars', sub { subtest 'allows non-nullable inputs to be omitted given a default', sub { my $doc = ' query SetsNonNullable($value: String = "default") { fieldWithNonNullableStringInput(input: $value) } '; run_test( [$schema, $doc], { data => { fieldWithNonNullableStringInput => '"default"' } }, ); }; subtest 'does not allow non-nullable inputs to be omitted in a variable', sub { my $doc = ' query SetsNonNullable($value: String!) { fieldWithNonNullableStringInput(input: $value) } '; run_test( [$schema, $doc], { errors => [ { message => q{Variable '$value' got invalid value null.}."\n". q{String! given null value.}."\n" } ] }, ); }; subtest 'does not allow non-nullable inputs to be set to null in a variable', sub { my $doc = ' query SetsNonNullable($value: String!) { fieldWithNonNullableStringInput(input: $value) } '; my $vars = { value => undef }; run_test( [$schema, $doc, undef, undef, $vars], { errors => [ { message => q{Variable '$value' got invalid value null.}."\n". q{String! given null value.}."\n" } ] }, ); }; subtest 'allows non-nullable inputs to be set to a value in a variable', sub { my $doc = ' query SetsNonNullable($value: String!) { fieldWithNonNullableStringInput(input: $value) } '; my $vars = { value => 'a' }; run_test( [$schema, $doc, undef, undef, $vars], { data => { fieldWithNonNullableStringInput => '"a"' } }, ); }; subtest 'allows non-nullable inputs to be set to a value directly', sub { my $doc = ' { fieldWithNonNullableStringInput(input: "a") } '; run_test( [$schema, $doc], { data => { fieldWithNonNullableStringInput => '"a"' } }, ); }; subtest 'reports error for missing non-nullable inputs', sub { my $doc = ' { fieldWithNonNullableStringInput } '; run_test( [$schema, $doc], { data => { fieldWithNonNullableStringInput => undef }, errors => [ { message => q{Argument 'input' of type 'String!' not given.}, locations => [ { line => 2, column => 43 } ], path => [ 'fieldWithNonNullableStringInput' ], } ] }, ); }; subtest 'reports error for array passed into string input', sub { my $doc = ' query SetsNonNullable($value: String!) { fieldWithNonNullableStringInput(input: $value) } '; my $vars = { value => [ 1, 2, 3 ] }; run_test( [$schema, $doc, undef, undef, $vars], { errors => [ { message => q{Variable '$value' got invalid value [1,2,3].}."\n". q{Not a String.}."\n" } ] }, ); }; subtest 'reports error for non-provided variables for non-nullable inputs', sub { my $doc = ' { fieldWithNonNullableStringInput(input: $foo) } '; run_test( [$schema, $doc], { data => { fieldWithNonNullableStringInput => undef }, errors => [ { message => q{Argument 'input' of type 'String!' was given variable '$foo' but no runtime value.}, locations => [ { line => 2, column => 56 } ], path => [ 'fieldWithNonNullableStringInput' ], } ] }, ); }; }; subtest 'Handles lists and nullability', sub { subtest 'allows lists to be null', sub { my $doc = ' query q($input: [String]) { list(input: $input) } '; my $vars = { input => undef }; run_test( [$schema, $doc, undef, undef, $vars], { data => { list => undef } }, ); }; subtest 'allows lists to contain values', sub { my $doc = ' query q($input: [String]) { list(input: $input) } '; my $vars = { input => [ 'A' ] }; run_test( [$schema, $doc, undef, undef, $vars], { data => { list => '["A"]' } }, ); }; subtest 'allows lists to contain null', sub { my $doc = ' query q($input: [String]) { list(input: $input) } '; my $vars = { input => [ 'A', undef, 'B' ] }; run_test( [$schema, $doc, undef, undef, $vars], { data => { list => '["A",null,"B"]' } }, ); }; subtest 'does not allow non-null lists to be null', sub { my $doc = ' query q($input: [String]!) { nnList(input: $input) } '; my $vars = { input => undef }; run_test( [$schema, $doc, undef, undef, $vars], { errors => [ { message => q{Variable '$input' got invalid value null.}."\n". q{[String]! given null value.}."\n" } ] }, ); }; subtest 'allows non-null lists to contain values', sub { my $doc = ' query q($input: [String]!) { nnList(input: $input) } '; my $vars = { input => [ 'A' ] }; run_test( [$schema, $doc, undef, undef, $vars], { data => { nnList => '["A"]' } }, ); }; subtest 'allows non-null lists to contain null', sub { my $doc = ' query q($input: [String]!) { nnList(input: $input) } '; my $vars = { input => [ 'A', undef, 'B' ] }; run_test( [$schema, $doc, undef, undef, $vars], { data => { nnList => '["A",null,"B"]' } }, ); }; subtest 'allows lists of non-nulls to be null', sub { my $doc = ' query q($input: [String!]) { listNN(input: $input) } '; my $vars = { input => undef }; run_test( [$schema, $doc, undef, undef, $vars], { data => { listNN => undef } }, ); }; subtest 'allows lists of non-nulls to contain values', sub { my $doc = ' query q($input: [String!]) { listNN(input: $input) } '; my $vars = { input => [ 'A' ] }; run_test( [$schema, $doc, undef, undef, $vars], { data => { listNN => '["A"]' } }, ); }; subtest 'does not allow lists of non-nulls to contain null', sub { my $doc = ' query q($input: [String!]) { listNN(input: $input) } '; my $vars = { input => [ 'A', undef, 'B' ] }; run_test( [$schema, $doc, undef, undef, $vars], { errors => [ { message => q{Variable '$input' got invalid value ["A",null,"B"].}."\n". q{In element #1: String! given null value.}."\n" } ] }, ); }; subtest 'does not allow non-null lists of non-nulls to be null', sub { my $doc = ' query q($input: [String!]!) { nnListNN(input: $input) } '; my $vars = { input => undef }; run_test( [$schema, $doc, undef, undef, $vars], { errors => [ { message => q{Variable '$input' got invalid value null.}."\n". q{[String!]! given null value.}."\n" } ] }, ); }; subtest 'allows non-null lists of non-nulls to contain values', sub { my $doc = ' query q($input: [String!]!) { nnListNN(input: $input) } '; my $vars = { input => [ 'A' ] }; run_test( [$schema, $doc, undef, undef, $vars], { data => { nnListNN => '["A"]' } }, ); }; subtest 'does not allow non-null lists of non-nulls to contain null', sub { my $doc = ' query q($input: [String!]!) { nnListNN(input: $input) } '; my $vars = { input => [ 'A', undef, 'B' ] }; run_test( [$schema, $doc, undef, undef, $vars], { errors => [ { message => q{Variable '$input' got invalid value ["A",null,"B"].}."\n". q{In element #1: String! given null value.}."\n" } ] }, ); }; subtest 'does not allow invalid types to be used as values', sub { my $doc = ' query q($input: TestType!) { fieldWithObjectInput(input: $input) } '; my $vars = { input => { list => [ 'A', 'B' ] } }; run_test( [$schema, $doc, undef, undef, $vars], { errors => [ { message => q{Variable '$input' is type 'TestType!' which cannot be used as an input type.}."\n" } ] }, ); }; subtest 'does not allow unknown types to be used as values', sub { my $doc = ' query q($input: UnknownType!) { fieldWithObjectInput(input: $input) } '; my $vars = { input => 'whoknows' }; run_test( [$schema, $doc, undef, undef, $vars], { errors => [ { message => q{Unknown type 'UnknownType'.}."\n" } ] }, ); }; }; subtest 'Execute: Uses argument default values', sub { subtest 'when no argument provided', sub { my $doc = ' { fieldWithDefaultArgumentValue } '; run_test( [$schema, $doc], { data => { fieldWithDefaultArgumentValue => '"Hello World"' } }, ); }; subtest 'when omitted variable provided', sub { my $doc = ' query optionalVariable($optional: String) { fieldWithDefaultArgumentValue(input: $optional) } '; run_test( [$schema, $doc], { data => { fieldWithDefaultArgumentValue => '"Hello World"' } }, ); }; subtest 'not when argument cannot be coerced', sub { my $doc = ' { fieldWithDefaultArgumentValue(input: WRONG_TYPE) } '; run_test( [$schema, $doc], { data => { fieldWithDefaultArgumentValue => undef }, errors => [ { message => q{Argument 'input' of type 'String!' was given WRONG_TYPE which is enum value.}, locations => [ { line => 2, column => 60 } ], path => [ 'fieldWithDefaultArgumentValue' ], } ] }, ); }; }; done_testing; }; done_testing; GraphQL-0.53/t/perl.t0000644000175000017500000004721614170415131014266 0ustar osboxesosboxesuse strict; use warnings; use lib 't/lib'; use GQLTest; my $JSON = JSON::MaybeXS->new->allow_nonref->canonical; use GraphQL::Schema; use GraphQL::Execution qw(execute); use GraphQL::Plugin::Type::DateTime; use GraphQL::Subscription qw(subscribe); use GraphQL::Type::Scalar qw($Int $Float $String $Boolean $ID); use GraphQL::Type::InputObject; use GraphQL::Type::Object; use GraphQL::Type::Interface; use GraphQL::Type::Enum; subtest 'DateTime->now as resolve' => sub { require DateTime; my $schema = GraphQL::Schema->from_doc(<<'EOF'); type DateTimeObj { ymd: String } type Query { dateTimeNow: DateTimeObj } EOF my $now = DateTime->now; my $root_value = { dateTimeNow => sub { $now } }; run_test([ $schema, "{ dateTimeNow { ymd } }", $root_value, (undef) x 3, sub { my ($root_value, $args, $context, $info) = @_; my $field_name = $info->{field_name}; my $property = ref($root_value) eq 'HASH' ? $root_value->{$field_name} : $root_value; return $property->($args, $context, $info) if ref $property eq 'CODE'; return $root_value->$field_name if ref $property; # no args $property; } ], { data => { dateTimeNow => { ymd => scalar $now->ymd } } }, ); }; subtest 'DateTime type' => sub { require DateTime; my $schema = GraphQL::Schema->from_doc(<<'EOF'); type Query { dateTimeNow: DateTime } EOF my $now = DateTime->now; my $root_value = { dateTimeNow => sub { $now } }; run_test([ $schema, "{ dateTimeNow }", $root_value, (undef) x 3 ], { data => { dateTimeNow => $now.'' } }, ); }; subtest 'nice errors Schema.from_ast' => sub { eval { GraphQL::Schema->from_ast([ { 'fields' => { 'subtitle' => { 'type' => undef }, }, 'kind' => 'type', 'name' => 'Blog' }, { 'fields' => { 'blog' => { 'type' => [ 'list', { 'type' => 'Blog' } ] }, }, 'kind' => 'type', 'name' => 'Query' }, ]) }; is $@, "Error in field 'subtitle': Undefined type given\n"; }; subtest 'test convert plugin' => sub { require_ok 'GraphQL::Plugin::Convert::Test'; my $converted = GraphQL::Plugin::Convert::Test->to_graphql( sub { my $text = $_[1]->{s}; my $ai = fake_promise_iterator(); $ai->publish({ timedEcho => $text }); $ai; }, ); run_test([ $converted->{schema}, '{helloWorld}', $converted->{root_value} ], { data => { helloWorld => 'Hello, world!' } }, ); run_test([ $converted->{schema}, 'mutation m($s: String = "yo") { echo(s: $s) }', $converted->{root_value}, undef, { s => "hi" }, ], { data => { echo => 'hi' } }, ); my $ai = subscribe( $converted->{schema}, 'subscription s { timedEcho(s: "argh") }', $converted->{root_value}, (undef) x 4, fake_promise_code(), $converted->{subscribe_resolver}, ); $ai = $ai->get; promise_test($ai->next_p, [{ data => { timedEcho => 'argh' } }], ''); }; subtest 'multi-line description' => sub { my $doc = <<'EOF'; type Query { """ first line second bit """ hello: String } EOF my $got = eval { GraphQL::Schema->from_doc($doc)->to_doc }; SKIP: { if ($@) { is ref($@) ? $@->message : $@, ''; skip 1; } is $got, $doc; } }; subtest 'list of enum as arg' => sub { my $schema = GraphQL::Schema->from_doc(<<'EOF'); enum E { available pending } type Query { hello(arg: [E]): String } EOF run_test([ $schema, '{hello(arg: [available])}', { hello => sub { 'Hello, '.shift->{arg}[0] } } ], { data => { hello => 'Hello, available' } }, ); }; subtest 'non-nullable enum as arg' => sub { my $schema = GraphQL::Schema->from_doc(<<'EOF'); enum E { available pending } type Query { hello(arg: E!): String } EOF run_test([ $schema, '{hello(arg: available)}', { hello => sub { 'Hello, '.shift->{arg} } } ], { data => { hello => 'Hello, available' } }, ); }; subtest 'arbitrary object as exception' => sub { { package MyException; use overload '""' => sub { join ' ', @{ $_[0] } }; sub new { my $class = shift; bless [ @_ ], $class; } } my $schema = GraphQL::Schema->from_doc(<<'EOF'); type Query { hello(arg: String): String } EOF run_test([ $schema, '{hello(arg: "Hi")}', { hello => sub { die MyException->new(qw(oh no)) } } ], { 'data' => { 'hello' => undef }, 'errors' => [ { 'locations' => [ { 'column' => 18, 'line' => 1 } ], 'message' => 'oh no', 'path' => [ 'hello' ], }, ], }); }; subtest 'mutations in order' => sub { my $schema = GraphQL::Schema->from_doc(<<'EOF'); type Query { q: String } type Mutation { hello(arg: String): String } EOF my @m; run_test([ $schema, <<'EOF', mutation m { h1: hello(arg: "Hi") h2: hello(arg: "Hi2") } EOF { hello => sub { push @m, $_[0]{arg}; $_[0]{arg} } } ], { 'data' => { h1 => "Hi", h2 => "Hi2" }, }); is_deeply \@m, [ qw(Hi Hi2) ]; }; subtest 'list in query params' => sub { my $stringlist = GraphQL::Type::List->new(of => $String); is $stringlist->is_valid([ 'string' ]), 1, 'is_valid works'; my $schema = GraphQL::Schema->new( query => GraphQL::Type::Object->new( name => 'Query', fields => { hello => { type => $String, args => { arg => { type => $stringlist } } }, } ), ); run_test([ $schema, 'query q($a: [String]) {hello(arg: $a)}', { hello => "yo" }, undef, { a => [ 'there' ] }, ], { 'data' => { 'hello' => "yo" }, }); }; subtest 'list/inputobject default value in Perl' => sub { my $schema = GraphQL::Schema->new( query => GraphQL::Type::Object->new( name => 'Query', fields => { hello => { type => $String, args => { arg => { type => $String->list, default_value => ["yo"] } } }, field2 => { type => $String, args => { f2arg => { type => GraphQL::Type::InputObject->new( name => 'TestInputObject', fields => { b => { type => $String->list }, }, ), default_value => { b => 'b' }, }, }, }, } ), ); lives_ok { $schema->to_doc } 'can get SDL ok'; run_test([ $schema, 'query q($a: [String]) {hello(arg: $a)}', { hello => sub { $_[0]->{arg}[0] } }, ], { 'data' => { 'hello' => "yo" }, }); }; subtest 'input object with null value' => sub { my $schema = GraphQL::Schema->from_doc(<<'EOF'); enum E1 { A, B } enum E2 { C, D } input TestInput { f1: E1, f2: E2 } type Query { hello(arg: TestInput): String } EOF run_test([ $schema, 'query q($a: TestInput) {hello(arg: $a)}', { hello => "yo" }, undef, { a => { f1 => 'A' } }, ], { 'data' => { 'hello' => "yo" }, }); }; subtest 'errors on incorrect query input', sub { my $doc = ' query q($id: String) { fieldWithObjectInput(input: { id: $id }) }'; my $TestInputObject = GraphQL::Type::InputObject->new( name => 'TestInputObject', fields => { a => { type => $String }, b => { type => $String->list }, c => { type => $String->non_null }, }, ); my $TestType = GraphQL::Type::Object->new( name => 'TestType', fields => { fieldWithObjectInput => { type => $String, args => { input => { type => $TestInputObject } }, resolve => sub { $_[1]->{input} && $JSON->encode($_[1]->{input}) }, }, }, ); my $schema = GraphQL::Schema->new(query => $TestType); run_test( [$schema, $doc], { data => { fieldWithObjectInput => undef }, errors => [ { message => q{Argument 'input' got invalid value {"id":null}.} ."\n"."Expected 'TestInputObject'.\nIn field \"id\": Unknown field.\n", locations => [{ column => 5, line => 4 }], path => ['fieldWithObjectInput'], } ] }, ); }; subtest 'test _debug', sub { require GraphQL::Debug; my @diags; { no warnings 'redefine'; local *Test::More::diag = sub { push @diags, @_ }; GraphQL::Debug::_debug('message', +{ key => 1 }); } is_deeply \@diags, ['message: ', < 1 } EOF }; subtest 'test String.is_valid' => sub { is $String->is_valid('string'), 1, 'is_valid works'; }; subtest 'test Scalar methods' => sub { my $scalar = GraphQL::Type::Scalar->from_ast({}, { name => 's', description => 'd' }); throws_ok { $scalar->serialize->('string') } qr{Fake}, 'fake serialize'; throws_ok { $scalar->parse_value->('string') } qr{Fake}, 'fake parse_value'; is $scalar->to_doc, qq{"d"\nscalar s\n}, 'to_doc'; is $Boolean->serialize->(1), 1, 'Boolean serialize'; is $Boolean->serialize->(JSON->true), 1, 'Boolean serialize blessed'; is $Boolean->parse_value->(JSON->true), 1, 'Boolean parse_value'; for my $type ($Int, $Float, $String, $Boolean) { is $type->$_->(undef), undef, join(' ', $type->name, $_, 'null') for qw(serialize parse_value); } is $JSON->encode( $String->serialize->(1 + 1) ), '"2"', "String serialize a number json encodes as string"; is $JSON->encode( $ID->serialize->(1 + 1) ), '"2"', "String serialize a ID json encodes as string" }; subtest 'exercise __type root field more'=> sub { my $TestType = GraphQL::Type::Object->new( name => 'TestType', fields => { testField => { type => $String, } } ); my $abstract = GraphQL::Type::Interface->new( name => 'i', fields => { testField => { type => $String, } } ); my $schema = GraphQL::Schema->new(query => $TestType, types => [$abstract]); my $request = <<'EOQ'; { __type(name: "TestType") { name kind fields { name } interfaces } i: __type(name: "i") { name possibleTypes } } EOQ run_test([$schema, $request], { data => { __type => { fields => [ { name => 'testField' }, ], interfaces => [], kind => 'OBJECT', name => 'TestType', }, i => { name => 'i', possibleTypes => [], } } }); }; subtest 'test List->name' => sub { my $stringlist = GraphQL::Type::List->new(of => $String); is $stringlist->name, 'String'; }; subtest 'test multi selection with same name' => sub { require DateTime; my $schema = GraphQL::Schema->from_doc(<<'EOF'); type DateTimeObj { ymd: String dmy: String } type Query { dateTimeNow: DateTimeObj } EOF my $now = DateTime->now; my $root_value = { dateTimeNow => sub { $now } }; run_test([ $schema, "{ dateTimeNow { ymd } dateTimeNow { dmy } }", $root_value, (undef) x 3, sub { my ($root_value, $args, $context, $info) = @_; my $field_name = $info->{field_name}; my $property = ref($root_value) eq 'HASH' ? $root_value->{$field_name} : $root_value; return $property->($args, $context, $info) if ref $property eq 'CODE'; return $root_value->$field_name if ref $property; # no args $property; } ], { data => { dateTimeNow => { ymd => scalar $now->ymd, dmy => scalar $now->dmy, } } }, ); }; subtest 'literal input object with $var as value' => sub { my $schema = GraphQL::Schema->from_doc(<<'EOF'); input AuditFilter { resource: String resource_id: String } type Query { allAudits(filter: AuditFilter): String } EOF my $now = DateTime->now; my $root_value = { allAudits => 'yo' }; run_test([ $schema, 'query q($device: String!) { allAudits(filter: {resource: "device", resource_id: $device}) }', $root_value, undef, { device => 'e0c05156-c623-459d-9535-f645fdd04f3c' }, ], { data => $root_value }, ); }; subtest 'error objects stringify' => sub { my $msg = 'Something is not right...'; my $error = GraphQL::Error->new(message => $msg); is $error.'', $msg; }; subtest 'enum default value', sub { my $ColorType = GraphQL::Type::Enum->new( name => 'Color', values => { RED => { value => 0 }, GREEN => { value => 1 }, BLUE => { value => 2 }, }, ); my $schema = GraphQL::Schema->new( query => GraphQL::Type::Object->new( name => 'Query', fields => { colorEnum => { type => $ColorType, args => { fromEnum => { type => $ColorType }, }, resolve => sub { $_[1]->{fromInt} // $_[1]->{fromString} // $_[1]->{fromEnum}; }, }, } ), ); run_test( [$schema, 'query c($val: Color = GREEN) { colorEnum(fromEnum: $val) }'], { data => { colorEnum => 'GREEN' } }, ); done_testing; }; subtest 'fake promises' => sub { my $p = FakePromise->resolve('yo'); promise_test($p, ['yo'], ''); $p = FakePromise->resolve('yo')->then(sub { shift . 'ga' }); is $p->get, 'yoga'; is $p->get, 'yoga'; # check can re-get $p = FakePromise->reject("yo\n"); promise_test($p, [], "yo\n"); $p = FakePromise->reject("f\n")->catch(sub { shift }); promise_test($p, ["f\n"], ""); $p = FakePromise->resolve("yo\n")->then(sub { die shift }); promise_test($p, [], "yo\n"); $p = FakePromise->reject("f\n")->catch(sub { shift })->then(sub { die shift }); promise_test($p, [], "f\n"); $p = FakePromise->resolve("yo\n")->then(sub { die shift })->catch(sub { shift }); promise_test($p, ["yo\n"], ""); $p = FakePromise->resolve('yo')->then(sub { FakePromise->resolve('y2') }); promise_test($p, ["y2"], ""); $p = FakePromise->resolve("s\n")->then(sub { FakePromise->reject(shift) }); promise_test($p, [], "s\n"); $p = FakePromise->resolve("s\n")->then(sub { FakePromise->reject(shift) })->catch(sub { shift }); promise_test($p, ["s\n"], ""); $p = FakePromise->all(FakePromise->reject("s\n"))->catch(sub { shift }); promise_test($p, ["s\n"], ""); $p = FakePromise->all('hi', FakePromise->resolve("yo"))->then(sub { map @$_, @_ }); promise_test($p, [qw(hi yo)], ""); $p = FakePromise->all( 'hi', FakePromise->resolve("yo")->then(sub { "$_[0]!" }), )->then(sub { map ucfirst $_->[0], @_ }),; promise_test($p, [qw(Hi Yo!)], ""); $p = FakePromise->all( FakePromise->resolve("hi")->then(sub { "$_[0]!" }), FakePromise->resolve("yo")->then(sub { "$_[0]!" }), )->then(sub { map ucfirst $_->[0], @_ }),; promise_test($p, [qw(Hi! Yo!)], ""); $p = FakePromise->all( FakePromise->all( FakePromise->reject("yo\n")->then( # simulates rejection that will skip first "then" sub { "$_[0]/" } )->then( # first catch undef, sub { die "$_[0]!\n" }, )->then( # second catch undef, sub { die ">$_[0]" }, ), ), )->then(undef, sub { map "^$_", @_ }),; promise_test($p, ["^>yo\n!\n"], ""); $p = FakePromise->new; is $p->status, undef; $p->resolve('hi'); promise_test($p, ["hi"], ""); $p = FakePromise->new; my $flag; my $p2 = $p->then(sub { $flag = $_[0].'!' }); $p->resolve('hi'); is $flag, "hi!", 'appended then gets run on settling, not get'; promise_test($p2, ["hi!"], ""); $p2 = FakePromise->new; $p = FakePromise->all($p2); $p2->resolve('hi'); promise_test($p, [["hi"]], ""); $p2 = FakePromise->new; $p = FakePromise->all($p2); $p2->reject("hi\n"); promise_test($p, [], "hi\n"); $p = FakePromise->resolve(FakePromise->reject("yo\n"))->then( sub { "replaced by then" }, sub { "replaced by catch" }, ); promise_test($p, ["replaced by catch"], ""); $p = FakePromise->all(FakePromise->resolve("hi"), 'there'); promise_test($p, [map [$_], qw(hi there)], ""); }; subtest 'pubsub' => sub { require GraphQL::PubSub; my $pubsub = GraphQL::PubSub->new; my ($flag1, @flag2); my $cb1 = sub { $flag1 = $_[0] }; my $cb2 = sub { @flag2 = @_ }; $pubsub->subscribe('channel1', $cb1); $pubsub->subscribe('channel1', $cb2); $pubsub->publish('channel1', 1); is $flag1, 1, 'cb1 received first publish'; is_deeply \@flag2, [ 1 ], 'cb2 received first publish'; $pubsub->unsubscribe('channel1', $cb1); $pubsub->publish('channel1', 2); is $flag1, 1, 'cb1 did not receive second publish'; is_deeply \@flag2, [ 2 ], 'cb2 still received second publish'; $pubsub->subscribe('channel2', $cb1); $pubsub->publish('channel1', 3); is $flag1, 1, 'cb1 did not receive third publish'; is_deeply \@flag2, [ 3 ], 'cb2 still received third publish'; my $normal_cb_counter = 0; my $normal_cb = sub { $normal_cb_counter++; die "aiiee" if $_[0] eq 'die' }; $pubsub->subscribe('errors', $normal_cb); is_deeply [ $normal_cb_counter ], [ 0 ], 'init state'; $pubsub->publish('errors', 'live'); is_deeply [ $normal_cb_counter ], [ 1 ], 'normal'; $pubsub->publish('errors', 'die'); is_deeply [ $normal_cb_counter ], [ 2 ], 'call with an exception'; $pubsub->publish('errors', 'live'); is_deeply [ $normal_cb_counter ], [ 2 ], 'got unsubscribed so normal not run'; $normal_cb_counter = 0; my $error_cb_called; my $error_cb = sub { $error_cb_called = 1 }; $pubsub->subscribe('errors', $normal_cb, $error_cb); is_deeply [ $normal_cb_counter, $error_cb_called ], [ 0, undef ], 'init state'; $pubsub->publish('errors', 'live'); is_deeply [ $normal_cb_counter, $error_cb_called ], [ 1, undef ], 'normal'; $pubsub->publish('errors', 'die'); is_deeply [ $normal_cb_counter, $error_cb_called ], [ 2, 1 ], 'error_cb called'; }; subtest 'asynciterator' => sub { my $ai = fake_promise_iterator(); my $promised_value = $ai->next_p; $ai->publish('hi'); promise_test($promised_value, ["hi"], ""); $ai->publish('yo'); promise_test($ai->next_p, ["yo"], ""); $ai->publish(1); $ai->publish(2); promise_test($ai->next_p, [1], ""); promise_test($ai->next_p, [2], ""); $ai->publish(3); $ai->error("9\n"); $ai->publish(4); promise_test($ai->next_p, [3], ""); promise_test($ai->next_p, [], "9\n"); my ($callcount1, $callcount2) = (0, 0); $ai->map_then(sub { $callcount1++; $_[0] + 100 }); promise_test($ai->next_p, [104], ""); is_deeply [ $callcount1, $callcount2 ], [ 1, 0 ]; $promised_value = $ai->next_p; $ai->map_then(sub { $callcount2++; $_[0] * 2 }); $ai->publish(5); is_deeply [ $callcount1, $callcount2 ], [ 2, 0 ]; promise_test($promised_value, [105], ""); $ai->publish(6); promise_test($ai->next_p, [212], ""); is_deeply [ $callcount1, $callcount2 ], [ 3, 1 ]; $ai->publish(7); promise_test($ai->next_p, [214], ""); is_deeply [ $callcount1, $callcount2 ], [ 4, 2 ]; $ai->close_tap; is $ai->next_p, undef; throws_ok { $ai->publish(6) } qr{closed}, 'publish to closed off'; }; subtest "sane class hierarchy" => sub { package OtherNamespace::Foo { use GraphQL::Type::Object; } package OtherNamespace::Bar { use GraphQL::MaybeTypeCheck; } is_deeply \@OtherNamespace::Foo::ISA, [], "OtherNamespace::Foo does not inherit MaybeTypeCheck"; is_deeply \@OtherNamespace::Bar::ISA, ['GraphQL::MaybeTypeCheck'], "OtherNamespace::Bar does inherit MaybeTypeCheck"; }; subtest 'can build a schema directly from the source with keyword override' => sub { # define my own Scalar # this serializes the return value uppercased { package GraphQL::Test::Type::MyScalar; use Moo; use Types::Standard -all; use GraphQL::MaybeTypeCheck; extends qw(GraphQL::Type::Scalar); method from_ast( HashRef $name2type, HashRef $ast_node, ) :ReturnType(InstanceOf[__PACKAGE__]) { return $self->new( $self->_from_ast_named($ast_node), serialize => sub { uc $_[0] }, parse_value => sub { lc $_[0] }, ); } } my $doc = <<'EOF'; schema { query: Query } scalar TestScalar type Query { test: TestScalar! } EOF my $schema = GraphQL::Schema->from_doc( $doc, { %GraphQL::Schema::KIND2CLASS, scalar => 'GraphQL::Test::Type::MyScalar' } ); run_test( [$schema, '{ test }', { test => sub { 'test' } }], { data => { test => 'TEST' } }, ); }; done_testing; GraphQL-0.53/t/kitchen-sink.graphql0000644000175000017500000000217113426174352017107 0ustar osboxesosboxes# Copyright (c) 2015-present, Facebook, Inc. # # This source code is licensed under the MIT license found in the # LICENSE file in the root directory of this source tree. query queryName($foo: ComplexType, $site: Site = MOBILE) @onQuery { whoever123is: node(id: [123, 456]) { id , ... on User @onInlineFragment { field2 { id , alias: field1(first:10, after:$foo,) @include(if: $foo) { id, ...frag @onFragmentSpread } } } ... @skip(unless: $foo) { id } ... { id } } } mutation likeStory @onMutation { like(story: 123) @onField { story { id @onField } } } subscription StoryLikeSubscription( $input: StoryLikeSubscribeInput ) @onSubscription { storyLikeSubscribe(input: $input) { story { likers { count } likeSentence { text } } } } fragment frag on Friend @onFragmentDefinition { foo(size: $size, bar: $b, obj: {key: "value", block: """ block string uses \""" """}) } { unnamed(truthy: true, falsey: false, nullish: null), query } query { __typename } GraphQL-0.53/t/type-introspection.t0000644000175000017500000015245414114450657017216 0ustar osboxesosboxesuse lib 't/lib'; use GQLTest; my $JSON = JSON::MaybeXS->new->allow_nonref; BEGIN { use_ok( 'GraphQL::Plugin::Type::DateTime' ) || print "Bail out!\n"; use_ok( 'GraphQL::Schema' ) || print "Bail out!\n"; use_ok( 'GraphQL::Type::Object' ) || print "Bail out!\n"; use_ok( 'GraphQL::Type::InputObject' ) || print "Bail out!\n"; use_ok( 'GraphQL::Type::Scalar', qw($String) ) || print "Bail out!\n"; use_ok( 'GraphQL::Type::Enum' ) || print "Bail out!\n"; use_ok( 'GraphQL::Introspection', '$QUERY' ) || print "Bail out!\n"; use_ok( 'GraphQL::Execution', qw(execute) ) || print "Bail out!\n"; } subtest 'executes an introspection query', sub { my $schema = GraphQL::Schema->new(query => GraphQL::Type::Object->new( name => 'QueryRoot', fields => { onlyField => { type => $String } }, )); my $got = execute($schema, $QUERY, undef, undef, undef, 'IntrospectionQuery'); my $expected_text = join '', ; $expected_text =~ s#bless\(\s*do\{\\\(my\s*\$o\s*=\s*(.)\)\},\s*'JSON::PP::Boolean'\s*\)#'JSON->' . ($1 ? 'true' : 'false')#ge; my $big_expected = eval 'use JSON::MaybeXS;my '.$expected_text.';$VAR1'; local ($Data::Dumper::Sortkeys, $Data::Dumper::Indent, $Data::Dumper::Terse, $Data::Dumper::Purity); $Data::Dumper::Sortkeys = $Data::Dumper::Indent = $Data::Dumper::Terse = $Data::Dumper::Purity = 1; #open my $fh, '>', 'tf'; print $fh Dumper $got; # uncomment to regenerate $Data::Dumper::Purity = 0; # makes debug dumps less readable if 1 is_deeply $got, $big_expected or diag Dumper $got; done_testing; }; subtest 'introspects on input object'=> sub { my $TestInputObject = GraphQL::Type::InputObject->new( name => 'TestInputObject', fields => { a => { type => $String, default_value => 'foo' }, b => { type => $String->list }, c => { type => $String, default_value => undef } } ); my $TestType = GraphQL::Type::Object->new( name => 'TestType', fields => { field => { type => $String, args => { complex => { type => $TestInputObject } }, resolve => sub { my (undef, $args) = @_; return $JSON->encode($args->{complex}); }, } } ); my $schema = GraphQL::Schema->new(query => $TestType); my $request = <<'EOQ'; { __schema { types { kind name inputFields { name type { ...TypeRef } defaultValue } } } } fragment TypeRef on __Type { kind name ofType { kind name ofType { kind name ofType { kind name } } } } EOQ run_test([$schema, $request], { data => { __schema => { types => supersetof( { kind => 'INPUT_OBJECT', name => 'TestInputObject', inputFields => bag( { name => 'a', type => { kind => 'SCALAR', name => 'String', ofType => undef, }, defaultValue => '"foo"', }, { name => 'b', type => { kind => 'LIST', name => undef, ofType => { kind => 'SCALAR', name => 'String', ofType => undef, } }, defaultValue => undef, }, { name => 'c', type => { kind => 'SCALAR', name => 'String', ofType => undef, }, defaultValue => undef, } ) } ) } } }); }; subtest 'supports the __type root field'=> sub { my $TestType = GraphQL::Type::Object->new( name => 'TestType', fields => { testField => { type => $String, } } ); my $schema = GraphQL::Schema->new(query => $TestType); my $request = <<'EOQ'; { __type(name: "TestType") { name } } EOQ run_test([$schema, $request], { data => { __type => { name => 'TestType' } } }); }; subtest 'identifies deprecated fields'=> sub { my $TestType = GraphQL::Type::Object->new( name => 'TestType', fields => { nonDeprecated => { type => $String, }, deprecated => { type => $String, deprecation_reason => 'Removed in 1.0' } } ); my $schema = GraphQL::Schema->new(query => $TestType); my $request = <<'EOQ'; { __type(name: "TestType") { name fields(includeDeprecated: true) { name isDeprecated, deprecationReason } } } EOQ run_test([$schema, $request], { data => { __type => { name => 'TestType', fields => [ { name => 'deprecated', isDeprecated => JSON->true, deprecationReason => 'Removed in 1.0' }, { name => 'nonDeprecated', isDeprecated => JSON->false, deprecationReason => undef, }, ] } } }); }; subtest 'respects the includeDeprecated parameter for fields'=> sub { my $TestType = GraphQL::Type::Object->new( name => 'TestType', fields => { nonDeprecated => { type => $String, }, deprecated => { type => $String, deprecation_reason => 'Removed in 1.0' } } ); my $schema = GraphQL::Schema->new(query => $TestType); my $request = <<'EOQ'; { __type(name: "TestType") { name trueFields: fields(includeDeprecated: true) { name } falseFields: fields(includeDeprecated: false) { name } omittedFields: fields { name } } } EOQ run_test([$schema, $request], { data => { __type => { name => 'TestType', trueFields => [ { name => 'deprecated' }, { name => 'nonDeprecated' }, ], falseFields => [ { name => 'nonDeprecated' }, ], omittedFields => [ { name => 'nonDeprecated' }, ], } } }); }; subtest 'identifies deprecated enum values'=> sub { my $TestEnum = GraphQL::Type::Enum->new( name => 'TestEnum', values => { NONDEPRECATED => { value => 0 }, DEPRECATED => { value => 1, deprecation_reason => 'Removed in 1.0' }, ALSONONDEPRECATED => { value => 2 } } ); my $TestType = GraphQL::Type::Object->new( name => 'TestType', fields => { testEnum => { type => $TestEnum, }, } ); my $schema = GraphQL::Schema->new(query => $TestType); my $request = <<'EOQ'; { __type(name: "TestEnum") { name enumValues(includeDeprecated: true) { name isDeprecated, deprecationReason } } } EOQ run_test([$schema, $request], { data => { __type => { name => 'TestEnum', enumValues => [ { name => 'ALSONONDEPRECATED', isDeprecated => JSON->false, deprecationReason => undef, }, { name => 'DEPRECATED', isDeprecated => JSON->true, deprecationReason => 'Removed in 1.0', }, { name => 'NONDEPRECATED', isDeprecated => JSON->false, deprecationReason => undef, }, ] } } }); }; subtest 'respects the includeDeprecated parameter for enum values'=> sub { my $TestEnum = GraphQL::Type::Enum->new( name => 'TestEnum', values => { NONDEPRECATED => { value => 0 }, DEPRECATED => { value => 1, deprecation_reason => 'Removed in 1.0' }, ALSONONDEPRECATED => { value => 2 } } ); my $TestType = GraphQL::Type::Object->new( name => 'TestType', fields => { testEnum => { type => $TestEnum, }, } ); my $schema = GraphQL::Schema->new(query => $TestType); my $request = <<'EOQ'; { __type(name: "TestEnum") { name trueValues: enumValues(includeDeprecated: true) { name } falseValues: enumValues(includeDeprecated: false) { name } omittedValues: enumValues { name } } } EOQ run_test([$schema, $request], { data => { __type => { name => 'TestEnum', trueValues => [ { name => 'ALSONONDEPRECATED' }, { name => 'DEPRECATED' }, { name => 'NONDEPRECATED', }, ], falseValues => [ { name => 'ALSONONDEPRECATED' }, { name => 'NONDEPRECATED' }, ], omittedValues => [ { name => 'ALSONONDEPRECATED' }, { name => 'NONDEPRECATED' }, ], } } }); }; subtest 'fails as expected on the __type root field without an arg'=> sub { my $TestType = GraphQL::Type::Object->new( name => 'TestType', fields => { testField => { type => $String, } } ); my $schema = GraphQL::Schema->new(query => $TestType); my $request = <<'EOQ'; { __type { name } } EOQ run_test([$schema, $request], { data => { __type => undef }, errors => [noclass(superhashof({ message => 'Argument \'name\' of type \'String!\' not given.', locations => [{ line => 5, column => 1 }], }))] }); }; subtest 'exposes descriptions on types and fields'=> sub { my $QueryRoot = GraphQL::Type::Object->new( name => 'QueryRoot', fields => { onlyField => { type => $String } } ); my $schema = GraphQL::Schema->new(query => $QueryRoot); my $request = <<'EOQ'; { schemaType: __type(name: "__Schema") { name, description, fields { name, description } } } EOQ run_test([$schema, $request], { data => { schemaType => { name => '__Schema', description => 'A GraphQL Schema defines the capabilities of a GraphQL server. It exposes all available types and directives on the server, as well as the entry points for query, mutation, and subscription operations.', fields => bag( { name => 'types', description => 'A list of all types supported by this server.' }, { name => 'queryType', description => 'The type that query operations will be rooted at.' }, { name => 'mutationType', description => 'If this server supports mutation, the type that mutation operations will be rooted at.' }, { name => 'subscriptionType', description => 'If this server support subscription, the type that subscription operations will be rooted at.', }, { name => 'directives', description => 'A list of all directives supported by this server.' } ) } } }); }; subtest 'exposes descriptions on enums'=> sub { my $QueryRoot = GraphQL::Type::Object->new( name => 'QueryRoot', fields => { onlyField => { type => $String } } ); my $schema = GraphQL::Schema->new(query => $QueryRoot); my $request = <<'EOQ'; { typeKindType: __type(name: "__TypeKind") { name, description, enumValues { name, description } } } EOQ run_test([$schema, $request], { data => { typeKindType => { name => '__TypeKind', description => 'An enum describing what kind of type a given `__Type` is.', enumValues => bag( { description => 'Indicates this type is a scalar.', name => 'SCALAR' }, { description => 'Indicates this type is an object. `fields` and `interfaces` are valid fields.', name => 'OBJECT' }, { description => 'Indicates this type is an interface. `fields` and `possibleTypes` are valid fields.', name => 'INTERFACE' }, { description => 'Indicates this type is a union. `possibleTypes` is a valid field.', name => 'UNION' }, { description => 'Indicates this type is an enum. `enumValues` is a valid field.', name => 'ENUM' }, { description => 'Indicates this type is an input object. `inputFields` is a valid field.', name => 'INPUT_OBJECT' }, { description => 'Indicates this type is a list. `ofType` is a valid field.', name => 'LIST' }, { description => 'Indicates this type is a non-null. `ofType` is a valid field.', name => 'NON_NULL' } ) } } }); }; done_testing; __DATA__ $VAR1 = { 'data' => { '__schema' => { 'directives' => [ { 'args' => [ { 'defaultValue' => undef, 'description' => 'Included when true.', 'name' => 'if', 'type' => { 'kind' => 'NON_NULL', 'name' => undef, 'ofType' => { 'kind' => 'SCALAR', 'name' => 'Boolean', 'ofType' => undef } } } ], 'description' => 'Directs the executor to include this field or fragment only when the `if` argument is true.', 'locations' => [ 'FIELD', 'FRAGMENT_SPREAD', 'INLINE_FRAGMENT' ], 'name' => 'include' }, { 'args' => [ { 'defaultValue' => undef, 'description' => 'Skipped when true.', 'name' => 'if', 'type' => { 'kind' => 'NON_NULL', 'name' => undef, 'ofType' => { 'kind' => 'SCALAR', 'name' => 'Boolean', 'ofType' => undef } } } ], 'description' => 'Directs the executor to skip this field or fragment when the `if` argument is true.', 'locations' => [ 'FIELD', 'FRAGMENT_SPREAD', 'INLINE_FRAGMENT' ], 'name' => 'skip' }, { 'args' => [ { 'defaultValue' => '"No longer supported"', 'description' => 'Explains why this element was deprecated, usually also including a suggestion for how to access supported similar data. Formatted in [Markdown](https://daringfireball.net/projects/markdown/).', 'name' => 'reason', 'type' => { 'kind' => 'SCALAR', 'name' => 'String', 'ofType' => undef } } ], 'description' => 'Marks an element of a GraphQL schema as no longer supported.', 'locations' => [ 'FIELD_DEFINITION', 'ENUM_VALUE' ], 'name' => 'deprecated' } ], 'mutationType' => undef, 'queryType' => { 'name' => 'QueryRoot' }, 'subscriptionType' => undef, 'types' => [ { 'description' => 'The `Boolean` scalar type represents `true` or `false`.', 'enumValues' => undef, 'fields' => undef, 'inputFields' => undef, 'interfaces' => undef, 'kind' => 'SCALAR', 'name' => 'Boolean', 'possibleTypes' => undef }, { 'description' => 'The `DateTime` scalar type represents a point in time. Canonically represented using ISO 8601 format, e.g. 20171114T07:41:10, which is 14 November 2017 at 07:41am.', 'enumValues' => undef, 'fields' => undef, 'inputFields' => undef, 'interfaces' => undef, 'kind' => 'SCALAR', 'name' => 'DateTime', 'possibleTypes' => undef }, { 'description' => 'The `Float` scalar type represents signed double-precision fractional values as specified by [IEEE 754](http://en.wikipedia.org/wiki/IEEE_floating_point).', 'enumValues' => undef, 'fields' => undef, 'inputFields' => undef, 'interfaces' => undef, 'kind' => 'SCALAR', 'name' => 'Float', 'possibleTypes' => undef }, { 'description' => 'The `ID` scalar type represents a unique identifier, often used to refetch an object or as key for a cache. The ID type appears in a JSON response as a String; however, it is not intended to be human-readable. When expected as an input type, any string (such as `"4"`) or integer (such as `4`) input value will be accepted as an ID.', 'enumValues' => undef, 'fields' => undef, 'inputFields' => undef, 'interfaces' => undef, 'kind' => 'SCALAR', 'name' => 'ID', 'possibleTypes' => undef }, { 'description' => 'The `Int` scalar type represents non-fractional signed whole numeric values. Int can represent values between -(2^31) and 2^31 - 1.', 'enumValues' => undef, 'fields' => undef, 'inputFields' => undef, 'interfaces' => undef, 'kind' => 'SCALAR', 'name' => 'Int', 'possibleTypes' => undef }, { 'description' => undef, 'enumValues' => undef, 'fields' => [ { 'args' => [], 'deprecationReason' => undef, 'description' => undef, 'isDeprecated' => bless( do{\(my $o = 0)}, 'JSON::PP::Boolean' ), 'name' => 'onlyField', 'type' => { 'kind' => 'SCALAR', 'name' => 'String', 'ofType' => undef } } ], 'inputFields' => undef, 'interfaces' => [], 'kind' => 'OBJECT', 'name' => 'QueryRoot', 'possibleTypes' => undef }, { 'description' => 'The `String` scalar type represents textual data, represented as UTF-8 character sequences. The String type is most often used by GraphQL to represent free-form human-readable text.', 'enumValues' => undef, 'fields' => undef, 'inputFields' => undef, 'interfaces' => undef, 'kind' => 'SCALAR', 'name' => 'String', 'possibleTypes' => undef }, { 'description' => 'A Directive provides a way to describe alternate runtime execution and type validation behavior in a GraphQL document. In some cases, you need to provide options to alter GraphQL\'s execution behavior in ways field arguments will not suffice, such as conditionally including or skipping a field. Directives provide this by describing additional information to the executor.', 'enumValues' => undef, 'fields' => [ { 'args' => [], 'deprecationReason' => undef, 'description' => undef, 'isDeprecated' => do{my $o}, 'name' => 'args', 'type' => { 'kind' => 'NON_NULL', 'name' => undef, 'ofType' => { 'kind' => 'LIST', 'name' => undef, 'ofType' => { 'kind' => 'NON_NULL', 'name' => undef, 'ofType' => { 'kind' => 'OBJECT', 'name' => '__InputValue', 'ofType' => undef } } } } }, { 'args' => [], 'deprecationReason' => undef, 'description' => undef, 'isDeprecated' => do{my $o}, 'name' => 'description', 'type' => { 'kind' => 'SCALAR', 'name' => 'String', 'ofType' => undef } }, { 'args' => [], 'deprecationReason' => undef, 'description' => undef, 'isDeprecated' => do{my $o}, 'name' => 'locations', 'type' => { 'kind' => 'NON_NULL', 'name' => undef, 'ofType' => { 'kind' => 'LIST', 'name' => undef, 'ofType' => { 'kind' => 'NON_NULL', 'name' => undef, 'ofType' => { 'kind' => 'ENUM', 'name' => '__DirectiveLocation', 'ofType' => undef } } } } }, { 'args' => [], 'deprecationReason' => undef, 'description' => undef, 'isDeprecated' => do{my $o}, 'name' => 'name', 'type' => { 'kind' => 'NON_NULL', 'name' => undef, 'ofType' => { 'kind' => 'SCALAR', 'name' => 'String', 'ofType' => undef } } } ], 'inputFields' => undef, 'interfaces' => [], 'kind' => 'OBJECT', 'name' => '__Directive', 'possibleTypes' => undef }, { 'description' => 'A Directive can be adjacent to many parts of the GraphQL language, a __DirectiveLocation describes one such possible adjacencies.', 'enumValues' => [ { 'deprecationReason' => undef, 'description' => 'Location adjacent to an argument definition.', 'isDeprecated' => do{my $o}, 'name' => 'ARGUMENT_DEFINITION' }, { 'deprecationReason' => undef, 'description' => 'Location adjacent to an enum definition.', 'isDeprecated' => do{my $o}, 'name' => 'ENUM' }, { 'deprecationReason' => undef, 'description' => 'Location adjacent to an enum value definition.', 'isDeprecated' => do{my $o}, 'name' => 'ENUM_VALUE' }, { 'deprecationReason' => undef, 'description' => 'Location adjacent to a field.', 'isDeprecated' => do{my $o}, 'name' => 'FIELD' }, { 'deprecationReason' => undef, 'description' => 'Location adjacent to a field definition.', 'isDeprecated' => do{my $o}, 'name' => 'FIELD_DEFINITION' }, { 'deprecationReason' => undef, 'description' => 'Location adjacent to a fragment definition.', 'isDeprecated' => do{my $o}, 'name' => 'FRAGMENT_DEFINITION' }, { 'deprecationReason' => undef, 'description' => 'Location adjacent to a fragment spread.', 'isDeprecated' => do{my $o}, 'name' => 'FRAGMENT_SPREAD' }, { 'deprecationReason' => undef, 'description' => 'Location adjacent to an inline fragment.', 'isDeprecated' => do{my $o}, 'name' => 'INLINE_FRAGMENT' }, { 'deprecationReason' => undef, 'description' => 'Location adjacent to an input object field definition.', 'isDeprecated' => do{my $o}, 'name' => 'INPUT_FIELD_DEFINITION' }, { 'deprecationReason' => undef, 'description' => 'Location adjacent to an input object type definition.', 'isDeprecated' => do{my $o}, 'name' => 'INPUT_OBJECT' }, { 'deprecationReason' => undef, 'description' => 'Location adjacent to an interface definition.', 'isDeprecated' => do{my $o}, 'name' => 'INTERFACE' }, { 'deprecationReason' => undef, 'description' => 'Location adjacent to a mutation operation.', 'isDeprecated' => do{my $o}, 'name' => 'MUTATION' }, { 'deprecationReason' => undef, 'description' => 'Location adjacent to an object type definition.', 'isDeprecated' => do{my $o}, 'name' => 'OBJECT' }, { 'deprecationReason' => undef, 'description' => 'Location adjacent to a query operation.', 'isDeprecated' => do{my $o}, 'name' => 'QUERY' }, { 'deprecationReason' => undef, 'description' => 'Location adjacent to a scalar definition.', 'isDeprecated' => do{my $o}, 'name' => 'SCALAR' }, { 'deprecationReason' => undef, 'description' => 'Location adjacent to a schema definition.', 'isDeprecated' => do{my $o}, 'name' => 'SCHEMA' }, { 'deprecationReason' => undef, 'description' => 'Location adjacent to a subscription operation.', 'isDeprecated' => do{my $o}, 'name' => 'SUBSCRIPTION' }, { 'deprecationReason' => undef, 'description' => 'Location adjacent to a union definition.', 'isDeprecated' => do{my $o}, 'name' => 'UNION' } ], 'fields' => undef, 'inputFields' => undef, 'interfaces' => undef, 'kind' => 'ENUM', 'name' => '__DirectiveLocation', 'possibleTypes' => undef }, { 'description' => 'One possible value for a given Enum. Enum values are unique values, not a placeholder for a string or numeric value. However an Enum value is returned in a JSON response as a string.', 'enumValues' => undef, 'fields' => [ { 'args' => [], 'deprecationReason' => undef, 'description' => undef, 'isDeprecated' => do{my $o}, 'name' => 'deprecationReason', 'type' => { 'kind' => 'SCALAR', 'name' => 'String', 'ofType' => undef } }, { 'args' => [], 'deprecationReason' => undef, 'description' => undef, 'isDeprecated' => do{my $o}, 'name' => 'description', 'type' => { 'kind' => 'SCALAR', 'name' => 'String', 'ofType' => undef } }, { 'args' => [], 'deprecationReason' => undef, 'description' => undef, 'isDeprecated' => do{my $o}, 'name' => 'isDeprecated', 'type' => { 'kind' => 'NON_NULL', 'name' => undef, 'ofType' => { 'kind' => 'SCALAR', 'name' => 'Boolean', 'ofType' => undef } } }, { 'args' => [], 'deprecationReason' => undef, 'description' => undef, 'isDeprecated' => do{my $o}, 'name' => 'name', 'type' => { 'kind' => 'NON_NULL', 'name' => undef, 'ofType' => { 'kind' => 'SCALAR', 'name' => 'String', 'ofType' => undef } } } ], 'inputFields' => undef, 'interfaces' => [], 'kind' => 'OBJECT', 'name' => '__EnumValue', 'possibleTypes' => undef }, { 'description' => 'Object and Interface types are described by a list of Fields, each of which has a name, potentially a list of arguments, and a return type.', 'enumValues' => undef, 'fields' => [ { 'args' => [], 'deprecationReason' => undef, 'description' => undef, 'isDeprecated' => do{my $o}, 'name' => 'args', 'type' => { 'kind' => 'NON_NULL', 'name' => undef, 'ofType' => { 'kind' => 'LIST', 'name' => undef, 'ofType' => { 'kind' => 'NON_NULL', 'name' => undef, 'ofType' => { 'kind' => 'OBJECT', 'name' => '__InputValue', 'ofType' => undef } } } } }, { 'args' => [], 'deprecationReason' => undef, 'description' => undef, 'isDeprecated' => do{my $o}, 'name' => 'deprecationReason', 'type' => { 'kind' => 'SCALAR', 'name' => 'String', 'ofType' => undef } }, { 'args' => [], 'deprecationReason' => undef, 'description' => undef, 'isDeprecated' => do{my $o}, 'name' => 'description', 'type' => { 'kind' => 'SCALAR', 'name' => 'String', 'ofType' => undef } }, { 'args' => [], 'deprecationReason' => undef, 'description' => undef, 'isDeprecated' => do{my $o}, 'name' => 'isDeprecated', 'type' => { 'kind' => 'NON_NULL', 'name' => undef, 'ofType' => { 'kind' => 'SCALAR', 'name' => 'Boolean', 'ofType' => undef } } }, { 'args' => [], 'deprecationReason' => undef, 'description' => undef, 'isDeprecated' => do{my $o}, 'name' => 'name', 'type' => { 'kind' => 'NON_NULL', 'name' => undef, 'ofType' => { 'kind' => 'SCALAR', 'name' => 'String', 'ofType' => undef } } }, { 'args' => [], 'deprecationReason' => undef, 'description' => undef, 'isDeprecated' => do{my $o}, 'name' => 'type', 'type' => { 'kind' => 'NON_NULL', 'name' => undef, 'ofType' => { 'kind' => 'OBJECT', 'name' => '__Type', 'ofType' => undef } } } ], 'inputFields' => undef, 'interfaces' => [], 'kind' => 'OBJECT', 'name' => '__Field', 'possibleTypes' => undef }, { 'description' => 'Arguments provided to Fields or Directives and the input fields of an InputObject are represented as Input Values which describe their type and optionally a default value.', 'enumValues' => undef, 'fields' => [ { 'args' => [], 'deprecationReason' => undef, 'description' => 'A GraphQL-formatted string representing the default value for this input value.', 'isDeprecated' => do{my $o}, 'name' => 'defaultValue', 'type' => { 'kind' => 'SCALAR', 'name' => 'String', 'ofType' => undef } }, { 'args' => [], 'deprecationReason' => undef, 'description' => undef, 'isDeprecated' => do{my $o}, 'name' => 'description', 'type' => { 'kind' => 'SCALAR', 'name' => 'String', 'ofType' => undef } }, { 'args' => [], 'deprecationReason' => undef, 'description' => undef, 'isDeprecated' => do{my $o}, 'name' => 'name', 'type' => { 'kind' => 'NON_NULL', 'name' => undef, 'ofType' => { 'kind' => 'SCALAR', 'name' => 'String', 'ofType' => undef } } }, { 'args' => [], 'deprecationReason' => undef, 'description' => undef, 'isDeprecated' => do{my $o}, 'name' => 'type', 'type' => { 'kind' => 'NON_NULL', 'name' => undef, 'ofType' => { 'kind' => 'OBJECT', 'name' => '__Type', 'ofType' => undef } } } ], 'inputFields' => undef, 'interfaces' => [], 'kind' => 'OBJECT', 'name' => '__InputValue', 'possibleTypes' => undef }, { 'description' => 'A GraphQL Schema defines the capabilities of a GraphQL server. It exposes all available types and directives on the server, as well as the entry points for query, mutation, and subscription operations.', 'enumValues' => undef, 'fields' => [ { 'args' => [], 'deprecationReason' => undef, 'description' => 'A list of all directives supported by this server.', 'isDeprecated' => do{my $o}, 'name' => 'directives', 'type' => { 'kind' => 'NON_NULL', 'name' => undef, 'ofType' => { 'kind' => 'LIST', 'name' => undef, 'ofType' => { 'kind' => 'NON_NULL', 'name' => undef, 'ofType' => { 'kind' => 'OBJECT', 'name' => '__Directive', 'ofType' => undef } } } } }, { 'args' => [], 'deprecationReason' => undef, 'description' => 'If this server supports mutation, the type that mutation operations will be rooted at.', 'isDeprecated' => do{my $o}, 'name' => 'mutationType', 'type' => { 'kind' => 'OBJECT', 'name' => '__Type', 'ofType' => undef } }, { 'args' => [], 'deprecationReason' => undef, 'description' => 'The type that query operations will be rooted at.', 'isDeprecated' => do{my $o}, 'name' => 'queryType', 'type' => { 'kind' => 'NON_NULL', 'name' => undef, 'ofType' => { 'kind' => 'OBJECT', 'name' => '__Type', 'ofType' => undef } } }, { 'args' => [], 'deprecationReason' => undef, 'description' => 'If this server support subscription, the type that subscription operations will be rooted at.', 'isDeprecated' => do{my $o}, 'name' => 'subscriptionType', 'type' => { 'kind' => 'OBJECT', 'name' => '__Type', 'ofType' => undef } }, { 'args' => [], 'deprecationReason' => undef, 'description' => 'A list of all types supported by this server.', 'isDeprecated' => do{my $o}, 'name' => 'types', 'type' => { 'kind' => 'NON_NULL', 'name' => undef, 'ofType' => { 'kind' => 'LIST', 'name' => undef, 'ofType' => { 'kind' => 'NON_NULL', 'name' => undef, 'ofType' => { 'kind' => 'OBJECT', 'name' => '__Type', 'ofType' => undef } } } } } ], 'inputFields' => undef, 'interfaces' => [], 'kind' => 'OBJECT', 'name' => '__Schema', 'possibleTypes' => undef }, { 'description' => 'The fundamental unit of any GraphQL Schema is the type. There are many kinds of types in GraphQL as represented by the `__TypeKind` enum. Depending on the kind of a type, certain fields describe information about that type. Scalar types provide no information beyond a name and description, while Enum types provide their values. Object and Interface types provide the fields they describe. Abstract types, Union and Interface, provide the Object types possible at runtime. List and NonNull types compose other types.', 'enumValues' => undef, 'fields' => [ { 'args' => [], 'deprecationReason' => undef, 'description' => undef, 'isDeprecated' => do{my $o}, 'name' => 'description', 'type' => { 'kind' => 'SCALAR', 'name' => 'String', 'ofType' => undef } }, { 'args' => [ { 'defaultValue' => 'false', 'description' => undef, 'name' => 'includeDeprecated', 'type' => { 'kind' => 'SCALAR', 'name' => 'Boolean', 'ofType' => undef } } ], 'deprecationReason' => undef, 'description' => undef, 'isDeprecated' => do{my $o}, 'name' => 'enumValues', 'type' => { 'kind' => 'LIST', 'name' => undef, 'ofType' => { 'kind' => 'NON_NULL', 'name' => undef, 'ofType' => { 'kind' => 'OBJECT', 'name' => '__EnumValue', 'ofType' => undef } } } }, { 'args' => [ { 'defaultValue' => 'false', 'description' => undef, 'name' => 'includeDeprecated', 'type' => { 'kind' => 'SCALAR', 'name' => 'Boolean', 'ofType' => undef } } ], 'deprecationReason' => undef, 'description' => undef, 'isDeprecated' => do{my $o}, 'name' => 'fields', 'type' => { 'kind' => 'LIST', 'name' => undef, 'ofType' => { 'kind' => 'NON_NULL', 'name' => undef, 'ofType' => { 'kind' => 'OBJECT', 'name' => '__Field', 'ofType' => undef } } } }, { 'args' => [], 'deprecationReason' => undef, 'description' => undef, 'isDeprecated' => do{my $o}, 'name' => 'inputFields', 'type' => { 'kind' => 'LIST', 'name' => undef, 'ofType' => { 'kind' => 'NON_NULL', 'name' => undef, 'ofType' => { 'kind' => 'OBJECT', 'name' => '__InputValue', 'ofType' => undef } } } }, { 'args' => [], 'deprecationReason' => undef, 'description' => undef, 'isDeprecated' => do{my $o}, 'name' => 'interfaces', 'type' => { 'kind' => 'LIST', 'name' => undef, 'ofType' => { 'kind' => 'NON_NULL', 'name' => undef, 'ofType' => { 'kind' => 'OBJECT', 'name' => '__Type', 'ofType' => undef } } } }, { 'args' => [], 'deprecationReason' => undef, 'description' => undef, 'isDeprecated' => do{my $o}, 'name' => 'kind', 'type' => { 'kind' => 'NON_NULL', 'name' => undef, 'ofType' => { 'kind' => 'ENUM', 'name' => '__TypeKind', 'ofType' => undef } } }, { 'args' => [], 'deprecationReason' => undef, 'description' => undef, 'isDeprecated' => do{my $o}, 'name' => 'name', 'type' => { 'kind' => 'SCALAR', 'name' => 'String', 'ofType' => undef } }, { 'args' => [], 'deprecationReason' => undef, 'description' => undef, 'isDeprecated' => do{my $o}, 'name' => 'ofType', 'type' => { 'kind' => 'OBJECT', 'name' => '__Type', 'ofType' => undef } }, { 'args' => [], 'deprecationReason' => undef, 'description' => undef, 'isDeprecated' => do{my $o}, 'name' => 'possibleTypes', 'type' => { 'kind' => 'LIST', 'name' => undef, 'ofType' => { 'kind' => 'NON_NULL', 'name' => undef, 'ofType' => { 'kind' => 'OBJECT', 'name' => '__Type', 'ofType' => undef } } } } ], 'inputFields' => undef, 'interfaces' => [], 'kind' => 'OBJECT', 'name' => '__Type', 'possibleTypes' => undef }, { 'description' => 'An enum describing what kind of type a given `__Type` is.', 'enumValues' => [ { 'deprecationReason' => undef, 'description' => 'Indicates this type is an enum. `enumValues` is a valid field.', 'isDeprecated' => do{my $o}, 'name' => 'ENUM' }, { 'deprecationReason' => undef, 'description' => 'Indicates this type is an input object. `inputFields` is a valid field.', 'isDeprecated' => do{my $o}, 'name' => 'INPUT_OBJECT' }, { 'deprecationReason' => undef, 'description' => 'Indicates this type is an interface. `fields` and `possibleTypes` are valid fields.', 'isDeprecated' => do{my $o}, 'name' => 'INTERFACE' }, { 'deprecationReason' => undef, 'description' => 'Indicates this type is a list. `ofType` is a valid field.', 'isDeprecated' => do{my $o}, 'name' => 'LIST' }, { 'deprecationReason' => undef, 'description' => 'Indicates this type is a non-null. `ofType` is a valid field.', 'isDeprecated' => do{my $o}, 'name' => 'NON_NULL' }, { 'deprecationReason' => undef, 'description' => 'Indicates this type is an object. `fields` and `interfaces` are valid fields.', 'isDeprecated' => do{my $o}, 'name' => 'OBJECT' }, { 'deprecationReason' => undef, 'description' => 'Indicates this type is a scalar.', 'isDeprecated' => do{my $o}, 'name' => 'SCALAR' }, { 'deprecationReason' => undef, 'description' => 'Indicates this type is a union. `possibleTypes` is a valid field.', 'isDeprecated' => do{my $o}, 'name' => 'UNION' } ], 'fields' => undef, 'inputFields' => undef, 'interfaces' => undef, 'kind' => 'ENUM', 'name' => '__TypeKind', 'possibleTypes' => undef } ] } } }; $VAR1->{'data'}{'__schema'}{'types'}[7]{'fields'}[0]{'isDeprecated'} = $VAR1->{'data'}{'__schema'}{'types'}[5]{'fields'}[0]{'isDeprecated'}; $VAR1->{'data'}{'__schema'}{'types'}[7]{'fields'}[1]{'isDeprecated'} = $VAR1->{'data'}{'__schema'}{'types'}[5]{'fields'}[0]{'isDeprecated'}; $VAR1->{'data'}{'__schema'}{'types'}[7]{'fields'}[2]{'isDeprecated'} = $VAR1->{'data'}{'__schema'}{'types'}[5]{'fields'}[0]{'isDeprecated'}; $VAR1->{'data'}{'__schema'}{'types'}[7]{'fields'}[3]{'isDeprecated'} = $VAR1->{'data'}{'__schema'}{'types'}[5]{'fields'}[0]{'isDeprecated'}; $VAR1->{'data'}{'__schema'}{'types'}[8]{'enumValues'}[0]{'isDeprecated'} = $VAR1->{'data'}{'__schema'}{'types'}[5]{'fields'}[0]{'isDeprecated'}; $VAR1->{'data'}{'__schema'}{'types'}[8]{'enumValues'}[1]{'isDeprecated'} = $VAR1->{'data'}{'__schema'}{'types'}[5]{'fields'}[0]{'isDeprecated'}; $VAR1->{'data'}{'__schema'}{'types'}[8]{'enumValues'}[2]{'isDeprecated'} = $VAR1->{'data'}{'__schema'}{'types'}[5]{'fields'}[0]{'isDeprecated'}; $VAR1->{'data'}{'__schema'}{'types'}[8]{'enumValues'}[3]{'isDeprecated'} = $VAR1->{'data'}{'__schema'}{'types'}[5]{'fields'}[0]{'isDeprecated'}; $VAR1->{'data'}{'__schema'}{'types'}[8]{'enumValues'}[4]{'isDeprecated'} = $VAR1->{'data'}{'__schema'}{'types'}[5]{'fields'}[0]{'isDeprecated'}; $VAR1->{'data'}{'__schema'}{'types'}[8]{'enumValues'}[5]{'isDeprecated'} = $VAR1->{'data'}{'__schema'}{'types'}[5]{'fields'}[0]{'isDeprecated'}; $VAR1->{'data'}{'__schema'}{'types'}[8]{'enumValues'}[6]{'isDeprecated'} = $VAR1->{'data'}{'__schema'}{'types'}[5]{'fields'}[0]{'isDeprecated'}; $VAR1->{'data'}{'__schema'}{'types'}[8]{'enumValues'}[7]{'isDeprecated'} = $VAR1->{'data'}{'__schema'}{'types'}[5]{'fields'}[0]{'isDeprecated'}; $VAR1->{'data'}{'__schema'}{'types'}[8]{'enumValues'}[8]{'isDeprecated'} = $VAR1->{'data'}{'__schema'}{'types'}[5]{'fields'}[0]{'isDeprecated'}; $VAR1->{'data'}{'__schema'}{'types'}[8]{'enumValues'}[9]{'isDeprecated'} = $VAR1->{'data'}{'__schema'}{'types'}[5]{'fields'}[0]{'isDeprecated'}; $VAR1->{'data'}{'__schema'}{'types'}[8]{'enumValues'}[10]{'isDeprecated'} = $VAR1->{'data'}{'__schema'}{'types'}[5]{'fields'}[0]{'isDeprecated'}; $VAR1->{'data'}{'__schema'}{'types'}[8]{'enumValues'}[11]{'isDeprecated'} = $VAR1->{'data'}{'__schema'}{'types'}[5]{'fields'}[0]{'isDeprecated'}; $VAR1->{'data'}{'__schema'}{'types'}[8]{'enumValues'}[12]{'isDeprecated'} = $VAR1->{'data'}{'__schema'}{'types'}[5]{'fields'}[0]{'isDeprecated'}; $VAR1->{'data'}{'__schema'}{'types'}[8]{'enumValues'}[13]{'isDeprecated'} = $VAR1->{'data'}{'__schema'}{'types'}[5]{'fields'}[0]{'isDeprecated'}; $VAR1->{'data'}{'__schema'}{'types'}[8]{'enumValues'}[14]{'isDeprecated'} = $VAR1->{'data'}{'__schema'}{'types'}[5]{'fields'}[0]{'isDeprecated'}; $VAR1->{'data'}{'__schema'}{'types'}[8]{'enumValues'}[15]{'isDeprecated'} = $VAR1->{'data'}{'__schema'}{'types'}[5]{'fields'}[0]{'isDeprecated'}; $VAR1->{'data'}{'__schema'}{'types'}[8]{'enumValues'}[16]{'isDeprecated'} = $VAR1->{'data'}{'__schema'}{'types'}[5]{'fields'}[0]{'isDeprecated'}; $VAR1->{'data'}{'__schema'}{'types'}[8]{'enumValues'}[17]{'isDeprecated'} = $VAR1->{'data'}{'__schema'}{'types'}[5]{'fields'}[0]{'isDeprecated'}; $VAR1->{'data'}{'__schema'}{'types'}[9]{'fields'}[0]{'isDeprecated'} = $VAR1->{'data'}{'__schema'}{'types'}[5]{'fields'}[0]{'isDeprecated'}; $VAR1->{'data'}{'__schema'}{'types'}[9]{'fields'}[1]{'isDeprecated'} = $VAR1->{'data'}{'__schema'}{'types'}[5]{'fields'}[0]{'isDeprecated'}; $VAR1->{'data'}{'__schema'}{'types'}[9]{'fields'}[2]{'isDeprecated'} = $VAR1->{'data'}{'__schema'}{'types'}[5]{'fields'}[0]{'isDeprecated'}; $VAR1->{'data'}{'__schema'}{'types'}[9]{'fields'}[3]{'isDeprecated'} = $VAR1->{'data'}{'__schema'}{'types'}[5]{'fields'}[0]{'isDeprecated'}; $VAR1->{'data'}{'__schema'}{'types'}[10]{'fields'}[0]{'isDeprecated'} = $VAR1->{'data'}{'__schema'}{'types'}[5]{'fields'}[0]{'isDeprecated'}; $VAR1->{'data'}{'__schema'}{'types'}[10]{'fields'}[1]{'isDeprecated'} = $VAR1->{'data'}{'__schema'}{'types'}[5]{'fields'}[0]{'isDeprecated'}; $VAR1->{'data'}{'__schema'}{'types'}[10]{'fields'}[2]{'isDeprecated'} = $VAR1->{'data'}{'__schema'}{'types'}[5]{'fields'}[0]{'isDeprecated'}; $VAR1->{'data'}{'__schema'}{'types'}[10]{'fields'}[3]{'isDeprecated'} = $VAR1->{'data'}{'__schema'}{'types'}[5]{'fields'}[0]{'isDeprecated'}; $VAR1->{'data'}{'__schema'}{'types'}[10]{'fields'}[4]{'isDeprecated'} = $VAR1->{'data'}{'__schema'}{'types'}[5]{'fields'}[0]{'isDeprecated'}; $VAR1->{'data'}{'__schema'}{'types'}[10]{'fields'}[5]{'isDeprecated'} = $VAR1->{'data'}{'__schema'}{'types'}[5]{'fields'}[0]{'isDeprecated'}; $VAR1->{'data'}{'__schema'}{'types'}[11]{'fields'}[0]{'isDeprecated'} = $VAR1->{'data'}{'__schema'}{'types'}[5]{'fields'}[0]{'isDeprecated'}; $VAR1->{'data'}{'__schema'}{'types'}[11]{'fields'}[1]{'isDeprecated'} = $VAR1->{'data'}{'__schema'}{'types'}[5]{'fields'}[0]{'isDeprecated'}; $VAR1->{'data'}{'__schema'}{'types'}[11]{'fields'}[2]{'isDeprecated'} = $VAR1->{'data'}{'__schema'}{'types'}[5]{'fields'}[0]{'isDeprecated'}; $VAR1->{'data'}{'__schema'}{'types'}[11]{'fields'}[3]{'isDeprecated'} = $VAR1->{'data'}{'__schema'}{'types'}[5]{'fields'}[0]{'isDeprecated'}; $VAR1->{'data'}{'__schema'}{'types'}[12]{'fields'}[0]{'isDeprecated'} = $VAR1->{'data'}{'__schema'}{'types'}[5]{'fields'}[0]{'isDeprecated'}; $VAR1->{'data'}{'__schema'}{'types'}[12]{'fields'}[1]{'isDeprecated'} = $VAR1->{'data'}{'__schema'}{'types'}[5]{'fields'}[0]{'isDeprecated'}; $VAR1->{'data'}{'__schema'}{'types'}[12]{'fields'}[2]{'isDeprecated'} = $VAR1->{'data'}{'__schema'}{'types'}[5]{'fields'}[0]{'isDeprecated'}; $VAR1->{'data'}{'__schema'}{'types'}[12]{'fields'}[3]{'isDeprecated'} = $VAR1->{'data'}{'__schema'}{'types'}[5]{'fields'}[0]{'isDeprecated'}; $VAR1->{'data'}{'__schema'}{'types'}[12]{'fields'}[4]{'isDeprecated'} = $VAR1->{'data'}{'__schema'}{'types'}[5]{'fields'}[0]{'isDeprecated'}; $VAR1->{'data'}{'__schema'}{'types'}[13]{'fields'}[0]{'isDeprecated'} = $VAR1->{'data'}{'__schema'}{'types'}[5]{'fields'}[0]{'isDeprecated'}; $VAR1->{'data'}{'__schema'}{'types'}[13]{'fields'}[1]{'isDeprecated'} = $VAR1->{'data'}{'__schema'}{'types'}[5]{'fields'}[0]{'isDeprecated'}; $VAR1->{'data'}{'__schema'}{'types'}[13]{'fields'}[2]{'isDeprecated'} = $VAR1->{'data'}{'__schema'}{'types'}[5]{'fields'}[0]{'isDeprecated'}; $VAR1->{'data'}{'__schema'}{'types'}[13]{'fields'}[3]{'isDeprecated'} = $VAR1->{'data'}{'__schema'}{'types'}[5]{'fields'}[0]{'isDeprecated'}; $VAR1->{'data'}{'__schema'}{'types'}[13]{'fields'}[4]{'isDeprecated'} = $VAR1->{'data'}{'__schema'}{'types'}[5]{'fields'}[0]{'isDeprecated'}; $VAR1->{'data'}{'__schema'}{'types'}[13]{'fields'}[5]{'isDeprecated'} = $VAR1->{'data'}{'__schema'}{'types'}[5]{'fields'}[0]{'isDeprecated'}; $VAR1->{'data'}{'__schema'}{'types'}[13]{'fields'}[6]{'isDeprecated'} = $VAR1->{'data'}{'__schema'}{'types'}[5]{'fields'}[0]{'isDeprecated'}; $VAR1->{'data'}{'__schema'}{'types'}[13]{'fields'}[7]{'isDeprecated'} = $VAR1->{'data'}{'__schema'}{'types'}[5]{'fields'}[0]{'isDeprecated'}; $VAR1->{'data'}{'__schema'}{'types'}[13]{'fields'}[8]{'isDeprecated'} = $VAR1->{'data'}{'__schema'}{'types'}[5]{'fields'}[0]{'isDeprecated'}; $VAR1->{'data'}{'__schema'}{'types'}[14]{'enumValues'}[0]{'isDeprecated'} = $VAR1->{'data'}{'__schema'}{'types'}[5]{'fields'}[0]{'isDeprecated'}; $VAR1->{'data'}{'__schema'}{'types'}[14]{'enumValues'}[1]{'isDeprecated'} = $VAR1->{'data'}{'__schema'}{'types'}[5]{'fields'}[0]{'isDeprecated'}; $VAR1->{'data'}{'__schema'}{'types'}[14]{'enumValues'}[2]{'isDeprecated'} = $VAR1->{'data'}{'__schema'}{'types'}[5]{'fields'}[0]{'isDeprecated'}; $VAR1->{'data'}{'__schema'}{'types'}[14]{'enumValues'}[3]{'isDeprecated'} = $VAR1->{'data'}{'__schema'}{'types'}[5]{'fields'}[0]{'isDeprecated'}; $VAR1->{'data'}{'__schema'}{'types'}[14]{'enumValues'}[4]{'isDeprecated'} = $VAR1->{'data'}{'__schema'}{'types'}[5]{'fields'}[0]{'isDeprecated'}; $VAR1->{'data'}{'__schema'}{'types'}[14]{'enumValues'}[5]{'isDeprecated'} = $VAR1->{'data'}{'__schema'}{'types'}[5]{'fields'}[0]{'isDeprecated'}; $VAR1->{'data'}{'__schema'}{'types'}[14]{'enumValues'}[6]{'isDeprecated'} = $VAR1->{'data'}{'__schema'}{'types'}[5]{'fields'}[0]{'isDeprecated'}; $VAR1->{'data'}{'__schema'}{'types'}[14]{'enumValues'}[7]{'isDeprecated'} = $VAR1->{'data'}{'__schema'}{'types'}[5]{'fields'}[0]{'isDeprecated'}; GraphQL-0.53/t/language-parser.t0000644000175000017500000000344413212122202016362 0ustar osboxesosboxes#!perl use 5.014; use strict; use warnings; use Test::More; use Test::Exception; BEGIN { use_ok( 'GraphQL::Language::Parser', qw(parse) ) || print "Bail out!\n"; } dies_ok { parse('{') }; like $@->message, qr/Expected name/, 'trivial fail'; dies_ok { parse(<<'EOF' { ...MissingOn } fragment MissingOn Type EOF ) }; like $@->message, qr/Expected "on"/, 'missing "on"'; dies_ok { parse('{ field: {} }') }; like $@->message, qr/Expected name/, 'expected'; dies_ok { parse('notanoperation Foo { field }') }; like $@->message, qr/Parse document failed/, 'bad op'; dies_ok { parse('...') }; like $@->message, qr/Parse document failed/, 'spread wrong place'; lives_ok { parse('{ field(complex: { a: { b: [ $var ] } }) }') } 'parses variable inline values'; dies_ok { parse('query Foo($x: Complex = { a: { b: [ $var ] } }) { field }') }; like $@->message, qr/Expected name or constant/, 'no var in default values'; dies_ok { parse('fragment on on on { on }') }; like $@->message, qr/Unexpected Name "on"/, 'no accept fragments named "on"'; dies_ok { parse('{ ...on }') }; like $@->message, qr/Unexpected Name "on"/, 'no accept fragment spread named "on"'; my @nonKeywords = ( 'on', 'fragment', 'query', 'mutation', 'subscription', 'true', 'false', ); my %k2sub = (on => 'a'); for my $keyword (@nonKeywords) { my $fragmentName = $k2sub{$keyword} || $keyword; lives_ok { parse(<new->allow_nonref->canonical; sub make_schema { my ($field) = @_; GraphQL::Schema->new( query => GraphQL::Type::Object->new( name => 'Query', fields => { test => $field }, ), ); } subtest 'default function accesses properties', sub { my $schema = make_schema({ type => $String }); my $root_value = { test => 'testvalue' }; my $expected = { %$root_value }; # copy in case of mutations run_test([$schema, '{ test }', $root_value], { data => $expected }); done_testing; }; subtest 'default function calls methods', sub { my $schema = make_schema({ type => $String }); use constant SECRETVAL => 'secretValue'; { package MyTest1; sub new { bless { _secret => ::SECRETVAL }, shift; } sub test { shift->{_secret} } } my $root_value = MyTest1->new; is $root_value->test, SECRETVAL; # fingers and toes run_test([$schema, '{ test }', $root_value], { data => { test => SECRETVAL } }); done_testing; }; subtest 'default function passes args and context', sub { my $schema = make_schema({ type => $Int, args => { addend1 => { type => $Int } }, }); { package Adder; sub new { bless { _num => $_[1] }, $_[0]; } sub test { shift->{_num} + shift->{addend1} + shift->{addend2} } } my $root_value = Adder->new(700); is $root_value->test({ addend1 => 80 }, { addend2 => 9 }), 789; run_test( [$schema, '{ test(addend1: 80) }', $root_value, { addend2 => 9 }], { data => { test => 789 } }, ); done_testing; }; subtest 'uses provided resolve function', sub { my $schema = make_schema({ type => $String, args => { aStr => { type => $String }, aInt => { type => $Int } }, resolve => sub { my ($root_value, $args, $context, $info) = @_; $JSON->encode([$root_value, $args]); }, }); run_test([$schema, '{ test }'], { data => { test => '[null,{}]' } }); run_test([$schema, '{ test }', '!'], { data => { test => '["!",{}]' } }); run_test( [$schema, '{ test(aStr: "String!") }', 'Info'], { data => { test => '["Info",{"aStr":"String!"}]' } }, ); run_test( [$schema, '{ test(aStr: "String!", aInt: -123) }', 'Info'], { data => { test => '["Info",{"aInt":-123,"aStr":"String!"}]' } }, ); done_testing; }; done_testing; GraphQL-0.53/t/type-enum.t0000644000175000017500000001752214006150277015251 0ustar osboxesosboxesuse lib 't/lib'; use GQLTest; BEGIN { use_ok( 'GraphQL::Schema' ) || print "Bail out!\n"; use_ok( 'GraphQL::Type::Enum' ) || print "Bail out!\n"; use_ok( 'GraphQL::Type::Object' ) || print "Bail out!\n"; use_ok( 'GraphQL::Type::Scalar', qw($String $Int $Boolean) ) || print "Bail out!\n"; use_ok( 'GraphQL::Execution', qw(execute) ) || print "Bail out!\n"; use_ok( 'GraphQL::Introspection' ) || print "Bail out!\n"; } my $ColorType = GraphQL::Type::Enum->new( name => 'Color', values => { RED => { value => 0 }, GREEN => { value => 1 }, BLUE => { value => 2 }, }, ); my $Complex1 = { someRandomFunction => sub { } }; my $Complex2 = { someRandomValue => 123 }; my $ComplexEnum = GraphQL::Type::Enum->new( name => 'Complex', values => { ONE => { value => $Complex1 }, TWO => { value => $Complex2 }, }, ); my $QueryType = GraphQL::Type::Object->new( name => 'Query', fields => { colorEnum => { type => $ColorType, args => { fromEnum => { type => $ColorType }, fromInt => { type => $Int }, fromString => { type => $String }, }, resolve => sub { $_[1]->{fromInt} // $_[1]->{fromString} // $_[1]->{fromEnum}; }, }, colorInt => { type => $Int, args => { fromEnum => { type => $ColorType }, fromInt => { type => $Int }, }, resolve => sub { $_[1]->{fromInt} // $_[1]->{fromEnum}; }, }, complexEnum => { type => $ComplexEnum, args => { fromEnum => { type => $ComplexEnum, default_value => $Complex1, # internal not JSON }, provideGoodValue => { type => $Boolean }, provideBadValue => { type => $Boolean }, }, resolve => sub { return $Complex2 if $_[1]->{provideGoodValue}; return { %$Complex2 } if $_[1]->{provideBadValue}; # copy so not == $_[1]->{fromEnum}; }, }, } ); my $MutationType = GraphQL::Type::Object->new( name => 'Mutation', fields => { favoriteEnum => { type => $ColorType, args => { color => { type => $ColorType } }, resolve => sub { $_[1]->{color} }, }, }, ); my $SubscriptionType = GraphQL::Type::Object->new( name => 'Subscription', fields => { subscribeToEnum => { type => $ColorType, args => { color => { type => $ColorType } }, resolve => sub { $_[1]->{color} }, }, }, ); my $schema = GraphQL::Schema->new( query => $QueryType, mutation => $MutationType, subscription => $SubscriptionType, ); subtest 'accepts enum literals as input', sub { run_test( [$schema, '{ colorInt(fromEnum: GREEN) }'], { data => { colorInt => 1 } }, ); done_testing; }; subtest 'enum may be output type', sub { run_test( [$schema, '{ colorEnum(fromInt: 1) }'], { data => { colorEnum => 'GREEN' } }, ); done_testing; }; subtest 'enum may be both input and output type', sub { run_test( [$schema, '{ colorEnum(fromEnum: GREEN) }'], { data => { colorEnum => 'GREEN' } }, ); done_testing; }; subtest 'does not accept string literals', sub { run_test( [$schema, '{ colorEnum(fromEnum: "GREEN") }'], { data => { colorEnum => undef }, errors => [ { message => "Argument 'fromEnum' of type 'Color' was given 'GREEN' which is not enum value.", locations => [ { line => 1, column => 32 } ], path => [ 'colorEnum' ], } ] }, ); done_testing; }; subtest 'does not accept incorrect internal value', sub { run_test( [$schema, '{ colorEnum(fromString: "GREEN") }'], { data => { colorEnum => undef }, errors => [ { message => "Expected a value of type 'Color' but received: 'GREEN'.\n", locations => [ { line => 1, column => 34 } ], path => [ 'colorEnum' ], } ] }, ); done_testing; }; subtest 'does not accept internal value in place of enum literal', sub { run_test( [$schema, '{ colorEnum(fromEnum: 1) }'], { data => { colorEnum => undef }, errors => [ { message => "Argument 'fromEnum' of type 'Color' was given '1' which is not enum value.", locations => [ { line => 1, column => 26 } ], path => [ 'colorEnum' ], } ] }, ); done_testing; }; subtest 'does not accept enum literal in place of int', sub { run_test( [$schema, '{ colorEnum(fromInt: GREEN) }'], { data => { colorEnum => undef }, errors => [ { message => "Argument 'fromInt' of type 'Int' was given GREEN which is enum value.", locations => [ { line => 1, column => 29 } ], path => [ 'colorEnum' ], } ] }, ); done_testing; }; subtest 'accepts JSON string as enum variable', sub { run_test( [$schema, 'query test($color: Color!) { colorEnum(fromEnum: $color) }', undef, undef, { color => 'BLUE' }], { data => { colorEnum => 'BLUE' } }, ); done_testing; }; subtest 'accepts enum literals as input arguments to mutations', sub { run_test( [$schema, 'mutation x($color: Color!) { favoriteEnum(color: $color) }', undef, undef, { color => 'GREEN' }], { data => { favoriteEnum => 'GREEN' } }, ); done_testing; }; subtest 'accepts enum literals as input arguments to subscriptions', sub { run_test( [$schema, 'subscription x($color: Color!) { subscribeToEnum(color: $color) }', undef, undef, { color => 'GREEN' }], { data => { subscribeToEnum => 'GREEN' } }, ); done_testing; }; subtest 'does not accept internal value as enum variable', sub { run_test( [$schema, 'query test($color: Color!) { colorEnum(fromEnum: $color) }', undef, undef, { color => 2 }], { errors => [ { message => "Variable '\$color' got invalid value 2.\nExpected type 'Color', found 2.\n" } ] }, ); done_testing; }; subtest 'does not accept string variables as enum input', sub { run_test( [$schema, 'query test($color: String!) { colorEnum(fromEnum: $color) }', undef, undef, { color => 'BLUE' }], { data => { colorEnum => undef }, errors => [ { message => "Variable '\$color' of type 'String!' where expected 'Color'.", locations => [ { line => 1, column => 59 } ], path => [ 'colorEnum' ], } ] }, ); done_testing; }; subtest 'does not accept internal value variable as enum input', sub { run_test( [$schema, 'query test($color: Int!) { colorEnum(fromEnum: $color) }', undef, undef, { color => 2 }], { data => { colorEnum => undef }, errors => [ { message => "Variable '\$color' of type 'Int!' where expected 'Color'.", locations => [ { line => 1, column => 56 } ], path => [ 'colorEnum' ], } ] }, ); done_testing; }; subtest 'enum value may have an internal value of 0', sub { run_test( [$schema, '{ colorEnum(fromEnum: RED) colorInt(fromEnum: RED) }'], { data => { colorEnum => 'RED', colorInt => 0 } }, ); done_testing; }; subtest 'enum inputs may be nullable', sub { run_test( [$schema, '{ colorEnum colorInt }'], { data => { colorEnum => undef, colorInt => undef } }, ); done_testing; }; subtest 'presents a getValues() API for complex enums', sub { is_deeply $ComplexEnum->_name2value, { ONE => $Complex1, TWO => $Complex2, }; done_testing; }; subtest 'may be internally represented with complex values', sub { run_test( [$schema, '{ first: complexEnum second: complexEnum(fromEnum: TWO) good: complexEnum(provideGoodValue: true) bad: complexEnum(provideBadValue: true) }'], { data => { first => 'ONE', second => 'TWO', good => 'TWO', bad => undef, }, errors => [ { message => "Expected a value of type 'Complex' but received: HASH.\n", locations => [ { line => 6, column => 5 } ], path => [ 'bad' ], } ] }, ); done_testing; }; subtest 'can be introspected without error', sub { my $got = execute($schema, $GraphQL::Introspection::QUERY); ok !$got->{errors}, 'no query errors' or diag explain $got; done_testing; }; done_testing; GraphQL-0.53/t/type-definition.t0000644000175000017500000003165114114450657016441 0ustar osboxesosboxesuse 5.014; use strict; use warnings; use Test::More; use Test::Exception; BEGIN { use_ok( 'GraphQL::Plugin::Type::DateTime' ) || print "Bail out!\n"; use_ok( 'GraphQL::Type::Interface' ) || print "Bail out!\n"; use_ok( 'GraphQL::Type::Object' ) || print "Bail out!\n"; use_ok( 'GraphQL::Type::Enum' ) || print "Bail out!\n"; use_ok( 'GraphQL::Type::Union' ) || print "Bail out!\n"; use_ok( 'GraphQL::Type::InputObject' ) || print "Bail out!\n"; use_ok( 'GraphQL::Schema' ) || print "Bail out!\n"; use_ok( 'GraphQL::Type::Scalar', qw($String $Int $Boolean) ) || print "Bail out!\n"; } my $BlogImage = GraphQL::Type::Object->new( name => 'Image', fields => { url => { type => $String }, width => { type => $Int }, height => { type => $Int }, }, ); my $BlogArticle; my $BlogAuthor = GraphQL::Type::Object->new( name => 'Author', fields => sub { { id => { type => $String }, name => { type => $String }, pic => { args => { width => { type => $Int }, height => { type => $Int } }, type => $BlogImage, }, recentArticle => { type => $BlogArticle }, } }, ); $BlogArticle = GraphQL::Type::Object->new( name => 'Article', fields => { id => { type => $String }, isPublished => { type => $Boolean }, author => { type => $BlogAuthor }, title => { type => $String }, body => { type => $String }, }, ); my $BlogQuery = GraphQL::Type::Object->new( name => 'Query', fields => { article => { args => { width => { type => $Int }, height => { type => $Int } }, type => $BlogArticle, }, feed => { type => $BlogArticle->list }, }, ); my $BlogMutation = GraphQL::Type::Object->new( name => 'Mutation', fields => { writeArticle => { type => $BlogArticle, }, }, ); my $BlogSubscription = GraphQL::Type::Object->new( name => 'Subscription', fields => { articleSubscribe => { args => { id => { type => $String } }, type => $BlogArticle, }, }, ); my $ObjectType = GraphQL::Type::Object->new( name => 'Object', is_type_of => sub { 1 }, fields => {}, ); my $InterfaceType = GraphQL::Type::Interface->new( name => 'Interface', is_type_of => sub { 1 }, fields => {}, ); my $UnionType = GraphQL::Type::Union->new( name => 'Union', types => [ $ObjectType ], ); my $EnumType = GraphQL::Type::Enum->new( name => 'Enum', values => { foo => {} }, ); my $InputObjectType = GraphQL::Type::InputObject->new(name => 'InputObject', fields => {}); subtest 'Type System: Example', sub { my $schema = GraphQL::Schema->new(query => $BlogQuery); is $schema->query, $BlogQuery; my $article_field = $BlogQuery->fields->{article}; my $article_field_type = $article_field->{type}; is $article_field_type, $BlogArticle; is $article_field_type->name, 'Article'; my $title_field = $article_field_type->fields->{title}; is $title_field->{type}, $String; is $title_field->{type}->name, 'String'; my $author_field = $article_field_type->fields->{author}; my $author_field_type = $author_field->{type}; my $recent_article_field = $author_field_type->fields->{recentArticle}; is $recent_article_field->{type}, $BlogArticle; my $feed_field = $BlogQuery->fields->{feed}; is $feed_field->{type}->of, $BlogArticle; }; subtest 'defines a mutation schema', sub { my $schema = GraphQL::Schema->new(query => $BlogQuery, mutation => $BlogMutation); is $schema->mutation, $BlogMutation; my $write_mutation = $BlogMutation->fields->{writeArticle}; is $write_mutation->{type}, $BlogArticle; is $write_mutation->{type}->name, 'Article'; }; subtest 'defines a subscription schema', sub { my $schema = GraphQL::Schema->new(query => $BlogQuery, subscription => $BlogSubscription); is $schema->subscription, $BlogSubscription; my $sub = $BlogSubscription->fields->{articleSubscribe}; is $sub->{type}, $BlogArticle; is $sub->{type}->name, 'Article'; }; subtest 'defines an enum type with deprecated value', sub { my $EnumTypeWithDeprecatedValue = GraphQL::Type::Enum->new( name => 'EnumTypeWithDeprecatedValue', values => { foo => { deprecation_reason => 'Just because' } }, ); is_deeply $EnumTypeWithDeprecatedValue->values, { foo => { deprecation_reason => 'Just because', is_deprecated => 1, value => 'foo', }, }; }; subtest 'defines an enum type with a value of `undef`', sub { # var name from JS test, but Perl has no null only undef my $EnumTypeWithNullishValue = GraphQL::Type::Enum->new( name => 'EnumTypeWithNullishValue', values => { foo => {}, UNDEFINED => { value => undef }, }, ); is_deeply $EnumTypeWithNullishValue->values, { foo => { value => 'foo', }, UNDEFINED => { value => undef, }, }; }; subtest 'defines an object type with deprecated field', sub { my $TypeWithDeprecatedField = GraphQL::Type::Object->new( name => 'foo', fields => { bar => { type => $String, deprecation_reason => 'A terrible reason' }, }, ); is_deeply $TypeWithDeprecatedField->fields, { bar => { type => $String, deprecation_reason => 'A terrible reason', is_deprecated => 1, }, }; }; subtest 'define definitions including fields with directives', sub { my $TypeWithFieldWithDirective = GraphQL::Type::Object->new( name => 'foo', fields => { bar => { type => $String, directives => [ { name => 'directive', arguments => { arg => 'value' }, } ] } }, ); is_deeply $TypeWithFieldWithDirective->fields->{bar}->{directives}, [ { name => 'directive', arguments => { arg => 'value' }, } ]; my $InterfaceWithFieldWithDirective = GraphQL::Type::Interface->new( name => 'foo', fields => { bar => { type => $String, directives => [ { name => 'directive', arguments => { arg => 'value' }, } ] } }, ); is_deeply $InterfaceWithFieldWithDirective->fields->{bar}->{directives}, [ { name => 'directive', arguments => { arg => 'value' }, } ]; my $InputWithFieldWithDirective = GraphQL::Type::InputObject->new( name => 'foo', fields => { bar => { type => $String, directives => [ { name => 'directive', arguments => { arg => 'value' }, } ] } }, ); is_deeply $InputWithFieldWithDirective->fields->{bar}->{directives}, [ { name => 'directive', arguments => { arg => 'value' }, } ]; }; subtest 'includes nested input objects in the map', sub { my $NestedInputObject = GraphQL::Type::InputObject->new( name => 'NestedInputObject', fields => { value => { type => $String } }, ); my $SomeInputObject = GraphQL::Type::InputObject->new( name => 'SomeInputObject', fields => { nested => { type => $NestedInputObject } }, ); my $SomeMutation = GraphQL::Type::Object->new( name => 'SomeMutation', fields => { mutateSomething => { type => $BlogArticle, args => { input => { type => $SomeInputObject } }, }, }, ); my $SomeSubscription = GraphQL::Type::Object->new( name => 'SomeSubscription', fields => { subscribeToSomething => { type => $BlogArticle, args => { input => { type => $SomeInputObject } }, }, }, ); my $schema = GraphQL::Schema->new( query => $BlogQuery, mutation => $SomeMutation, subscription => $SomeSubscription, ); is_deeply [ sort keys %{$schema->name2type} ], [ qw(Article Author Boolean DateTime Float ID Image Int NestedInputObject Query SomeInputObject SomeMutation SomeSubscription String), qw(__Directive __DirectiveLocation __EnumValue __Field __InputValue __Schema __Type __TypeKind) ] or diag explain [ sort keys %{$schema->name2type} ]; }; subtest 'includes interfaces\' subtypes in the type map', sub { my $SomeInterface = GraphQL::Type::Interface->new( name => 'SomeInterface', fields => { f => { type => $Int } }, ); my $SomeSubtype = GraphQL::Type::Object->new( name => 'SomeSubtype', fields => { f => { type => $Int } }, interfaces => [ $SomeInterface ], is_type_of => sub { 1 }, ); my $query = GraphQL::Type::Object->new( name => 'Query', fields => { iface => { type => $SomeInterface } }, ); my $schema = GraphQL::Schema->new(query => $query, types => [ $SomeSubtype ]); is_deeply [ sort keys %{$schema->name2type} ], [ qw(Boolean Int Query SomeInterface SomeSubtype String), qw(__Directive __DirectiveLocation __EnumValue __Field __InputValue __Schema __Type __TypeKind) ] or diag explain [ sort keys %{$schema->name2type} ]; }; subtest 'includes interfaces\' thunk subtypes in the type map', sub { my $SomeInterface = GraphQL::Type::Interface->new( name => 'SomeInterface', fields => { f => { type => $Int } }, ); my $SomeSubtype = GraphQL::Type::Object->new( name => 'SomeSubtype', fields => { f => { type => $Int } }, interfaces => sub { [ $SomeInterface ] }, is_type_of => sub { 1 }, ); my $query = GraphQL::Type::Object->new( name => 'Query', fields => { iface => { type => $SomeInterface } }, ); my $schema = GraphQL::Schema->new(query => $query, types => [ $SomeSubtype ]); is_deeply [ sort keys %{$schema->name2type} ], [ qw(Boolean Int Query SomeInterface SomeSubtype String), qw(__Directive __DirectiveLocation __EnumValue __Field __InputValue __Schema __Type __TypeKind) ] or diag explain [ sort keys %{$schema->name2type} ]; }; # NB for now, not overloading stringification, but providing to_string method subtest 'stringifies simple types', sub { is $Int->to_string, 'Int'; is $BlogArticle->to_string, 'Article'; is $InterfaceType->to_string, 'Interface'; is $UnionType->to_string, 'Union'; is $EnumType->to_string, 'Enum'; is $InputObjectType->to_string, 'InputObject'; is($Int->non_null->to_string, 'Int!'); is($Int->list->to_string, '[Int]'); is($Int->list->non_null->to_string, '[Int]!'); is($Int->non_null->list->to_string, '[Int!]'); is($Int->list->list->to_string, '[[Int]]'); }; sub test_as_type { my ($type, $as, $should) = @_; $should = !!$should; map { my $got = !!$_->does("GraphQL::Role::$as"); is $got, $should, "$_ $as ($should)"; } $type, $type->list, $type->non_null; } subtest 'identifies input types', sub { test_as_type($Int, 'Input', 1); test_as_type($ObjectType, 'Input', ''); test_as_type($InterfaceType, 'Input', ''); test_as_type($UnionType, 'Input', ''); test_as_type($EnumType, 'Input', 1); test_as_type($InputObjectType, 'Input', 1); }; subtest 'identifies output types', sub { test_as_type($Int, 'Output', 1); test_as_type($ObjectType, 'Output', 1); test_as_type($InterfaceType, 'Output', 1); test_as_type($UnionType, 'Output', 1); test_as_type($EnumType, 'Output', 1); test_as_type($InputObjectType, 'Output', ''); }; subtest 'prohibits putting non-Object types in unions', sub { map { throws_ok { GraphQL::Type::Union->new( name => 'BadUnion', types => [ $_ ], )} qr/did not pass type constraint/ } ( $Int, $Int->non_null, $Int->list, $InterfaceType, $UnionType, $EnumType, $InputObjectType, ); }; subtest 'allows a thunk for Union\'s types', sub { my $u = GraphQL::Type::Union->new( name => 'ThunkUnion', types => sub { [ $ObjectType ] }, ); is_deeply $u->types, [ $ObjectType ]; }; subtest 'does not mutate passed field definitions', sub { my $fields = { field1 => { type => $String, deprecation_reason => 'because' }, field2 => { type => $String, args => { id => { type => $String } } }, }; my $o1 = GraphQL::Type::Object->new(name => 'O1', fields => $fields); my $o2 = GraphQL::Type::Object->new(name => 'O2', fields => $fields); is_deeply $o1->fields, $o2->fields; is_deeply $fields, { field1 => { type => $String, deprecation_reason => 'because' }, field2 => { type => $String, args => { id => { type => $String } } }, }; my $fields2 = { field1 => { type => $String }, field2 => { type => $String, default_value => 'hi' }, }; lives_ok { my $i1 = GraphQL::Type::InputObject->new(name => 'I1', fields => $fields2); my $i2 = GraphQL::Type::InputObject->new(name => 'I2', fields => $fields2); is_deeply $i1->fields, $i2->fields; is_deeply $fields2, { field1 => { type => $String }, field2 => { type => $String, default_value => 'hi' }, }; }; }; subtest 'check default value type', sub { throws_ok { GraphQL::Type::InputObject->new( name => 'I1', fields => { ifield1 => { type => $String }, ifield2 => { type => $Int, default_value => 'hi invalid' }, }, ) } qr/did not pass type constraint/; }; subtest 'all other thunks', sub { lives_ok { my $SomeInputObject = GraphQL::Type::InputObject->new( name => 'SomeInputObject', fields => sub { { nested => { type => $Int } } }, ); is_deeply $SomeInputObject->fields, { nested => { type => $Int } }; }; }; done_testing; GraphQL-0.53/t/type-schema.t0000644000175000017500000000252313326440552015542 0ustar osboxesosboxesuse 5.014; use strict; use warnings; use Test::More; use Test::Exception; BEGIN { use_ok( 'GraphQL::Type::Interface' ) || print "Bail out!\n"; use_ok( 'GraphQL::Type::Object' ) || print "Bail out!\n"; use_ok( 'GraphQL::Schema' ) || print "Bail out!\n"; use_ok( 'GraphQL::Type::Scalar', qw($String) ) || print "Bail out!\n"; } my $implementing_type; my $interface_type = GraphQL::Type::Interface->new( name => 'Interface', fields => { field_name => { type => $String } }, resolve_type => sub { return $implementing_type; }, ); $implementing_type = GraphQL::Type::Object->new( name => 'Object', interfaces => [ $interface_type ], fields => { field_name => { type => $String, resolve => sub { '' } }}, ); my @schema_args = ( query => GraphQL::Type::Object->new( name => 'Query', fields => { getObject => { type => $interface_type, resolve => sub { return {}; } } } ) ); my $schema = GraphQL::Schema->new(@schema_args); throws_ok { $schema->is_possible_type($interface_type, $implementing_type) } qr/not find possible implementing/, 'readable error if no types given'; $schema = GraphQL::Schema->new(@schema_args, types => [ $implementing_type ]); lives_and { ok $schema->is_possible_type($interface_type, $implementing_type) } 'no error if types given'; done_testing; GraphQL-0.53/t/lib/0000755000175000017500000000000014170415414013677 5ustar osboxesosboxesGraphQL-0.53/t/lib/GQLTest.pm0000644000175000017500000001250014006154243015514 0ustar osboxesosboxesuse strict; use warnings; use base 'Exporter'; my @IMPORT; BEGIN { @IMPORT = qw( strict warnings Test::More Test::Exception Test::Deep JSON::MaybeXS ); do { eval "use $_; 1" or die $@ } for @IMPORT; } use Data::Dumper; our @EXPORT = qw( run_test fake_promise_code promise_test ); sub import { my $target = caller; $target->export_to_level(1); $_->import::into(1) for @IMPORT; } sub run_test { my ($args, $expected, $force_promise) = @_; local $Test::Builder::Level = $Test::Builder::Level + 1; my @args = @$args; $args[7] ||= fake_promise_code() if !defined $force_promise or $force_promise; my $got = execute(@args); if (!defined $force_promise) { if (ref $got eq 'FakePromise') { $got = eval { $got->get }; is $@, '' or diag(explain $@), return; } } elsif ($force_promise) { isa_ok $got, 'FakePromise' or diag(explain $got), return; $got = eval { $got->get }; is $@, '' or diag(explain $@), return; } else { # specified did not want promise isnt ref($got), 'FakePromise' or return; } cmp_deeply $got, $expected or diag explain $got; } sub promise_test { my ($p, $fulfilled, $rejected) = @_; local $Test::Builder::Level = $Test::Builder::Level + 1; my $got = [ eval { $p->get } ]; is_deeply $got, $fulfilled or diag 'got unexpected result: ', explain $got; my $e = $@; is $e, $rejected or diag 'got unexpected error: ', explain $e; } sub fake_promise_code { +{ resolve => FakePromise->curry::resolve, reject => FakePromise->curry::reject, all => FakePromise->curry::all, new => FakePromise->curry::new, }; } sub fake_promise_iterator { require GraphQL::AsyncIterator; GraphQL::AsyncIterator->new( promise_code => fake_promise_code(), ); } { package FakePromise; use Moo; use GraphQL::PubSub; use curry; has status => (is => 'rw'); # status = undef/'fulfilled'/'rejected' has _values => (is => 'rw'); has parent => (is => 'ro'); has handlers => (is => 'ro'); has pubsub => (is => 'lazy', builder => sub { GraphQL::PubSub->new }); sub BUILD { my ($self, $args) = @_; if (my $parent = $args->{parent}) { $self->_get_or_subscribe($parent, $self->curry::settle); } } sub _get_or_subscribe { my ($self, $promise, $func) = @_; if (defined(my $status = $promise->status)) { # parent settled, copy now $func->($status, @{$promise->_values}); } else { $promise->pubsub->subscribe(settle => $func); } } sub resolve { my $self = shift; $self = $self->new if !ref $self; $self->settle('fulfilled', @_); $self; } sub reject { my $self = shift; $self = $self->new if !ref $self; $self->settle('rejected', @_); $self; } sub all { my $self = shift; die "all is a class method only" if ref $self; $self = $self->new; my ($i, @values) = (0); my $unsettled = grep ref($_) eq __PACKAGE__, @_; my @promise_deferral; # till after @values filled, avoid prematurely settle for my $v (@_) { if (ref(my $promise = $v) eq __PACKAGE__) { my $this_value_index = $i; push @values, undef; push @promise_deferral, [ $promise, sub { my ($status, @these_vals) = @_; if ($status eq 'rejected') { $self->settle($status, @these_vals); } elsif (!defined $self->status) { # if it IS defined, we already got rejected so it's over $values[$this_value_index] = \@these_vals; $unsettled--; if ($unsettled <= 0) { $self->settle('fulfilled', @values); } } } ]; } else { push @values, [ $v ]; } $i++; } $self->_get_or_subscribe(@$_) for @promise_deferral; $self; } sub then { my $self = shift; $self->new(parent => $self, handlers => +{ then => shift, catch => shift }); } sub catch { shift->then(undef, @_) } sub _safe_call { my @r = eval { $_[0]->() }; $@ ? ('rejected', $@) : ('fulfilled', @r); } sub _settled_with_promise { my ($self, $value) = @_; return 0 if ref($value) ne __PACKAGE__; $self->_get_or_subscribe($value, $self->curry::settle); 1; } sub settle { my $self = shift; die "Error: tried to settle an already-settled promise" if defined $self->status; my ($status, @values) = @_; return if $self->_settled_with_promise($values[0]); if (my $h = delete $self->{handlers}) { # zap as no longer needed, might get rerun if was settled with promise if ($status eq 'fulfilled' and $h->{then}) { ($status, @values) = _safe_call(sub { $h->{then}->(@values) }); return if $self->_settled_with_promise($values[0]); } if ($status eq 'rejected' and $h->{catch}) { ($status, @values) = _safe_call(sub { $h->{catch}->(@values) }); return if $self->_settled_with_promise($values[0]); } } $self->status($status); $self->_values(\@values); $self->pubsub->publish(settle => $status, @values) if $self->{pubsub}; } sub get { my $self = shift; die "Error: tried to 'get' a non-settled promise" if !defined $self->status; my @values = @{$self->_values}; die @values if $self->status eq 'rejected'; die "Tried to scalar-get but >1 value" if !wantarray and @values > 1; return $values[0] if !wantarray; @values; } } 1; GraphQL-0.53/t/type-interface.t0000644000175000017500000000112713326440550016237 0ustar osboxesosboxesuse 5.014; use strict; use warnings; use Test::More; use Test::Exception; BEGIN { use_ok( 'GraphQL::Type::Interface' ) || print "Bail out!\n"; use_ok( 'GraphQL::Type::Scalar', qw($String) ) || print "Bail out!\n"; } my $interface_type = GraphQL::Type::Interface->new( name => 'Interface', fields => { field_name => { type => $String } }, resolve_type => sub { return $String; }, ); throws_ok { GraphQL::Type::Interface->new( name => '@Interface', fields => { field_name => { type => $String } }, ) } qr/did not pass type constraint/, 'name validation'; done_testing; GraphQL-0.53/t/language-schema-parser.t0000644000175000017500000003326113433564611017643 0ustar osboxesosboxesuse lib 't/lib'; use GQLTest; BEGIN { use_ok( 'GraphQL::Language::Parser', qw(parse) ) || print "Bail out!\n"; } lives_ok { parse('type Hello { world: String }') } 'simple schema'; lives_ok { parse('extend type Hello { world: String }') } 'simple extend'; lives_ok { parse('type Hello { world: String! }') } 'non-null'; lives_ok { parse('type Hello implements World') } 'implements'; lives_ok { parse('type Hello implements Wo & rld') } 'implements multi &'; lives_ok { parse('type Hello implements & Wo & rld') } 'implements multi and leading &'; lives_ok { parse('enum Hello { WORLD }') } 'single enum'; lives_ok { parse('enum Hello { WO, RLD }') } 'multi enum'; dies_ok { parse('enum Hello { true }') }; like $@->message, qr/Invalid enum value/, 'invalid enum'; dies_ok { parse('enum Hello { false }') }; like $@->message, qr/Invalid enum value/, 'invalid enum'; dies_ok { parse('enum Hello { null }') }; like $@->message, qr/Invalid enum value/, 'invalid enum'; lives_ok { parse('interface Hello { world: String }') } 'simple interface'; lives_ok { parse('type Hello { world(flag: Boolean): String }') } 'type with arg'; lives_ok { parse('type Hello { world(flag: Boolean = true): String }') } 'type with default arg'; lives_ok { parse('type Hello { world(things: [String]): String }') } 'type with list arg'; lives_ok { parse('type Hello { world(argOne: Boolean, argTwo: Int): String }') } 'type with two args'; lives_ok { parse('union Hello = World') } 'simple union'; lives_ok { parse('union Hello = Wo | Rld') } 'union of two'; lives_ok { parse('scalar Hello') } 'scalar'; lives_ok { parse('input Hello { world: String }') } 'simple input'; dies_ok { parse('input Hello { world(foo: Int): String }') }; like $@->message, qr/Parse document failed/, 'input with arg should fail'; open my $fh, '<', 't/schema-kitchen-sink.graphql'; lives_ok { my $got = parse(join('', <$fh>)); my $expected_text = join '', ; $expected_text =~ s#bless\(\s*do\{\\\(my\s*\$o\s*=\s*(.)\)\},\s*'JSON::PP::Boolean'\s*\)#'JSON->' . ($1 ? 'true' : 'false')#ge; my $expected = eval $expected_text; #open $fh, '>', 'tf'; print $fh explain $got; # uncomment this line to regen is_deeply $got, $expected, 'lex big doc correct' or diag explain $got; } 'parse lives' or diag explain $@; done_testing; __DATA__ [ { 'kind' => 'schema', 'location' => { 'column' => 0, 'line' => 9 }, 'mutation' => 'MutationType', 'query' => 'QueryType' }, { 'description' => 'This is a description of the `Foo` type.', 'fields' => { 'five' => { 'args' => { 'argument' => { 'default_value' => [ 'string', 'string' ], 'type' => [ 'list', { 'type' => 'String' } ] } }, 'type' => 'String' }, 'four' => { 'args' => { 'argument' => { 'default_value' => 'string', 'type' => 'String' } }, 'type' => 'String' }, 'one' => { 'description' => 'Description of the `one` field.', 'type' => 'Type' }, 'seven' => { 'args' => { 'argument' => { 'default_value' => undef, 'type' => 'Int' } }, 'type' => 'Type' }, 'six' => { 'args' => { 'argument' => { 'default_value' => { 'key' => 'value' }, 'type' => 'InputType' } }, 'type' => 'Type' }, 'three' => { 'args' => { 'argument' => { 'type' => 'InputType' }, 'other' => { 'type' => 'String' } }, 'description' => 'This is a description of the `three` field.', 'type' => 'Int' }, 'two' => { 'args' => { 'argument' => { 'description' => 'This is a description of the `argument` argument.', 'type' => [ 'non_null', { 'type' => 'InputType' } ] } }, 'description' => 'This is a description of the `two` field.', 'type' => 'Type' } }, 'interfaces' => [ 'Bar', 'Baz' ], 'kind' => 'type', 'location' => { 'column' => 0, 'line' => 33 }, 'name' => 'Foo' }, { 'directives' => [ { 'arguments' => { 'arg' => 'value' }, 'name' => 'onObject' } ], 'fields' => { 'annotatedField' => { 'args' => { 'arg' => { 'default_value' => 'default', 'directives' => [ { 'name' => 'onArgumentDefinition' } ], 'type' => 'Type' } }, 'directives' => [ { 'name' => 'onField' } ], 'type' => 'Type' } }, 'kind' => 'type', 'location' => { 'column' => 0, 'line' => 37 }, 'name' => 'AnnotatedObject' }, { 'fields' => {}, 'kind' => 'type', 'location' => { 'column' => 1, 'line' => 41 }, 'name' => 'UndefinedType' }, { 'fields' => { 'seven' => { 'args' => { 'argument' => { 'type' => [ 'list', { 'type' => 'String' } ] } }, 'type' => 'Type' } }, 'kind' => 'type', 'location' => { 'column' => 0, 'line' => 43 }, 'name' => 'Foo' }, { 'directives' => [ { 'name' => 'onType' } ], 'fields' => {}, 'kind' => 'type', 'location' => { 'column' => 0, 'line' => 45 }, 'name' => 'Foo' }, { 'fields' => { 'four' => { 'args' => { 'argument' => { 'default_value' => 'string', 'type' => 'String' } }, 'type' => 'String' }, 'one' => { 'type' => 'Type' } }, 'kind' => 'interface', 'location' => { 'column' => 0, 'line' => 50 }, 'name' => 'Bar' }, { 'directives' => [ { 'name' => 'onInterface' } ], 'fields' => { 'annotatedField' => { 'args' => { 'arg' => { 'directives' => [ { 'name' => 'onArgumentDefinition' } ], 'type' => 'Type' } }, 'directives' => [ { 'name' => 'onField' } ], 'type' => 'Type' } }, 'kind' => 'interface', 'location' => { 'column' => 0, 'line' => 54 }, 'name' => 'AnnotatedInterface' }, { 'fields' => {}, 'kind' => 'interface', 'location' => { 'column' => 1, 'line' => 58 }, 'name' => 'UndefinedInterface' }, { 'fields' => { 'two' => { 'args' => { 'argument' => { 'type' => [ 'non_null', { 'type' => 'InputType' } ] } }, 'type' => 'Type' } }, 'kind' => 'interface', 'location' => { 'column' => 0, 'line' => 60 }, 'name' => 'Bar' }, { 'directives' => [ { 'name' => 'onInterface' } ], 'fields' => {}, 'kind' => 'interface', 'location' => { 'column' => 0, 'line' => 62 }, 'name' => 'Bar' }, { 'kind' => 'union', 'location' => { 'column' => 1, 'line' => 69 }, 'name' => 'Feed', 'types' => [ 'Story', 'Article', 'Advert' ] }, { 'directives' => [ { 'name' => 'onUnion' } ], 'kind' => 'union', 'location' => { 'column' => 1, 'line' => 71 }, 'name' => 'AnnotatedUnion', 'types' => [ 'A', 'B' ] }, { 'directives' => [ { 'name' => 'onUnion' } ], 'kind' => 'union', 'location' => { 'column' => 1, 'line' => 73 }, 'name' => 'AnnotatedUnionTwo', 'types' => [ 'A', 'B' ] }, { 'kind' => 'union', 'location' => { 'column' => 1, 'line' => 75 }, 'name' => 'UndefinedUnion' }, { 'kind' => 'union', 'location' => { 'column' => 1, 'line' => 77 }, 'name' => 'Feed', 'types' => [ 'Photo', 'Video' ] }, { 'directives' => [ { 'name' => 'onUnion' } ], 'kind' => 'union', 'location' => { 'column' => 0, 'line' => 77 }, 'name' => 'Feed' }, { 'kind' => 'scalar', 'location' => { 'column' => 1, 'line' => 81 }, 'name' => 'CustomScalar' }, { 'directives' => [ { 'name' => 'onScalar' } ], 'kind' => 'scalar', 'location' => { 'column' => 0, 'line' => 81 }, 'name' => 'AnnotatedScalar' }, { 'directives' => [ { 'name' => 'onScalar' } ], 'kind' => 'scalar', 'location' => { 'column' => 0, 'line' => 83 }, 'name' => 'CustomScalar' }, { 'kind' => 'enum', 'location' => { 'column' => 0, 'line' => 96 }, 'name' => 'Site', 'values' => { 'DESKTOP' => { 'description' => 'This is a description of the `DESKTOP` value' }, 'MOBILE' => { 'description' => 'This is a description of the `MOBILE` value' }, 'WEB' => { 'description' => 'This is a description of the `WEB` value' } } }, { 'directives' => [ { 'name' => 'onEnum' } ], 'kind' => 'enum', 'location' => { 'column' => 0, 'line' => 101 }, 'name' => 'AnnotatedEnum', 'values' => { 'ANNOTATED_VALUE' => { 'directives' => [ { 'name' => 'onEnumValue' } ] }, 'OTHER_VALUE' => {} } }, { 'kind' => 'enum', 'location' => { 'column' => 1, 'line' => 105 }, 'name' => 'UndefinedEnum', 'values' => {} }, { 'kind' => 'enum', 'location' => { 'column' => 0, 'line' => 107 }, 'name' => 'Site', 'values' => { 'VR' => {} } }, { 'directives' => [ { 'name' => 'onEnum' } ], 'kind' => 'enum', 'location' => { 'column' => 0, 'line' => 109 }, 'name' => 'Site', 'values' => {} }, { 'fields' => { 'answer' => { 'default_value' => 42, 'type' => 'Int' }, 'key' => { 'type' => [ 'non_null', { 'type' => 'String' } ] } }, 'kind' => 'input', 'location' => { 'column' => 0, 'line' => 114 }, 'name' => 'InputType' }, { 'directives' => [ { 'name' => 'onInputObject' } ], 'fields' => { 'annotatedField' => { 'directives' => [ { 'name' => 'onInputFieldDefinition' } ], 'type' => 'Type' } }, 'kind' => 'input', 'location' => { 'column' => 0, 'line' => 118 }, 'name' => 'AnnotatedInput' }, { 'fields' => {}, 'kind' => 'input', 'location' => { 'column' => 1, 'line' => 122 }, 'name' => 'UndefinedInput' }, { 'fields' => { 'other' => { 'default_value' => 12300, 'directives' => [ { 'name' => 'onInputFieldDefinition' } ], 'type' => 'Float' } }, 'kind' => 'input', 'location' => { 'column' => 0, 'line' => 124 }, 'name' => 'InputType' }, { 'directives' => [ { 'name' => 'onInputObject' } ], 'fields' => {}, 'kind' => 'input', 'location' => { 'column' => 0, 'line' => 126 }, 'name' => 'InputType' }, { 'args' => { 'if' => { 'directives' => [ { 'name' => 'onArgumentDefinition' } ], 'type' => [ 'non_null', { 'type' => 'Boolean' } ] } }, 'description' => 'This is a description of the `@skip` directive', 'kind' => 'directive', 'location' => { 'column' => 0, 'line' => 133 }, 'locations' => [ 'FIELD', 'FRAGMENT_SPREAD', 'INLINE_FRAGMENT' ], 'name' => 'skip' }, { 'args' => { 'if' => { 'type' => [ 'non_null', { 'type' => 'Boolean' } ] } }, 'kind' => 'directive', 'location' => { 'column' => 0, 'line' => 138 }, 'locations' => [ 'FIELD', 'FRAGMENT_SPREAD', 'INLINE_FRAGMENT' ], 'name' => 'include' }, { 'args' => { 'if' => { 'type' => [ 'non_null', { 'type' => 'Boolean' } ] } }, 'kind' => 'directive', 'location' => { 'column' => 0, 'line' => 143 }, 'locations' => [ 'FIELD', 'FRAGMENT_SPREAD', 'INLINE_FRAGMENT' ], 'name' => 'include2' }, { 'directives' => [ { 'name' => 'onSchema' } ], 'kind' => 'schema', 'location' => { 'column' => 0, 'line' => 145 } }, { 'directives' => [ { 'name' => 'onSchema' } ], 'kind' => 'schema', 'location' => { 'column' => 0, 'line' => 149 }, 'subscription' => 'SubscriptionType' } ] GraphQL-0.53/t/execution-directives.t0000644000175000017500000000712313217121260017455 0ustar osboxesosboxesuse lib 't/lib'; use GQLTest; my $JSON = JSON::MaybeXS->new->allow_nonref->canonical; BEGIN { use_ok( 'GraphQL::Schema' ) || print "Bail out!\n"; use_ok( 'GraphQL::Type::Object' ) || print "Bail out!\n"; use_ok( 'GraphQL::Type::Scalar', qw($String $Int $Boolean) ) || print "Bail out!\n"; use_ok( 'GraphQL::Execution', qw(execute) ) || print "Bail out!\n"; use_ok( 'GraphQL::Language::Parser', qw(parse) ) || print "Bail out!\n"; } my $schema = GraphQL::Schema->new( query => GraphQL::Type::Object->new( name => 'TestType', fields => { a => { type => $String }, b => { type => $String }, }, ), ); my %data = ( a => sub { 'a' }, b => sub { 'b' }, ); subtest 'works without directives' => sub { run_test([$schema, '{ a, b }', \%data], { data => { a => 'a', b => 'b' } }); }; subtest 'works on scalars' => sub { run_test([$schema, '{ a, b @include(if: true) }', \%data], { data => { a => 'a', b => 'b' } }); run_test([$schema, '{ a, b @include(if: false) }', \%data], { data => { a => 'a' } }); run_test([$schema, '{ a, b @skip(if: false) }', \%data], { data => { a => 'a', b => 'b' } }); run_test([$schema, '{ a, b @skip(if: true) }', \%data], { data => { a => 'a' } }); }; subtest 'works on fragment spreads' => sub { my $q; $q = <<'EOQ'; query Q { a ...Frag @include(if: false) } fragment Frag on TestType { b } EOQ run_test([$schema, $q, \%data], { data => { a => 'a' } }); $q = <<'EOQ'; query Q { a ...Frag @include(if: true) } fragment Frag on TestType { b } EOQ run_test([$schema, $q, \%data], { data => { a => 'a', b => 'b' } }); $q = <<'EOQ'; query Q { a ...Frag @skip(if: false) } fragment Frag on TestType { b } EOQ run_test([$schema, $q, \%data], { data => { a => 'a', b => 'b' } }); $q = <<'EOQ'; query Q { a ...Frag @skip(if: true) } fragment Frag on TestType { b } EOQ run_test([$schema, $q, \%data], { data => { a => 'a' } }); }; subtest 'works on inline fragment' => sub { my $q; $q = <<'EOQ'; query Q { a ... on TestType @include(if: false) { b } } EOQ run_test([$schema, $q, \%data], { data => { a => 'a' } }); $q = <<'EOQ'; query Q { a ... on TestType @include(if: true) { b } } EOQ run_test([$schema, $q, \%data], { data => { a => 'a', b => 'b' } }); $q = <<'EOQ'; query Q { a ... on TestType @skip(if: false) { b } } EOQ run_test([$schema, $q, \%data], { data => { a => 'a', b => 'b' } }); $q = <<'EOQ'; query Q { a ... on TestType @skip(if: true) { b } } EOQ run_test([$schema, $q, \%data], { data => { a => 'a' } }); }; subtest 'works on anonymous inline fragment' => sub { my $q; $q = <<'EOQ'; query Q { a ... @include(if: false) { b } } EOQ run_test([$schema, $q, \%data], { data => { a => 'a' } }); $q = <<'EOQ'; query Q { a ... @include(if: true) { b } } EOQ run_test([$schema, $q, \%data], { data => { a => 'a', b => 'b' } }); $q = <<'EOQ'; query Q { a ... @skip(if: false) { b } } EOQ run_test([$schema, $q, \%data], { data => { a => 'a', b => 'b' } }); $q = <<'EOQ'; query Q { a ... @skip(if: true) { b } } EOQ run_test([$schema, $q, \%data], { data => { a => 'a' } }); }; subtest 'works with skip and include directives' => sub { run_test([$schema, '{ a, b @include(if: true) @skip(if: false) }', \%data], { data => { a => 'a', b => 'b' } }); run_test([$schema, '{ a, b @include(if: true) @skip(if: true) }', \%data], { data => { a => 'a' } }); run_test([$schema, '{ a, b @include(if: false) @skip(if: false) }', \%data], { data => { a => 'a' } }); }; done_testing; GraphQL-0.53/t/00-report-prereqs.t0000644000175000017500000001350613212122202016514 0ustar osboxesosboxes#!perl use strict; use warnings; # This test was generated by Dist::Zilla::Plugin::Test::ReportPrereqs 0.020 # THEN modified with more info by Ed J for PDL project use Test::More tests => 1; use ExtUtils::MakeMaker; use File::Spec; # from $version::LAX my $lax_version_re = qr/(?: undef | (?: (?:[0-9]+) (?: \. | (?:\.[0-9]+) (?:_[0-9]+)? )? | (?:\.[0-9]+) (?:_[0-9]+)? ) | (?: v (?:[0-9]+) (?: (?:\.[0-9]+)+ (?:_[0-9]+)? )? | (?:[0-9]+)? (?:\.[0-9]+){2,} (?:_[0-9]+)? ) )/x; # hide optional CPAN::Meta modules from prereq scanner # and check if they are available my $cpan_meta = "CPAN::Meta"; my $cpan_meta_pre = "CPAN::Meta::Prereqs"; my $HAS_CPAN_META = eval "require $cpan_meta; $cpan_meta->VERSION('2.120900')" && eval "require $cpan_meta_pre"; ## no critic # Verify requirements? my $DO_VERIFY_PREREQS = 1; sub _max { my $max = shift; $max = ( $_ > $max ) ? $_ : $max for @_; return $max; } sub _merge_prereqs { my ($collector, $prereqs) = @_; # CPAN::Meta::Prereqs object if (ref $collector eq $cpan_meta_pre) { return $collector->with_merged_prereqs( CPAN::Meta::Prereqs->new( $prereqs ) ); } # Raw hashrefs for my $phase ( keys %$prereqs ) { for my $type ( keys %{ $prereqs->{$phase} } ) { for my $module ( keys %{ $prereqs->{$phase}{$type} } ) { $collector->{$phase}{$type}{$module} = $prereqs->{$phase}{$type}{$module}; } } } return $collector; } my @include = qw( ); my @exclude = qw( ); # Add static prereqs to the included modules list my $static_prereqs = do 't/00-report-prereqs.dd'; # Merge all prereqs (either with ::Prereqs or a hashref) my $full_prereqs = _merge_prereqs( ( $HAS_CPAN_META ? $cpan_meta_pre->new : {} ), $static_prereqs ); # Add dynamic prereqs to the included modules list (if we can) my ($source) = grep { -f } 'MYMETA.json', 'MYMETA.yml'; if ( $source && $HAS_CPAN_META ) { if ( my $meta = eval { CPAN::Meta->load_file($source) } ) { $full_prereqs = _merge_prereqs($full_prereqs, $meta->prereqs); } } else { $source = 'static metadata'; } my @full_reports; my @dep_errors; my $req_hash = $HAS_CPAN_META ? $full_prereqs->as_string_hash : $full_prereqs; # Add static includes into a fake section for my $mod (@include) { $req_hash->{other}{modules}{$mod} = 0; } for my $phase ( qw(configure build test runtime develop other) ) { next unless $req_hash->{$phase}; next if ($phase eq 'develop' and not $ENV{AUTHOR_TESTING}); for my $type ( qw(requires recommends suggests conflicts modules) ) { next unless $req_hash->{$phase}{$type}; my $title = ucfirst($phase).' '.ucfirst($type); my @reports = [qw/Module Want Have Where Howbig/]; for my $mod ( sort keys %{ $req_hash->{$phase}{$type} } ) { next if $mod eq 'perl'; next if grep { $_ eq $mod } @exclude; my $file = $mod; $file =~ s{::}{/}g; $file .= ".pm"; my ($prefix) = grep { -e File::Spec->catfile($_, $file) } @INC; my $want = $req_hash->{$phase}{$type}{$mod}; $want = "undef" unless defined $want; $want = "any" if !$want && $want == 0; my $req_string = $want eq 'any' ? 'any version required' : "version '$want' required"; if ($prefix) { my $filename = File::Spec->catfile($prefix, $file); my $have = MM->parse_version( $filename ); $have = "undef" unless defined $have; push @reports, [$mod, $want, $have, $prefix, (-s $filename)]; if ( $DO_VERIFY_PREREQS && $HAS_CPAN_META && $type eq 'requires' ) { if ( $have !~ /\A$lax_version_re\z/ ) { push @dep_errors, "$mod version '$have' cannot be parsed ($req_string)"; } elsif ( ! $full_prereqs->requirements_for( $phase, $type )->accepts_module( $mod => $have ) ) { push @dep_errors, "$mod version '$have' is not in required range '$want'"; } } } else { push @reports, [$mod, $want, "missing", '', 0]; if ( $DO_VERIFY_PREREQS && $type eq 'requires' ) { push @dep_errors, "$mod is not installed ($req_string)"; } } } if ( @reports ) { push @full_reports, "=== $title ===\n\n"; my $ml = _max( map { length $_->[0] } @reports ); my $wl = _max( map { length $_->[1] } @reports ); my $hl = _max( map { length $_->[2] } @reports ); my $ll = _max( map { length $_->[3] } @reports ); # location my $sl = _max( map { length $_->[4] } @reports ); # size if ($type eq 'modules') { splice @reports, 1, 0, ["-" x $ml, "", "-" x $hl, "-" x $ll, "-" x $sl]; push @full_reports, map { sprintf(" %*s %*s\n", -$ml, $_->[0], $hl, $_->[2]) } @reports; } else { splice @reports, 1, 0, ["-" x $ml, "-" x $wl, "-" x $hl, "-" x $ll, "-" x $sl]; push @full_reports, map { sprintf(" %*s %*s %*s %*s %*s\n", -$ml, $_->[0], $wl, $_->[1], $hl, $_->[2], -$ll, $_->[3], $sl, $_->[4]) } @reports; } push @full_reports, "\n"; } } } if ( @full_reports ) { diag "\nVersions for all modules listed in $source (including optional ones):\n\n", @full_reports; } if ( @dep_errors ) { diag join("\n", "\n*** WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING ***\n", "The following REQUIRED prerequisites were not satisfied:\n", @dep_errors, "\n" ); } pass; # vim: ts=4 sts=4 sw=4 et: GraphQL-0.53/t/language-lexer.t0000644000175000017500000003437313426211267016233 0ustar osboxesosboxesuse lib 't/lib'; use GQLTest; use GraphQL::Language::Parser qw(parse); open my $fh, '<', 't/kitchen-sink.graphql'; my $got = parse(join('', <$fh>)); lives_ok { my $expected_text = join '', ; $expected_text =~ s#bless\(\s*do\{\\\(my\s*\$o\s*=\s*(.)\)\},\s*'JSON::PP::Boolean'\s*\)#'JSON->' . ($1 ? 'true' : 'false')#ge; my $expected = eval $expected_text; #open $fh, '>', 'tf'; print $fh explain $got; # uncomment to regenerate is_deeply $got, $expected, 'lex big doc correct' or diag explain $got; } or diag explain $@; dies_ok { parse("\x{0007}") }; like $@->message, qr/Parse document failed for some reason/, 'invalid char'; lives_ok { parse("\x{FEFF} query foo { id }") } 'accepts BOM'; dies_ok { parse("\n\n ? \n\n\n") }; is_deeply [ map $@->locations->[0]->{$_}, qw(line column) ], [3,5], 'error respects whitespace'; $got = parse(string_make(' x ')); is string_lookup($got), ' x ', 'string preserve whitespace' or diag explain $got; $got = parse(string_make('quote \\"')); is string_lookup($got), 'quote "', 'string quote kept' or diag explain $got; dies_ok { parse(string_make('quote \\')) }; is_deeply [map $@->locations->[0]->{$_}, qw(line column)], [1,21], 'error on unterminated string'; dies_ok { parse(q(query q { foo(name: 'hello') { id } })) }; is_deeply [map $@->locations->[0]->{$_}, qw(line column)], [1,21], 'error on single quote'; dies_ok { parse("\x{0007}") }; is_deeply [map $@->locations->[0]->{$_}, qw(line column)], [1,1], 'error on invalid char'; dies_ok { parse(string_make("\x{0000}")) }; is_deeply [map $@->locations->[0]->{$_}, qw(line column)], [1,21], 'error on NUL char'; dies_ok { parse(string_make("hi\nthere")) }; is_deeply [map $@->locations->[0]->{$_}, qw(line column)], [1,21], 'error on multi-line string'; dies_ok { parse(string_make("hi\rthere")) }; is_deeply [map $@->locations->[0]->{$_}, qw(line column)], [1,21], 'error on MacOS multi-line string'; dies_ok { parse(string_make('\z')) }; is_deeply [map $@->locations->[0]->{$_}, qw(line column)], [1,21], 'error on invalid escape'; dies_ok { parse(string_make('bad \\x esc')) }; is_deeply [map $@->locations->[0]->{$_}, qw(line column)], [1,21], 'error on invalid escape'; dies_ok { parse(string_make('bad \\u1 esc')) }; is_deeply [map $@->locations->[0]->{$_}, qw(line column)], [1,21], 'error on invalid escape'; dies_ok { parse(string_make('bad \\u0XX1 esc')) }; is_deeply [map $@->locations->[0]->{$_}, qw(line column)], [1,21], 'error on invalid escape'; dies_ok { parse(string_make('bad \\uXXXX esc')) }; is_deeply [map $@->locations->[0]->{$_}, qw(line column)], [1,21], 'error on invalid escape'; dies_ok { parse(string_make('bad \\uFXXX esc')) }; is_deeply [map $@->locations->[0]->{$_}, qw(line column)], [1,21], 'error on invalid escape'; dies_ok { parse(string_make('bad \\uXXXF esc')) }; is_deeply [map $@->locations->[0]->{$_}, qw(line column)], [1,21], 'error on invalid escape'; number_test('4', 'int', 'simple int'); number_test('4.123', 'float', 'simple float'); number_test('9', 'int', 'simple int'); number_test('0', 'int', 'simple int'); number_test('-4.123', 'float', 'negative float'); number_test('0.123', 'float', 'simple float 0'); number_test('123e4', 'float', 'float exp lower'); number_test('123E4', 'float', 'float exp upper'); number_test('123e-4', 'float', 'float negexp lower'); number_test('123e+4', 'float', 'float posexp lower'); number_test('-1.123e4', 'float', 'neg float exp lower'); number_test('-1.123E4', 'float', 'neg float exp upper'); number_test('-1.123e-4', 'float', 'neg float negexp lower'); number_test('-1.123e+4', 'float', 'neg float posexp lower'); number_test('-1.123e4567', 'float', 'neg float longexp lower'); dies_ok { parse(number_make('00')) }; is_deeply [map $@->locations->[0]->{$_}, qw(line column)], [1,22], 'error on invalid int'; dies_ok { parse(number_make('+1')) }; is_deeply [map $@->locations->[0]->{$_}, qw(line column)], [1,21], 'error on invalid int'; dies_ok { parse(number_make('1.')) }; is_deeply [map $@->locations->[0]->{$_}, qw(line column)], [1,22], 'error on invalid int'; dies_ok { parse(number_make('.123')) }; is_deeply [map $@->locations->[0]->{$_}, qw(line column)], [1,21], 'error on invalid float'; dies_ok { parse(number_make('1.A')) }; is_deeply [map $@->locations->[0]->{$_}, qw(line column)], [1,22], 'error on invalid int'; dies_ok { parse(number_make('-A')) }; is_deeply [map $@->locations->[0]->{$_}, qw(line column)], [1,21], 'error on invalid int'; dies_ok { parse(number_make('1.0e')) }; is_deeply [map $@->locations->[0]->{$_}, qw(line column)], [1,25], 'error on invalid int'; dies_ok { parse(number_make('1.0eA')) }; is_deeply [map $@->locations->[0]->{$_}, qw(line column)], [1,26], 'error on invalid int'; my $multibyte = "Has a \x{0A0A} multi-byte character."; $got = parse(string_make($multibyte)); is string_lookup($got), $multibyte, 'multibyte kept' or diag explain $got; done_testing; sub number_test { my ($text, $type, $label) = @_; my $got = parse(number_make($text)); cmp_ok query_lookup($got, $type), '==', $text, $label or diag explain $got; } sub number_make { my ($text) = @_; return query_make($text); } sub string_make { my ($text) = @_; return query_make(sprintf '"%s"', $text); } sub query_make { my ($text) = @_; return sprintf 'query q { foo(name: %s) { id } }', $text; } sub string_lookup { my ($got) = @_; return query_lookup($got, 'string'); } sub query_lookup { my ($got, $type) = @_; return $got->[0]{selections}[0]{arguments}{name}; } __DATA__ [ { 'directives' => [ { 'name' => 'onQuery' } ], 'kind' => 'operation', 'location' => { 'column' => 1, 'line' => 27 }, 'name' => 'queryName', 'operationType' => 'query', 'selections' => [ { 'alias' => 'whoever123is', 'arguments' => { 'id' => [ 123, 456 ] }, 'kind' => 'field', 'location' => { 'column' => 1, 'line' => 25 }, 'name' => 'node', 'selections' => [ { 'kind' => 'field', 'location' => { 'column' => 5, 'line' => 9 }, 'name' => 'id' }, { 'directives' => [ { 'name' => 'onInlineFragment' } ], 'kind' => 'inline_fragment', 'location' => { 'column' => 5, 'line' => 18 }, 'on' => 'User', 'selections' => [ { 'kind' => 'field', 'location' => { 'column' => 5, 'line' => 17 }, 'name' => 'field2', 'selections' => [ { 'kind' => 'field', 'location' => { 'column' => 9, 'line' => 12 }, 'name' => 'id' }, { 'alias' => 'alias', 'arguments' => { 'after' => \'foo', 'first' => 10 }, 'directives' => [ { 'arguments' => { 'if' => \'foo' }, 'name' => 'include' } ], 'kind' => 'field', 'location' => { 'column' => 7, 'line' => 16 }, 'name' => 'field1', 'selections' => [ { 'kind' => 'field', 'location' => { 'column' => 11, 'line' => 14 }, 'name' => 'id' }, { 'directives' => [ { 'name' => 'onFragmentSpread' } ], 'kind' => 'fragment_spread', 'location' => { 'column' => 0, 'line' => 14 }, 'name' => 'frag' } ] } ] } ] }, { 'directives' => [ { 'arguments' => { 'unless' => \'foo' }, 'name' => 'skip' } ], 'kind' => 'inline_fragment', 'location' => { 'column' => 5, 'line' => 21 }, 'selections' => [ { 'kind' => 'field', 'location' => { 'column' => 5, 'line' => 20 }, 'name' => 'id' } ] }, { 'kind' => 'inline_fragment', 'location' => { 'column' => 3, 'line' => 24 }, 'selections' => [ { 'kind' => 'field', 'location' => { 'column' => 5, 'line' => 23 }, 'name' => 'id' } ] } ] } ], 'variables' => { 'foo' => { 'type' => 'ComplexType' }, 'site' => { 'default_value' => \\'MOBILE', 'type' => 'Site' } } }, { 'directives' => [ { 'name' => 'onMutation' } ], 'kind' => 'operation', 'location' => { 'column' => 1, 'line' => 35 }, 'name' => 'likeStory', 'operationType' => 'mutation', 'selections' => [ { 'arguments' => { 'story' => 123 }, 'directives' => [ { 'name' => 'onField' } ], 'kind' => 'field', 'location' => { 'column' => 1, 'line' => 33 }, 'name' => 'like', 'selections' => [ { 'kind' => 'field', 'location' => { 'column' => 3, 'line' => 32 }, 'name' => 'story', 'selections' => [ { 'directives' => [ { 'name' => 'onField' } ], 'kind' => 'field', 'location' => { 'column' => 0, 'line' => 30 }, 'name' => 'id' } ] } ] } ] }, { 'directives' => [ { 'name' => 'onSubscription' } ], 'kind' => 'operation', 'location' => { 'column' => 1, 'line' => 50 }, 'name' => 'StoryLikeSubscription', 'operationType' => 'subscription', 'selections' => [ { 'arguments' => { 'input' => \'input' }, 'kind' => 'field', 'location' => { 'column' => 1, 'line' => 48 }, 'name' => 'storyLikeSubscribe', 'selections' => [ { 'kind' => 'field', 'location' => { 'column' => 3, 'line' => 47 }, 'name' => 'story', 'selections' => [ { 'kind' => 'field', 'location' => { 'column' => 7, 'line' => 43 }, 'name' => 'likers', 'selections' => [ { 'kind' => 'field', 'location' => { 'column' => 7, 'line' => 42 }, 'name' => 'count' } ] }, { 'kind' => 'field', 'location' => { 'column' => 5, 'line' => 46 }, 'name' => 'likeSentence', 'selections' => [ { 'kind' => 'field', 'location' => { 'column' => 7, 'line' => 45 }, 'name' => 'text' } ] } ] } ] } ], 'variables' => { 'input' => { 'type' => 'StoryLikeSubscribeInput' } } }, { 'directives' => [ { 'name' => 'onFragmentDefinition' } ], 'kind' => 'fragment', 'location' => { 'column' => 1, 'line' => 58 }, 'name' => 'frag', 'on' => 'Friend', 'selections' => [ { 'arguments' => { 'bar' => \'b', 'obj' => { 'block' => 'block string uses """', 'key' => 'value' }, 'size' => \'size' }, 'kind' => 'field', 'location' => { 'column' => 1, 'line' => 56 }, 'name' => 'foo' } ] }, { 'kind' => 'operation', 'location' => { 'column' => 1, 'line' => 63 }, 'selections' => [ { 'arguments' => { 'falsey' => bless( do{\(my $o = 0)}, 'JSON::PP::Boolean' ), 'nullish' => undef, 'truthy' => bless( do{\(my $o = 1)}, 'JSON::PP::Boolean' ) }, 'kind' => 'field', 'location' => { 'column' => 3, 'line' => 60 }, 'name' => 'unnamed' }, { 'kind' => 'field', 'location' => { 'column' => 1, 'line' => 61 }, 'name' => 'query' } ] }, { 'kind' => 'operation', 'location' => { 'column' => 1, 'line' => 64 }, 'operationType' => 'query', 'selections' => [ { 'kind' => 'field', 'location' => { 'column' => 20, 'line' => 63 }, 'name' => '__typename' } ] } ] GraphQL-0.53/t/execution-abstract.t0000644000175000017500000001472113217121260017121 0ustar osboxesosboxesuse lib 't/lib'; use GQLTest; BEGIN { use_ok( 'GraphQL::Schema' ) || print "Bail out!\n"; use_ok( 'GraphQL::Type::Object' ) || print "Bail out!\n"; use_ok( 'GraphQL::Type::Interface' ) || print "Bail out!\n"; use_ok( 'GraphQL::Type::Union' ) || print "Bail out!\n"; use_ok( 'GraphQL::Type::Scalar', qw($String $Boolean) ) || print "Bail out!\n"; use_ok( 'GraphQL::Execution', qw(execute) ) || print "Bail out!\n"; } { package Dog; use Moo; has [qw(name woofs)] => (is => 'ro'); } { package Cat; use Moo; has [qw(name meows)] => (is => 'ro'); } { package Human; use Moo; has name => (is => 'ro'); } my @PETOBJS = (Dog->new(name => 'Odie', woofs => 1), Cat->new(name => 'Garfield', meows => 0)); my $DOC = '{ pets { name ... on Dog { woofs } ... on Cat { meows } } }'; my @EXPECTED = ( { name => 'Odie', woofs => JSON->true }, { name => 'Garfield', meows => JSON->false }, ); sub make_schema { my ($pets_type, $resolve, $other_args) = @_; GraphQL::Schema->new( query => GraphQL::Type::Object->new( name => 'Query', fields => { pets => { type => $pets_type, resolve => $resolve, }, }, ), @{ $other_args || [] }, ); } # makes field-resolver that takes resolver args and calls Moo accessor, returns field_def sub make_field { my ($field_name, $type) = @_; ($field_name => { resolve => sub { my ($root_value, $args, $context, $info) = @_; my @passon = %$args ? ($args) : (); $root_value->$field_name(@passon); }, type => $type }); } subtest 'isTypeOf used to resolve runtime type for Interface', sub { my $PetType = GraphQL::Type::Interface->new( name => 'Pet', fields => { name => { type => $String } }, ); my $DogType = GraphQL::Type::Object->new( name => 'Dog', interfaces => [ $PetType ], is_type_of => sub { $_[0]->isa('Dog') }, fields => { make_field(name => $String), make_field(woofs => $Boolean) }, ); my $CatType = GraphQL::Type::Object->new( name => 'Cat', interfaces => [ $PetType ], is_type_of => sub { $_[0]->isa('Cat') }, fields => { make_field(name => $String), make_field(meows => $Boolean) }, ); my $schema = make_schema( $PetType->list, sub { [ @PETOBJS ] }, [ types => [ $CatType, $DogType ] ], ); run_test( [$schema, $DOC], { data => { pets => [ @EXPECTED ], } }, ); done_testing; }; subtest 'isTypeOf used to resolve runtime type for Union', sub { my $DogType = GraphQL::Type::Object->new( name => 'Dog', is_type_of => sub { $_[0]->isa('Dog') }, fields => { make_field(name => $String), make_field(woofs => $Boolean) }, ); my $CatType = GraphQL::Type::Object->new( name => 'Cat', is_type_of => sub { $_[0]->isa('Cat') }, fields => { make_field(name => $String), make_field(meows => $Boolean) }, ); my $PetType = GraphQL::Type::Union->new( name => 'Pet', types => [ $DogType, $CatType ], ); my $schema = make_schema( $PetType->list, sub { [ @PETOBJS ] }, ); run_test( [$schema, $DOC], { data => { pets => [ @EXPECTED ], } }, ); done_testing; }; subtest 'resolveType on Interface yields useful error', sub { my ($CatType, $DogType, $HumanType); my $PetType = GraphQL::Type::Interface->new( name => 'Pet', fields => { name => { type => $String } }, resolve_type => sub { $_[0]->isa('Dog') ? $DogType : $_[0]->isa('Cat') ? $CatType : $_[0]->isa('Human') ? $HumanType : undef }, ); $HumanType = GraphQL::Type::Object->new( name => 'Human', fields => { make_field(name => $String) }, ); $DogType = GraphQL::Type::Object->new( name => 'Dog', interfaces => [ $PetType ], fields => { make_field(name => $String), make_field(woofs => $Boolean) }, ); $CatType = GraphQL::Type::Object->new( name => 'Cat', interfaces => [ $PetType ], fields => { make_field(name => $String), make_field(meows => $Boolean) }, ); my $schema = make_schema( $PetType->list, sub { [ @PETOBJS, Human->new(name => 'Jon') ] }, [ types => [ $CatType, $DogType ] ], ); run_test( [$schema, $DOC], { data => { pets => [ @EXPECTED, undef ], }, errors => [ { message => "Runtime Object type 'Human' is not a possible type for 'Pet'.", locations => [ { line => 11, column => 1 } ], path => [ 'pets', 2 ], }, ] }, ); done_testing; }; subtest 'resolveType on Union yields useful error', sub { my ($CatType, $DogType, $HumanType); $HumanType = GraphQL::Type::Object->new( name => 'Human', fields => { make_field(name => $String) }, ); $DogType = GraphQL::Type::Object->new( name => 'Dog', fields => { make_field(name => $String), make_field(woofs => $Boolean) }, ); $CatType = GraphQL::Type::Object->new( name => 'Cat', fields => { make_field(name => $String), make_field(meows => $Boolean) }, ); my $PetType = GraphQL::Type::Union->new( name => 'Pet', resolve_type => sub { $_[0]->isa('Dog') ? $DogType : $_[0]->isa('Cat') ? $CatType : $_[0]->isa('Human') ? $HumanType : undef }, types => [ $DogType, $CatType ], ); my $schema = make_schema( $PetType->list, sub { [ @PETOBJS, Human->new(name => 'Jon') ] }, ); run_test( [$schema, $DOC], { data => { pets => [ @EXPECTED, undef ], }, errors => [ { message => "Runtime Object type 'Human' is not a possible type for 'Pet'.", locations => [ { line => 11, column => 1 } ], path => [ 'pets', 2 ], } ] }, ); done_testing; }; subtest 'resolveType allows resolving with type name', sub { my ($CatType, $DogType, $HumanType); my $PetType = GraphQL::Type::Interface->new( name => 'Pet', fields => { name => { type => $String } }, resolve_type => sub { $_[0]->isa('Dog') ? 'Dog' : $_[0]->isa('Cat') ? 'Cat' : undef }, ); $DogType = GraphQL::Type::Object->new( name => 'Dog', interfaces => [ $PetType ], fields => { make_field(name => $String), make_field(woofs => $Boolean) }, ); $CatType = GraphQL::Type::Object->new( name => 'Cat', interfaces => [ $PetType ], fields => { make_field(name => $String), make_field(meows => $Boolean) }, ); my $schema = make_schema( $PetType->list, sub { [ @PETOBJS ] }, [ types => [ $CatType, $DogType ] ], ); run_test( [$schema, $DOC], { data => { pets => [ @EXPECTED ], } }, ); done_testing; }; done_testing; GraphQL-0.53/t/directives.t0000644000175000017500000000503613573302700015462 0ustar osboxesosboxesuse lib 't/lib'; use GQLTest; BEGIN { use_ok( 'GraphQL::Schema' ) || print "Bail out!\n"; use_ok( 'GraphQL::Execution', qw(execute) ) || print "Bail out!\n"; } my $doc = q< directive @length(max: Int) on FIELD_DEFINITION | INPUT_FIELD_DEFINITION directive @note(msg: Str) on SCHEMA | SCALAR | OBJECT | FIELD_DEFINITION | ARGUMENT_DEFINITION | INTERFACE | UNION | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION type Todo { task: String! @length(max: 15) } type Query { todos: [Todo] } type Mutation @length(max: 15) { add_todo(task: String! @length(max: 15)): Todo } schema @note(msg: "Test") { query: Query mutation: Mutation } >; ok my $schema = GraphQL::Schema->from_doc($doc); my @orig_data = my @data = ( {task => 'Exercise!'}, {task => 'Bulk Milk'}, {task => 'Walk Dogs'}, ); my $add_task = { task => 'milk1' }; my $bad_task = { task => 'milk1milk1milk1milk1' }; my %root_value = ( todos => sub { return \@data; }, add_todo => sub { my ($args, $context, $info) = @_; my $task = $args->{task}; # hardcoding arg name for simplicity my $length_dir = _get_directive($info, 'task', 'length'); if ($length_dir) { my $max = $length_dir->{arguments}{max}; die "Length of '$task' > max=$max\n" if length $task > $max; } my $data = { task => $task }; push @data, $data; return $data; } ); sub _get_directive { my ($info, $arg, $name) = @_; my $parent_type = $info->{parent_type}; my $field_def = $parent_type->fields->{$info->{field_name}}; my $arg_directives = $field_def->{args}{$arg}{directives}; # would autovivify my ($directive) = grep $_->{name} eq $name, @$arg_directives; $directive; } my $q = q< mutation m($task: String!) { add_todo(task: $task) { task } } query q { todos { task } } >; subtest 'basic directives' => sub { run_test( [$schema, $q, \%root_value, undef, undef, 'q'], { data => { todos => \@orig_data } }, ); run_test( [$schema, $q, \%root_value, undef, $add_task, 'm'], { data => { add_todo => $add_task } }, ); run_test( [$schema, $q, \%root_value, undef, $bad_task, 'm'], { data => { add_todo => undef }, errors => [ { locations => [ { column => 3, line => 4 } ], message => "Length of 'milk1milk1milk1milk1' > max=15\n", path => [ 'add_todo' ] } ] }, ); run_test( [$schema, $q, \%root_value, undef, undef, 'q'], { data => { todos => [ @orig_data, $add_task ] } }, ); }; done_testing; GraphQL-0.53/t/schema-kitchen-sink.graphql0000644000175000017500000000522613426211267020346 0ustar osboxesosboxes# Copyright (c) 2015-present, Facebook, Inc. # # This source code is licensed under the MIT license found in the # LICENSE file in the root directory of this source tree. schema { query: QueryType mutation: MutationType } """ This is a description of the `Foo` type. """ type Foo implements Bar & Baz { "Description of the `one` field." one: Type """ This is a description of the `two` field. """ two( """ This is a description of the `argument` argument. """ argument: InputType! ): Type """This is a description of the `three` field.""" three(argument: InputType, other: String): Int four(argument: String = "string"): String five(argument: [String] = ["string", "string"]): String six(argument: InputType = {key: "value"}): Type seven(argument: Int = null): Type } type AnnotatedObject @onObject(arg: "value") { annotatedField(arg: Type = "default" @onArgumentDefinition): Type @onField } type UndefinedType extend type Foo { seven(argument: [String]): Type } extend type Foo @onType interface Bar { one: Type four(argument: String = "string"): String } interface AnnotatedInterface @onInterface { annotatedField(arg: Type @onArgumentDefinition): Type @onField } interface UndefinedInterface extend interface Bar { two(argument: InputType!): Type } extend interface Bar @onInterface union Feed = | Story | Article | Advert union AnnotatedUnion @onUnion = A | B union AnnotatedUnionTwo @onUnion = | A | B union UndefinedUnion extend union Feed = Photo | Video extend union Feed @onUnion scalar CustomScalar scalar AnnotatedScalar @onScalar extend scalar CustomScalar @onScalar enum Site { """ This is a description of the `DESKTOP` value """ DESKTOP """This is a description of the `MOBILE` value""" MOBILE "This is a description of the `WEB` value" WEB } enum AnnotatedEnum @onEnum { ANNOTATED_VALUE @onEnumValue OTHER_VALUE } enum UndefinedEnum extend enum Site { VR } extend enum Site @onEnum input InputType { key: String! answer: Int = 42 } input AnnotatedInput @onInputObject { annotatedField: Type @onInputFieldDefinition } input UndefinedInput extend input InputType { other: Float = 1.23e4 @onInputFieldDefinition } extend input InputType @onInputObject """ This is a description of the `@skip` directive """ directive @skip( if: Boolean! @onArgumentDefinition ) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT directive @include(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT directive @include2(if: Boolean!) on | FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT extend schema @onSchema extend schema @onSchema { subscription: SubscriptionType } GraphQL-0.53/Dockerfile0000644000175000017500000000063313212704510014654 0ustar osboxesosboxes# from https://docs.docker.com/get-started/part2/#dockerfile # stuff from https://hub.docker.com/_/perl/ # + https://github.com/perl/docker-perl/blob/master/5.026.001-64bit/Dockerfile FROM graphqlperl/graphql-prereq:latest # Copy the current directory contents into the container ADD . /opt/graphql-lib/ RUN cd /opt/graphql-lib \ && perl Makefile.PL \ && make test install \ && perl5.26.1 -MGraphQL -e0 GraphQL-0.53/README.md0000644000175000017500000001623714115723676014171 0ustar osboxesosboxes# NAME GraphQL - Perl implementation of GraphQL # PROJECT STATUS | OS | Build status | |:-------:|--------------:| | Linux | [![Build Status](https://travis-ci.org/graphql-perl/graphql-perl.svg?branch=master)](https://travis-ci.org/graphql-perl/graphql-perl) | [![CPAN version](https://badge.fury.io/pl/GraphQL.svg)](https://metacpan.org/pod/GraphQL) [![Coverage Status](https://coveralls.io/repos/github/graphql-perl/graphql-perl/badge.svg?branch=master)](https://coveralls.io/github/graphql-perl/graphql-perl?branch=master) # SYNOPSIS use GraphQL::Schema; use GraphQL::Type::Object; use GraphQL::Type::Scalar qw($String); use GraphQL::Execution qw(execute); my $schema = GraphQL::Schema->from_doc(<<'EOF'); type Query { helloWorld: String } EOF post '/graphql' => sub { send_as JSON => execute( $schema, body_parameters->{query}, { helloWorld => 'Hello world!' }, undef, body_parameters->{variables}, body_parameters->{operationName}, undef, ); }; The above is from [the sample Dancer 2 applet](https://github.com/graphql-perl/sample-dancer2). # DESCRIPTION This module is a port of the GraphQL reference implementation, [graphql-js](https://github.com/graphql-js/graphql-js), to Perl 5. It now supports Promises, allowing asynchronous operation. See [Mojolicious::Plugin::GraphQL](https://metacpan.org/pod/Mojolicious%3A%3APlugin%3A%3AGraphQL) for an example of how to take advantage of this. As of 0.39, supports GraphQL subscriptions. See [GraphQL::Type](https://metacpan.org/pod/GraphQL%3A%3AType) for description of how to create GraphQL types. ## Introduction to GraphQL GraphQL is a technology that lets clients talk to APIs via a single endpoint, which acts as a single "source of the truth". This means clients do not need to seek the whole picture from several APIs. Additionally, it makes this efficient in network traffic, time, and programming effort: - Network traffic The request asks for exactly what it wants, which it gets, and no more. No wasted traffic. - Time It gets all the things it needs in one go, including any connected resources, so it does not need to make several requests to fill its information requirement. - Programming effort With "fragments" that can be attached to user-interface components, keeping track of what information a whole page needs to request can be automated. See [Relay](https://facebook.github.io/relay/) or [Apollo](http://dev.apollodata.com/) for more on this. ## Basic concepts GraphQL implements a system featuring a [schema](https://metacpan.org/pod/GraphQL%3A%3ASchema), which features various classes of [types](https://metacpan.org/pod/GraphQL%3A%3AType), some of which are [objects](https://metacpan.org/pod/GraphQL%3A%3AType%3A%3AObject). Special objects provide the roots of queries (mandatory), and mutations and subscriptions (both optional). Objects have fields, each of which can be specified to take arguments, and which have a return type. These are effectively the properties and/or methods on the type. If they return an object, then a query can specify subfields of that object, and so on - as alluded to in the "time-saving" point above. For more, see the JavaScript tutorial in ["SEE ALSO"](#see-also). ## Hooking your system up to GraphQL You will need to decide how to model your system in GraphQL terms. This will involve deciding on what [output object types](https://metacpan.org/pod/GraphQL%3A%3AType%3A%3AObject) you have, what fields they have, and what arguments and return-types those fields have. Additionally, you will need to design mutations if you want to be able to update/create/delete data. This requires some thought for return types, to ensure you can get all the information you need to proceed to avoid extra round-trips. The easiest way to achieve these things is to make a [GraphQL::Plugin::Convert](https://metacpan.org/pod/GraphQL%3A%3APlugin%3A%3AConvert) subclass, to encapsulate the specifics of your system. See the documentation for further information. Finally, you should consider whether you need "subscriptions". These are designed to hook into WebSockets. Apollo has a [JavaScript module](https://github.com/apollographql/graphql-subscriptions) for this. Specifying types and fields is straightforward. See [the document](https://metacpan.org/pod/GraphQL%3A%3AType%3A%3ALibrary#FieldMapOutput) for how to make resolvers. # DEBUGGING To debug, set environment variable `GRAPHQL_DEBUG` to a true value. # EXPORT None yet. # SEE ALSO [SQL::Translator::Producer::GraphQL](https://metacpan.org/pod/SQL%3A%3ATranslator%3A%3AProducer%3A%3AGraphQL) - produce GraphQL schemas from a [DBIx::Class::Schema](https://metacpan.org/pod/DBIx%3A%3AClass%3A%3ASchema) (or in fact any SQL database) [GraphQL::Plugin::Convert::DBIC](https://metacpan.org/pod/GraphQL%3A%3APlugin%3A%3AConvert%3A%3ADBIC) - produce working GraphQL schema from a [DBIx::Class::Schema](https://metacpan.org/pod/DBIx%3A%3AClass%3A%3ASchema) [GraphQL::Plugin::Convert::OpenAPI](https://metacpan.org/pod/GraphQL%3A%3APlugin%3A%3AConvert%3A%3AOpenAPI) - produce working GraphQL schema from an OpenAPI specification [Sample Mojolicious OpenAPI to GraphQL applet](https://github.com/graphql-perl/sample-mojolicious-openapi) [Sample Dancer 2 applet](https://github.com/graphql-perl/sample-dancer2) [Sample Mojolicious applet](https://github.com/graphql-perl/sample-mojolicious) [Dancer2::Plugin::GraphQL](https://metacpan.org/pod/Dancer2%3A%3APlugin%3A%3AGraphQL) [Mojolicious::Plugin::GraphQL](https://metacpan.org/pod/Mojolicious%3A%3APlugin%3A%3AGraphQL) [http://facebook.github.io/graphql/](http://facebook.github.io/graphql/) - GraphQL specification [http://graphql.org/graphql-js/](http://graphql.org/graphql-js/) - Tutorial on the JavaScript version, highly recommended. [Translation to graphql-perl](http://blogs.perl.org/users/ed_j/2017/10/graphql-perl---graphql-js-tutorial-translation-to-graphql-perl-and-mojoliciousplugingraphql.html). # AUTHOR Ed J, `` # BUGS Please report any bugs or feature requests on [https://github.com/graphql-perl/graphql-perl/issues](https://github.com/graphql-perl/graphql-perl/issues). Or, if you prefer email and/or RT: to `bug-graphql at rt.cpan.org`, or through the web interface at [http://rt.cpan.org/NoAuth/ReportBug.html?Queue=GraphQL](http://rt.cpan.org/NoAuth/ReportBug.html?Queue=GraphQL). I will be notified, and then you'll automatically be notified of progress on your bug as I make changes. # ACKNOWLEDGEMENTS The creation of this work has been sponsored by Perl Careers: [https://perl.careers/](https://perl.careers/). Artur Khabibullin `` contributed valuable ports of the JavaScript tests. The creation of the subscriptions functionality in this work has been sponsored by Sanctus.app: [https://sanctus.app](https://sanctus.app). # LICENSE AND COPYRIGHT Copyright 2017 Ed J. This program is free software; you can redistribute it and/or modify it under the terms of the the Artistic License (2.0). You may obtain a copy of the full license at: [http://www.perlfoundation.org/artistic\_license\_2\_0](http://www.perlfoundation.org/artistic_license_2_0) GraphQL-0.53/META.yml0000644000175000017500000000222114170415415014135 0ustar osboxesosboxes--- abstract: 'Perl implementation of GraphQL' author: - 'Ed J ' build_requires: ExtUtils::MakeMaker: '0' Test::Deep: '1.127' Test::Exception: '0.42' Test::More: '0.88' configure_requires: ExtUtils::MakeMaker: '6.64' dynamic_config: 0 generated_by: 'ExtUtils::MakeMaker version 7.44, CPAN::Meta::Converter version 2.150010' license: artistic_2 meta-spec: url: http://module-build.sourceforge.net/META-spec-v1.4.html version: '1.4' name: GraphQL no_index: directory: - t - inc requires: Attribute::Handlers: '0' DateTime: '0' DateTime::Format::ISO8601: '0' Devel::StrictMode: '0' Function::Parameters: '2.001001' Import::Into: '1.002003' JSON::MaybeXS: '1.003009' JSON::PP: '2.92' Module::Runtime: '0' Moo: '0' MooX::Thunking: '0.07' Pegex: '0.64' Return::Type: '0' Type::Tiny: '0' curry: '0' perl: '5.014' resources: IRC: irc://irc.perl.org/#graphql-perl bugtracker: https://github.com/graphql-perl/graphql-perl/issues license: http://dev.perl.org/licenses/ repository: https://github.com/graphql-perl/graphql-perl version: '0.53' x_serialization_backend: 'CPAN::Meta::YAML version 0.018' GraphQL-0.53/Changes0000644000175000017500000001514714170415322014167 0ustar osboxesosboxes0.53 2022-01-15 - String and ID now always serialise to JSON strings - thanks @cliveholloway 0.52 2021-09-03 - not always load DateTime type - thanks @mangano-ito 0.51 2021-07-04 - from_doc accepts kind2class to override SDL keyword - thanks @mangano-ito 0.50 2021-04-08 - Scalar.parse_value was wrong for Boolean - thanks @ccakes for report 0.49 2021-02-15 - type-checking only on direct MaybeTypeCheck use (#35) - thanks @ccakes 0.48 2021-02-05 - require Import::Into 1.002003 as we depend on module-loading 0.47 2021-02-02 - type-checking now only when Devel::StrictMode STRICT (#31) - thanks @ccakes - can give default values to enums in queries (#33) - thanks @ccakes - eliminate tail-call in type expansion - thanks @ccakes 0.46 2021-01-27 - PromiseCode example to interoperate with Promises - thanks @hitode909 0.45 2021-01-27 - Promise only needs then method - resolve/reject go via PromiseCode - thanks @hitode909 for prompt 0.44 2020-11-09 - Allow serialising already-blessed bools (#30) - thanks @ccakes 0.43 2020-11-07 - mutations now guaranteed to be executed in order - thanks @ccakes for prompt 0.42 2020-11-07 - List needed a perl_to_graphql method - thanks @schoeer for report - so did InputObject 0.41 2020-08-05 - Scalar types now correctly handle null values 0.40 2020-05-09 - GraphQL::AsyncIterator->map_then now mutates object, returns self 0.39 2020-05-06 - simplify Int and Float implementations - improve Boolean error-checking - Convert::Test has an "echo" mutation and optional "timedEcho" subscription - Subscriptions implemented 0.38 2019-12-08 - error objects now stringify - thanks @jjn1056 for idea - more tests for directives - thanks @jjn1056 - Support field directives - thanks @yigarashi-9 0.37 2019-11-04 - fix literal input-object coercion problem - thanks @ccakes - add "extensions" field to GraphQL::Error - thanks @cockscomb 0.36 2019-05-01 - doc improvements 0.35 2019-04-29 - remove author-mode stuff from affecting make in distro 0.34 2019-04-28 - List now has "name" that is its "of"'s name 0.33 Thu 21 Feb 22:58:02 GMT 2019 - update Plugin::Convert docs - thanks @aksonov - zap "meaningful comment" as description - implement GraphQL::Plugin::Type API, switch DateTime to that 0.32 Tue 5 Feb 02:37:20 GMT 2019 - metadata and test updates - unescape string literals - thanks @chazmcgarvey - add block strings - thanks @chazmcgarvey - update SDL parsing to current graphql-js, including empty defs of most types - make to_doc produce new-style descriptions while from_doc still understands old, so can convert 0.31 Sun 1 Jul 23:10:59 BST 2018 - Fix non-nullable Enum being misdetected - thanks @mechairoi 0.30 Wed 28 Feb 04:29:50 GMT 2018 - make variable-type errors JSONable for returning - thanks @nohuhu 0.29 Thu Feb 15 03:40:12 GMT 2018 - Pegex version that gets line_column right 0.28 Sat Feb 10 18:26:29 GMT 2018 - fix Enum type-check bug #10 0.27 Fri Feb 9 18:13:35 GMT 2018 - fix input type-check bug #9 0.26 Sat Dec 30 08:45:37 GMT 2017 - the "then" of ->all will get each result as array-ref 0.25 Fri Dec 29 12:47:13 GMT 2017 - more-correct handle of lists and non-null - handles Promises if give helper code-refs (type PromiseCode) 0.24 Tue 12 Dec 03:57:18 GMT 2017 - handle array of enum 0.23 Sat 9 Dec 17:48:42 GMT 2017 - fix 5.14 bug with "?" ambiguity 0.22 Sat 9 Dec 06:44:51 GMT 2017 - basic Dockerfile makes 5.26.1 image with GraphQL - multi-line descriptions parsed and round-tripped right - doc updates 0.21 Thu Nov 16 05:22:53 GMT 2017 - add DateTime scalar type - allow class overrides in Schema.from_ast - add and use _complete_value to the Type classes 0.20 Wed Nov 1 03:59:54 GMT 2017 - add GraphQL::Plugin::Convert API 0.19 Sun Oct 29 16:11:31 GMT 2017 - add DateTime as test dep, thanks @andk! 0.18 Fri Oct 27 05:00:46 BST 2017 - make AST not have "node" 0.17 Thu Oct 26 19:58:00 BST 2017 - fill out Schema.to_doc include default if eg type Query 0.16 Sun Oct 22 07:09:44 BST 2017 - execute take AST as well as text - error reporting improvements - implement @skip and @include - implement GraphQL::Schema->from_doc et al - rework of graphql.pgx 0.15 Mon Oct 16 00:13:31 BST 2017 - introspection improvements - switch parse and execute from methods to functions 0.14 Fri Oct 13 06:31:33 BST 2017 - catch and report query-parse errors - parse and other errors now report query-doc location - eliminated various data mutations for purer functions 0.13 Thu Oct 12 16:18:20 BST 2017 - bulletproof JSON booleans. figured with help from @haarg and @shadowcat-mst 0.12 Tue Oct 10 05:13:29 BST 2017 - allow $context to be anything (such as request headers) 0.11 Mon Oct 9 17:17:17 BST 2017 - coerce numbers in Int and Float to dodge stringifying of numbers for JSON::XS 0.10 Thu Oct 5 05:30:56 BST 2017 - tighten the JSON module requirements to zap stringifying-numbers test fails 0.09 Mon Oct 2 06:39:07 BST 2017 - implement conversion between Perl and GraphQL/JSON values - add more usable debugging code - error handling - implement Object field resolving - implement fragments - implement Enum, Directive - more type checking - implement introspection - list GitHub as bugtracker - documentation 0.08 Sat Sep 23 06:27:20 BST 2017 - zap GraphQL::Argument - get all thunking right using MooX::Thunking - is_deprecated auto handling - Execution does top-level field resolving, type coercing - port execution-resolve.t execution-variables.t from graphql-js - Schema API mainly complete - many types for validation - make Parser return usable AST - improvements to Pegex grammar from ingy - start of GraphQL::Error - JSON::MaybeXS so no stringify numbers 0.07 Tue Sep 5 19:50:33 BST 2017 - make GraphQL::Directive, GraphQL::Argument - make GraphQL::Type::Library with Type::Tiny constraints - make GraphQL::Type superclass and subclasses - tidy quoting - standardise method and attribute snake_casing - working, tested GraphQL::Schema except assert_object_implements_interface - various GraphQL::Role-s - basic doc of type system 0.06 Sun Aug 13 22:41:08 BST 2017 - rename ::Language to ::Parser - language parser with tests 0.05 Wed Jul 5 03:08:25 BST 2017 - Fuller Pegex lexer - Incorporate rest of graphql-js lexer tests 0.04 Wed May 31 16:20:17 BST 2017 - Switch to new graphql-perl GH organisation repo - Nicer README.md - Doc improvements - Basic Pegex lexer + tests 0.03 Fri May 26 18:54:36 BST 2017 - Include meta info - Basic class system in place - Start of t and xt dirs 0.02 Thu May 18 16:00:15 BST 2017 - JS classnames to Perl classnames 0.01 Thu May 18 15:46:07 BST 2017 - First version, released on an unsuspecting world. GraphQL-0.53/lib/0000755000175000017500000000000014170415414013434 5ustar osboxesosboxesGraphQL-0.53/lib/GraphQL/0000755000175000017500000000000014170415414014732 5ustar osboxesosboxesGraphQL-0.53/lib/GraphQL/AsyncIterator.pm0000644000175000017500000000737614070317276020102 0ustar osboxesosboxespackage GraphQL::AsyncIterator; use 5.014; use strict; use warnings; use Moo; use GraphQL::Debug qw(_debug); use Types::Standard -all; use Types::TypeTiny -all; use GraphQL::Type::Library -all; use GraphQL::PubSub; use GraphQL::MaybeTypeCheck; use curry; use constant DEBUG => $ENV{GRAPHQL_DEBUG}; =head1 NAME GraphQL::AsyncIterator - iterator objects that return promise to next result =head1 SYNOPSIS use GraphQL::AsyncIterator; my $i = GraphQL::AsyncIterator->new( promise_code => $pc, ); # also works when publish happens before next_p called my $promised_value = $i->next_p; $i->publish('hi'); # now $promised_value will be fulfilled $i->close_tap; # now next_p will return undef =head1 DESCRIPTION Encapsulates the asynchronous event-handling needed for the publish/subscribe behaviour needed by L. =head1 ATTRIBUTES =head2 promise_code A hash-ref matching L, which must provide the C key. =cut has promise_code => (is => 'ro', isa => PromiseCode); =head1 METHODS =head2 publish(@values) Resolves the relevant promise with C<@values>. =cut has _values_queue => (is => 'ro', isa => ArrayRef, default => sub { [] }); has _next_promise => (is => 'rw', isa => Maybe[Promise]); method publish(@values) { $self->_emit('resolve', \@values); } method _promisify((Enum[qw(resolve reject)]) $method, $data) { return $data if is_Promise($data); $self->promise_code->{$method}->(@$data); } method _thenify(Maybe[CodeLike] $then, Maybe[CodeLike] $catch, (Enum[qw(resolve reject)]) $method, $data) { return $data unless $then or $catch; $self->_promisify($method, $data)->then($then, $catch); } method _emit((Enum[qw(resolve reject)]) $method, $data) { if ($self->_exhausted) { die "Tried to emit to closed-off AsyncIterator\n"; } if (my $next_promise = $self->_next_promise) { $next_promise->$method(ref $data eq 'ARRAY' ? @$data : $data); $self->_next_promise(undef); } else { push @{$self->_values_queue}, { data => $data, method => $method }; } } =head2 error(@values) Rejects the relevant promise with C<@values>. =cut method error(@values) { $self->_emit('reject', \@values); } =head2 next_p Returns either a L of the next value, or C when closed off. Do not call this if a previous promised next value has not been settled, as a queue is not maintained. The promise will have each of the sets of handlers added by L appended. =cut method next_p() :ReturnType(Maybe[Promise]) { return undef if $self->_exhausted and !@{$self->_values_queue}; my $np; if (my $value = shift @{$self->_values_queue}) { $np = $self->_promisify(@$value{qw(method data)}); } else { $np = $self->_next_promise($self->promise_code->{new}->()); } $np = $self->_thenify(@$_, 'resolve', $np) for @{$self->_handler_frames}; $np; } =head2 close_tap Switch to being closed off. L will return C as soon as it runs out of Led values. L will throw an exception. B This will not cause the settling of any outstanding promise returned by L. =cut has _exhausted => (is => 'rw', isa => Bool, default => sub { 0 }); method close_tap() :ReturnType(Maybe[Promise]) { return if $self->_exhausted; # already done - no need to redo $self->_exhausted(1); } =head2 map_then($then, $catch) Adds the handlers to this object's list of handlers, which will be attached to promises returned by L. Returns self. =cut has _handler_frames => ( is => 'ro', isa => ArrayRef[ArrayRef[CodeLike]], default => sub {[]}, ); method map_then(Maybe[CodeLike] $then, Maybe[CodeLike] $catch = undef) { push @{$self->_handler_frames}, [ $then, $catch ]; $self; } __PACKAGE__->meta->make_immutable(); 1; GraphQL-0.53/lib/GraphQL/Plugin/0000755000175000017500000000000014170415414016170 5ustar osboxesosboxesGraphQL-0.53/lib/GraphQL/Plugin/Convert/0000755000175000017500000000000014170415414017610 5ustar osboxesosboxesGraphQL-0.53/lib/GraphQL/Plugin/Convert/Test.pm0000644000175000017500000000353014006154243021064 0ustar osboxesosboxespackage GraphQL::Plugin::Convert::Test; use Moo; use GraphQL::Schema; extends qw(GraphQL::Plugin::Convert); =head1 NAME GraphQL::Plugin::Convert::Test - GraphQL plugin test class =head1 SYNOPSIS package main; use GraphQL::Plugin::Convert::Test; use GraphQL::Execution qw(execute); my $converted = GraphQL::Plugin::Convert::Test->to_graphql; print execute( $converted->{schema}, '{helloWorld}', $converted->{root_value} )->{data}{helloWorld}, "\n"; # show schema from shell perl -Maliased=GraphQL::Plugin::Convert::Test -e 'print Test->to_graphql->{schema}->to_doc' =head1 DESCRIPTION Example class to allow testing of convert plugin consumers. =head1 METHODS Produces a schema and root value that defines the top-level query field C. That will return the string C. Also has a mutation, C, that takes a String C, and returns it. =head2 to_graphql(@values) If the first value is true, it is a C, enabling subscriptions in the generated schema. It will be returned as the relevant key in the hash-ref, suitable for being passed as the relevant arg to L. The schema will have a subscription field C that takes a String C, and should return it periodically, in a way determined by the subscription function. =cut sub to_graphql { my ($class, $subscribe_resolver) = @_; my $sdl = <<'EOF'; type Query { helloWorld: String! } type Mutation { echo(s: String!): String! } EOF $sdl .= "type Subscription { timedEcho(s: String!): String! }\n" if $subscribe_resolver; +{ schema => GraphQL::Schema->from_doc($sdl), root_value => { helloWorld => 'Hello, world!', echo => sub { $_[0]->{s} }, }, $subscribe_resolver ? (subscribe_resolver => $subscribe_resolver) : (), }; } __PACKAGE__->meta->make_immutable(); 1; GraphQL-0.53/lib/GraphQL/Plugin/Type/0000755000175000017500000000000014170415414017111 5ustar osboxesosboxesGraphQL-0.53/lib/GraphQL/Plugin/Type/DateTime.pm0000644000175000017500000000255513433617401021153 0ustar osboxesosboxespackage GraphQL::Plugin::Type::DateTime; use strict; use warnings; use GraphQL::Type::Scalar; use GraphQL::Plugin::Type; use DateTime::Format::ISO8601; =head1 NAME GraphQL::Plugin::Type::DateTime - GraphQL DateTime scalar type =head1 SYNOPSIS use GraphQL::Schema; use GraphQL::Plugin::Type::DateTime; use GraphQL::Execution qw(execute); my $schema = GraphQL::Schema->from_doc(<<'EOF'); type Query { dateTimeNow: DateTime } EOF post '/graphql' => sub { send_as JSON => execute( $schema, body_parameters->{query}, { dateTimeNow => sub { DateTime->now } }, undef, body_parameters->{variables}, body_parameters->{operationName}, undef, ); }; =head1 DESCRIPTION Implements a non-standard GraphQL scalar type that represents a point in time, canonically represented in ISO 8601 format, e.g. C<20171114T07:41:10>. =cut my $iso8601 = DateTime::Format::ISO8601->new; GraphQL::Plugin::Type->register( GraphQL::Type::Scalar->new( name => 'DateTime', description => 'The `DateTime` scalar type represents a point in time. ' . 'Canonically represented using ISO 8601 format, e.g. 20171114T07:41:10, '. 'which is 14 November 2017 at 07:41am.', serialize => sub { return if !defined $_[0]; $_[0].'' }, parse_value => sub { return if !defined $_[0]; $iso8601->parse_datetime(@_); }, ) ); 1; GraphQL-0.53/lib/GraphQL/Plugin/Convert.pm0000644000175000017500000000346714006154243020156 0ustar osboxesosboxespackage GraphQL::Plugin::Convert; use Moo; use strict; use warnings; =head1 NAME GraphQL::Plugin::Convert - GraphQL plugin API abstract class =head1 SYNOPSIS package GraphQL::Plugin::Convert::DBIC; use Moo; extends qw(GraphQL::Plugin::Convert); # ... package main; use Mojolicious::Lite; use Schema; use GraphQL::Plugin::Convert::DBIC; helper db => sub { Schema->connect('dbi:SQLite:test.db') }; my $converted = GraphQL::Plugin::Convert::DBIC->to_graphql(sub { app->db }); plugin GraphQL => { map { $_ => $converted->{$_} } qw(schema resolver root_value subscribe_resolver) }; # OR, for knowledgeable consumers of GraphQL::Plugin::Convert APIs: package main; use Mojolicious::Lite; use Schema; helper db => sub { Schema->connect('dbi:SQLite:test.db') }; plugin GraphQL => { convert => [ 'DBIC', sub { app->db } ] }; =head1 DESCRIPTION Abstract class for other GraphQL type classes to inherit from and implement. =head1 METHODS =head2 to_graphql(@values) When called with suitable values (as defined by the implementing class), will return a hash-ref with these keys: =over =item schema A L. =item resolver A code-ref suitable for using as a resolver by L. Optional. =item root_value A hash-ref suitable for using as a C<$root_value> by L. Optional. =item subscribe_resolver A code-ref suitable for using as a C<$subscribe_resolver> by L. Optional. =back =head2 from_graphql When called with a hash-ref shaped as above, with at least a C key with a L, returns some value(s). Optional to implement. If the plugin does implement this, allows conversion from a GraphQL schema to that plugin's domain. =cut __PACKAGE__->meta->make_immutable(); 1; GraphQL-0.53/lib/GraphQL/Plugin/Type.pm0000644000175000017500000000400714070317276017456 0ustar osboxesosboxespackage GraphQL::Plugin::Type; use Moo; use GraphQL::MaybeTypeCheck; use Types::Standard -all; =head1 NAME GraphQL::Plugin::Type - GraphQL plugins implementing types =head1 SYNOPSIS package GraphQL::Plugin::Type::DateTime; use Moo; extends qw(GraphQL::Plugin::Type); my $iso8601 = DateTime::Format::ISO8601->new; GraphQL::Plugin::Type->register( GraphQL::Type::Scalar->new( name => 'DateTime', serialize => sub { return if !defined $_[0]; $_[0].'' }, parse_value => sub { return if !defined $_[0]; $iso8601->parse_datetime(@_); }, ) ); 1; package main; use GraphQL::Schema; use GraphQL::Plugin::Type::DateTime; use GraphQL::Execution qw(execute); my $schema = GraphQL::Schema->from_doc(<<'EOF'); type Query { dateTimeNow: DateTime } EOF post '/graphql' => sub { send_as JSON => execute( $schema, body_parameters->{query}, { dateTimeNow => sub { DateTime->now } }, undef, body_parameters->{variables}, body_parameters->{operationName}, undef, ); }; =head1 DESCRIPTION Class implementing the scheme by which additional GraphQL type classes can be implemented. The author considers this is only worth doing for scalars, and indeed this scheme is (now) how the non-standard C is implemented in graphql-perl. If one wants to create other types (L, L, etc), then the L is already available. However, any type can be registered with the L method, and will be automatically available to L objects with no additional code. =head1 METHODS =head2 register($graphql_type) When called with a L subclass, will register it, otherwise dies. =cut my @registered; method register((InstanceOf['GraphQL::Type']) $type) { push @registered, $type; } =head2 registered Returns a list of registered classes. =cut method registered() { @registered; } __PACKAGE__->meta->make_immutable(); 1; GraphQL-0.53/lib/GraphQL/Debug.pm0000644000175000017500000000131713433571113016320 0ustar osboxesosboxespackage GraphQL::Debug; use 5.014; use strict; use warnings; use Exporter 'import'; our @EXPORT_OK = qw(_debug); =head1 NAME GraphQL::Debug - debug GraphQL =cut =head1 SYNOPSIS use GraphQL::Debug qw(_debug); use constant DEBUG => $ENV{GRAPHQL_DEBUG}; DEBUG and _debug('current_function', $value1, $value2); =head1 DESCRIPTION Creates debugging output when called. Intended to have its calls optimised out when debugging not sought, using the construct shown above. The values given will be passed through L. =cut # TODO make log instead of diag sub _debug { my $func = shift; require Test::More; Test::More::diag("$func: ", Test::More::explain(@_ == 1 ? @_ : [ @_ ])); } 1; GraphQL-0.53/lib/GraphQL/Schema.pm0000644000175000017500000002627214114450657016507 0ustar osboxesosboxespackage GraphQL::Schema; use 5.014; use strict; use warnings; use Moo; use Types::Standard -all; use GraphQL::Type::Library -all; use GraphQL::MaybeTypeCheck; use GraphQL::Debug qw(_debug); use GraphQL::Directive; use GraphQL::Introspection qw($SCHEMA_META_TYPE); use GraphQL::Language::Parser qw(parse); use GraphQL::Plugin::Type; use Module::Runtime qw(require_module); use Exporter 'import'; our $VERSION = '0.02'; our @EXPORT_OK = qw(lookup_type); use constant DEBUG => $ENV{GRAPHQL_DEBUG}; my @TYPE_ATTRS = qw(query mutation subscription); =head1 NAME GraphQL::Schema - GraphQL schema object =head1 SYNOPSIS use GraphQL::Schema; use GraphQL::Type::Object; my $schema = GraphQL::Schema->new( query => GraphQL::Type::Object->new( name => 'Query', fields => { getObject => { type => $interfaceType, resolve => sub { return {}; } } } ) ); =head1 DESCRIPTION Class implementing GraphQL schema. =head1 ATTRIBUTES =head2 query =cut has query => (is => 'ro', isa => InstanceOf['GraphQL::Type::Object'], required => 1); =head2 mutation =cut has mutation => (is => 'ro', isa => InstanceOf['GraphQL::Type::Object']); =head2 subscription =cut has subscription => (is => 'ro', isa => InstanceOf['GraphQL::Type::Object']); =head2 types Defaults to the types returned by L. Note that this includes the standard scalar types always loaded by L. If you wish to supply an overriding value for this attribute, bear that in mind. =cut has types => ( is => 'ro', isa => ArrayRef[ConsumerOf['GraphQL::Role::Named']], default => sub { [ GraphQL::Plugin::Type->registered ] }, ); =head2 directives =cut has directives => ( is => 'ro', isa => ArrayRef[InstanceOf['GraphQL::Directive']], default => sub { \@GraphQL::Directive::SPECIFIED_DIRECTIVES }, ); =head1 METHODS =head2 name2type In this schema, returns a hash-ref mapping all types' names to their type object. =cut has name2type => (is => 'lazy', isa => Map[StrNameValid, ConsumerOf['GraphQL::Role::Named']]); sub _build_name2type { my ($self) = @_; my @types = grep $_, (map $self->$_, @TYPE_ATTRS), $SCHEMA_META_TYPE; push @types, @{ $self->types || [] }; my %name2type; map _expand_type(\%name2type, $_), @types; \%name2type; } =head2 get_possible_types($abstract_type) In this schema, get all of either the implementation types (if interface) or possible types (if union) of the C<$abstract_type>. =cut fun _expand_type( (Map[StrNameValid, ConsumerOf['GraphQL::Role::Named']]) $map, (InstanceOf['GraphQL::Type']) $type, ) :ReturnType(ArrayRef[ConsumerOf['GraphQL::Role::Named']]) { @_ = ($map, $type->of), goto &_expand_type if $type->can('of'); # avoid blowing out the stack my $name = $type->name if $type->can('name'); return [] if $name and $map->{$name} and $map->{$name} == $type; # seen die "Duplicate type $name" if $map->{$name}; $map->{$name} = $type; my @types; push @types, ($type, map @{ _expand_type($map, $_) }, @{ $type->interfaces || [] }) if $type->isa('GraphQL::Type::Object'); push @types, ($type, map @{ _expand_type($map, $_) }, @{ $type->get_types }) if $type->isa('GraphQL::Type::Union'); if (grep $type->DOES($_), qw(GraphQL::Role::FieldsInput GraphQL::Role::FieldsOutput)) { my $fields = $type->fields||{}; push @types, map { map @{ _expand_type($map, $_->{type}) }, $_, values %{ $_->{args}||{} } } values %$fields; } DEBUG and _debug('_expand_type', \@types); \@types; } has _interface2types => (is => 'lazy', isa => Map[StrNameValid, ArrayRef[InstanceOf['GraphQL::Type::Object']]]); sub _build__interface2types { my ($self) = @_; my $name2type = $self->name2type||{}; my %interface2types; map { my $o = $_; map { push @{$interface2types{$_->name}}, $o; # TODO assert_object_implements_interface } @{ $o->interfaces||[] }; } grep $_->isa('GraphQL::Type::Object'), values %$name2type; \%interface2types; } method get_possible_types( (ConsumerOf['GraphQL::Role::Abstract']) $abstract_type ) :ReturnType(ArrayRef[InstanceOf['GraphQL::Type::Object']]) { return $abstract_type->get_types if $abstract_type->isa('GraphQL::Type::Union'); $self->_interface2types->{$abstract_type->name} || []; } =head2 is_possible_type($abstract_type, $possible_type) In this schema, is the given C<$possible_type> either an implementation (if interface) or a possibility (if union) of the C<$abstract_type>? =cut has _possible_type_map => (is => 'rw', isa => Map[StrNameValid, Map[StrNameValid, Bool]]); method is_possible_type( (ConsumerOf['GraphQL::Role::Abstract']) $abstract_type, (InstanceOf['GraphQL::Type::Object']) $possible_type, ) :ReturnType(Bool) { my $map = $self->_possible_type_map || {}; return $map->{$abstract_type->name}{$possible_type->name} if $map->{$abstract_type->name}; # we know about the abstract_type my @possibles = @{ $self->get_possible_types($abstract_type)||[] }; die <name]} in schema. Check that schema.types is defined and is an array of all possible types in the schema. EOF $map->{$abstract_type->name} = { map { ($_->name => 1) } @possibles }; $self->_possible_type_map($map); $map->{$abstract_type->name}{$possible_type->name}; } =head2 assert_object_implements_interface($type, $iface) In this schema, does the given C<$type> implement interface C<$iface>? If not, throw exception. =cut method assert_object_implements_interface( (ConsumerOf['GraphQL::Role::Abstract']) $abstract_type, (InstanceOf['GraphQL::Type::Object']) $possible_type, ) { my @types = @{ $self->types }; return; } =head2 from_ast($ast[, \%kind2class]) Class method. Takes AST (array-ref of hash-refs) made by L and returns a schema object. Will not be a complete schema since it will have only default resolvers. If C<\%kind2class> is given, it will override the default mapping of SDL keywords to Perl classes. This is probably most useful for L. The default is available as C<%GraphQL::Schema::KIND2CLASS>. E.g. my $schema = GraphQL::Schema->from_ast( $doc, { %GraphQL::Schema::KIND2CLASS, type => 'GraphQL::Type::Object::DBIC' } ); Makes available the additional types returned by L. =cut our %KIND2CLASS = qw( type GraphQL::Type::Object enum GraphQL::Type::Enum interface GraphQL::Type::Interface union GraphQL::Type::Union scalar GraphQL::Type::Scalar input GraphQL::Type::InputObject ); my %CLASS2KIND = reverse %KIND2CLASS; method from_ast( ArrayRef[HashRef] $ast, HashRef $kind2class = \%KIND2CLASS, ) :ReturnType(InstanceOf[__PACKAGE__]) { DEBUG and _debug('Schema.from_ast', $ast); my @type_nodes = grep $kind2class->{$_->{kind}}, @$ast; my ($schema_node, $e) = grep $_->{kind} eq 'schema', @$ast; die "Must provide only one schema definition.\n" if $e; my %name2type = (map { $_->name => $_ } GraphQL::Plugin::Type->registered); for (@type_nodes) { die "Type '$_->{name}' was defined more than once.\n" if $name2type{$_->{name}}; require_module $kind2class->{$_->{kind}}; $name2type{$_->{name}} = $kind2class->{$_->{kind}}->from_ast(\%name2type, $_); } if (!$schema_node) { # infer one $schema_node = +{ map { $name2type{ucfirst $_} ? ($_ => ucfirst $_) : () } @TYPE_ATTRS }; } die "Must provide schema definition with query type or a type named Query.\n" unless $schema_node->{query}; my @directives = map GraphQL::Directive->from_ast(\%name2type, $_), grep $_->{kind} eq 'directive', @$ast; my $schema = $self->new( (map { $schema_node->{$_} ? ($_ => $name2type{$schema_node->{$_}} // die "Specified $_ type '$schema_node->{$_}' not found.\n") : () } @TYPE_ATTRS), (@directives ? (directives => [ @GraphQL::Directive::SPECIFIED_DIRECTIVES, @directives ]) : ()), types => [ values %name2type ], ); $schema->name2type; # walks all types, fields, args - finds undefined types $schema; } =head2 from_doc($doc[, \%kind2class]) Class method. Takes text that is a Schema Definition Language (SDL) (aka Interface Definition Language) document and returns a schema object. Will not be a complete schema since it will have only default resolvers. As of v0.32, this accepts both old-style "meaningful comments" and new-style string values, as field or type descriptions. If C<\%kind2class> is given, it will override the default mapping of SDL keywords to Perl classes. This is probably most useful for L. The default is available as C<%GraphQL::Schema::KIND2CLASS>. =cut method from_doc( Str $doc, HashRef $kind2class = \%KIND2CLASS, ) :ReturnType(InstanceOf[__PACKAGE__]) { $self->from_ast(parse($doc), $kind2class); } =head2 to_doc($doc) Returns Schema Definition Language (SDL) document that describes this schema object. As of v0.32, this produces the new-style descriptions that are string values, rather than old-style "meaningful comments". As of v0.33, will not return a description of types supplied with the attribute L. Obviously, by default this includes types returned by L. =cut has to_doc => (is => 'lazy', isa => Str); my %directive2builtin = map { ($_=>1) } @GraphQL::Directive::SPECIFIED_DIRECTIVES; sub _build_to_doc { my ($self) = @_; my $schema_doc; if (grep $self->$_->name ne ucfirst $_, grep $self->$_, @TYPE_ATTRS) { $schema_doc = join('', map "$_\n", "schema {", (map " $_: @{[$self->$_->name]}", grep $self->$_, @TYPE_ATTRS), "}"); } my %supplied_type = (map {$_->name => 1} GraphQL::Plugin::Type->registered); join "\n", grep defined, $schema_doc, (map $_->to_doc, sort { $a->name cmp $b->name } grep !$directive2builtin{$_}, @{ $self->directives }), (map $self->name2type->{$_}->to_doc, grep !/^__/, grep $CLASS2KIND{ref $self->name2type->{$_}}, grep !$supplied_type{$_}, sort keys %{$self->name2type}), ; } =head2 name2directive In this schema, returns a hash-ref mapping all directives' names to their directive object. =cut has name2directive => (is => 'lazy', isa => Map[StrNameValid, InstanceOf['GraphQL::Directive']]); method _build_name2directive() { +{ map { ($_->name => $_) } @{ $self->directives } }; } =head1 FUNCTIONS =head2 lookup_type($typedef, $name2type) Turns given AST fragment into a type. If the hash-ref's C member is a string, will return a type of that name. If an array-ref, first element must be either C or C, second will be a recursive AST fragment, which will be passed into a recursive call. The result will then have the modifier method (C or C) called, and that will be returned. =cut fun lookup_type( HashRef $typedef, (Map[StrNameValid, InstanceOf['GraphQL::Type']]) $name2type, ) :ReturnType(InstanceOf['GraphQL::Type']) { my $type = $typedef->{type}; die "Undefined type given\n" if !defined $type; return $name2type->{$type} // die "Unknown type '$type'.\n" if is_Str($type); my ($wrapper_type, $wrapped) = @$type; lookup_type($wrapped, $name2type)->$wrapper_type; } __PACKAGE__->meta->make_immutable(); 1; GraphQL-0.53/lib/GraphQL/PubSub.pm0000644000175000017500000000427114070317276016502 0ustar osboxesosboxespackage GraphQL::PubSub; use 5.014; use strict; use warnings; use Moo; use GraphQL::Debug qw(_debug); use Types::TypeTiny -all; use Types::Standard -all; use GraphQL::Type::Library -all; use GraphQL::MaybeTypeCheck; use constant DEBUG => $ENV{GRAPHQL_DEBUG}; =head1 NAME GraphQL::PubSub - publish/subscribe =head1 SYNOPSIS use GraphQL::PubSub; my $pubsub = GraphQL::PubSub->new; $pubsub->subscribe('channel1', \&callback); $pubsub->publish('channel1', 1); $pubsub->unsubscribe('channel1', \&callback); =head1 DESCRIPTION Encapsulates the publish/subscribe logic needed by L. =head1 METHODS =head2 subscribe($channel, \&callback[, \&error_callback]) Registers the given callback on the given channel. The optional second "error" callback is called as a method on the object when an exception is thrown by the first callback. If not given, the default is for the subscription to be cancelled with L. The error callback will be called with values of the channel, the original callback (to enable unsubscribing), the exception thrown, then the values passed to the original callback. Any exceptions will be ignored. =cut has _subscriptions => (is => 'ro', isa => HashRef, default => sub { {} }); method _default_error_callback(Str $channel, CodeLike $callback, Any $exception, @values) { eval { $self->unsubscribe($channel, $callback) }; } method subscribe(Str $channel, CodeLike $callback, Maybe[CodeLike] $error_callback = undef) { $self->_subscriptions->{$channel}{$callback} = [ $callback, $error_callback || \&_default_error_callback, ]; } =head2 unsubscribe($channel, \&callback) Removes the given callback from the given channel. =cut method unsubscribe(Str $channel, CodeLike $callback) { delete $self->_subscriptions->{$channel}{$callback}; } =head2 publish($channel, @values) Calls each callback registered on the given channel, with the given values. =cut method publish(Str $channel, @values) { for my $cb (values %{ $self->_subscriptions->{$channel} }) { my ($normal, $error) = @$cb; eval { $normal->(@values) }; eval { $self->$error($channel, $normal, $@, @values) } if $@; } } __PACKAGE__->meta->make_immutable(); 1; GraphQL-0.53/lib/GraphQL/Execution.pm0000644000175000017500000005775014070317276017257 0ustar osboxesosboxespackage GraphQL::Execution; use 5.014; use strict; use warnings; use Types::Standard -all; use Types::TypeTiny -all; use GraphQL::Type::Library -all; use GraphQL::MaybeTypeCheck; use GraphQL::Language::Parser qw(parse); use GraphQL::Error; use JSON::MaybeXS; use GraphQL::Debug qw(_debug); use GraphQL::Introspection qw( $SCHEMA_META_FIELD_DEF $TYPE_META_FIELD_DEF $TYPE_NAME_META_FIELD_DEF ); use GraphQL::Directive; use GraphQL::Schema qw(lookup_type); use Exporter 'import'; =head1 NAME GraphQL::Execution - Execute GraphQL queries =cut our @EXPORT_OK = qw( execute ); our $VERSION = '0.02'; my $JSON = JSON::MaybeXS->new->allow_nonref; use constant DEBUG => $ENV{GRAPHQL_DEBUG}; # "DEBUG and" gets optimised out if false =head1 SYNOPSIS use GraphQL::Execution qw(execute); my $result = execute($schema, $doc, $root_value); =head1 DESCRIPTION Executes a GraphQL query, returns results. =head1 METHODS =head2 execute my $result = execute( $schema, $doc, # can also be AST $root_value, $context_value, $variable_values, $operation_name, $field_resolver, $promise_code, ); =over =item $schema A L. =item $doc Either a GraphQL query document to be fed in to L, or a return value from that. =item $root_value A root value that can be used by field-resolvers. The default one needs a code-ref, a hash-ref or an object. For instance: my $schema = GraphQL::Schema->from_doc(<<'EOF'); type Query { dateTimeNow: String, hi: String } EOF my $root_value = { dateTimeNow => sub { DateTime->now->ymd }, hi => "Bob", }; my $data = execute($schema, "{ dateTimeNow hi }", $root_value); will return: { data => { dateTimeNow => { ymd => '20190501' }, hi => 'Bob', } } Be aware that with the default field-resolver, when it calls a method, that method will get called with L L, L. To override that to pass no parameters, this is suitable as a C<$field_resolver> parameters: sub { my ($root_value, $args, $context, $info) = @_; my $field_name = $info->{field_name}; my $property = ref($root_value) eq 'HASH' ? $root_value->{$field_name} : $root_value; return $property->($args, $context, $info) if ref $property eq 'CODE'; return $root_value->$field_name if ref $property; # no args $property; } =item $context_value A per-request scalar, that will be passed to field-resolvers. =item $variable_values A hash-ref, typically the decoded JSON object supplied by a client. E.g. for this query: query q($input: TestInputObject) { fieldWithObjectInput(input: $input) } The C<$variable_values> will need to be a JSON object with a key C, whose value will need to conform to the L C. The purpose of this is to avoid needing to hard-code input values in your query. This aids in, among other things, being able to whitelist individual queries as acceptable, non-abusive queries to your system; and being able to generate client-side code for client-side validation rather than including the full GraphQL system in client code. =item $operation_name A string (or C) that if given will be the name of one of the operations in the query. =item $field_resolver A code-ref to be used instead of the default field-resolver. =item $promise_code If you need to return a promise, supply a hash-ref matching L. =back =cut fun execute( (InstanceOf['GraphQL::Schema']) $schema, Str | ArrayRef[HashRef] $doc, Any $root_value = undef, Any $context_value = undef, Maybe[HashRef] $variable_values = undef, Maybe[Str] $operation_name = undef, Maybe[CodeLike] $field_resolver = undef, Maybe[PromiseCode] $promise_code = undef, ) :ReturnType(ExecutionResult | Promise) { my $context = eval { my $ast = ref($doc) ? $doc : parse($doc); _build_context( $schema, $ast, $root_value, $context_value, $variable_values, $operation_name, $field_resolver, $promise_code, ); }; DEBUG and _debug('execute', $context, $@); return _build_response(_wrap_error($@)) if $@; my $result = _execute_operation( $context, $context->{operation}, $root_value, ); DEBUG and _debug('execute(result)', $result, $@); _build_response($result, 1); } fun _build_response( ExecutionPartialResult | Promise $result, Bool $force_data = 0, ) :ReturnType(ExecutionResult | Promise) { return $result->then(sub { _build_response(@_) }) if is_Promise($result); my @errors = @{$result->{errors} || []}; +{ $force_data ? (data => undef) : (), # default if none given %$result, @errors ? (errors => [ map $_->to_json, @{$result->{errors}} ]) : (), }; } fun _wrap_error( Any $error, ) :ReturnType(ExecutionPartialResult) { return $error if is_ExecutionPartialResult($error); +{ errors => [ GraphQL::Error->coerce($error) ] }; } fun _build_context( (InstanceOf['GraphQL::Schema']) $schema, ArrayRef[HashRef] $ast, Any $root_value, Any $context_value, Maybe[HashRef] $variable_values, Maybe[Str] $operation_name, Maybe[CodeLike] $field_resolver, Maybe[PromiseCode] $promise_code, ) :ReturnType(HashRef) { my %fragments = map { ($_->{name} => $_) } grep $_->{kind} eq 'fragment', @$ast; my @operations = grep $_->{kind} eq 'operation', @$ast; die "No operations supplied.\n" if !@operations; die "Can only execute document containing fragments or operations\n" if @$ast != keys(%fragments) + @operations; my $operation = _get_operation($operation_name, \@operations); { schema => $schema, fragments => \%fragments, root_value => $root_value, context_value => $context_value, operation => $operation, variable_values => _variables_apply_defaults( $schema, $operation->{variables} || {}, $variable_values || {}, ), field_resolver => $field_resolver || \&_default_field_resolver, promise_code => $promise_code, }; } # takes each operation var: query q(a: String) # applies to it supplied variable from web request # if none, applies any defaults in the operation var: query q(a: String = "h") # converts with graphql_to_perl (which also validates) to Perl values # return { varname => { value => ..., type => $type } } fun _variables_apply_defaults( (InstanceOf['GraphQL::Schema']) $schema, HashRef $operation_variables, HashRef $variable_values, ) :ReturnType(HashRef) { my @bad = grep { ! lookup_type($operation_variables->{$_}, $schema->name2type)->DOES('GraphQL::Role::Input'); } keys %$operation_variables; die "Variable '\$$bad[0]' is type '@{[ lookup_type($operation_variables->{$bad[0]}, $schema->name2type)->to_string ]}' which cannot be used as an input type.\n" if @bad; +{ map { my $opvar = $operation_variables->{$_}; my $opvar_type = lookup_type($opvar, $schema->name2type); my $parsed_value; my $maybe_value = $variable_values->{$_} // $opvar->{default_value}; eval { $parsed_value = $opvar_type->graphql_to_perl($maybe_value) }; if ($@) { my $error = $@; $error =~ s#\s+at.*line\s+\d+\.#.#; # JSON cannot encode scalar references my $jsonable = _coerce_for_error($maybe_value); die "Variable '\$$_' got invalid value @{[$JSON->canonical->encode($jsonable)]}.\n$error"; } ($_ => { value => $parsed_value, type => $opvar_type }) } keys %$operation_variables }; } fun _get_operation( Maybe[Str] $operation_name, ArrayRef[HashRef] $operations, ) { DEBUG and _debug('_get_operation', @_); if (!$operation_name) { die "Must provide operation name if query contains multiple operations.\n" if @$operations > 1; return $operations->[0]; } my @matching = grep $_->{name} eq $operation_name, @$operations; return $matching[0] if @matching == 1; die "No operations matching '$operation_name' found.\n"; } fun _execute_operation( HashRef $context, HashRef $operation, Any $root_value, ) :ReturnType(ExecutionPartialResult | Promise) { my $op_type = $operation->{operationType} || 'query'; my $type = $context->{schema}->$op_type; return _wrap_error("No $op_type in schema") if !$type; my ($fields) = $type->_collect_fields( $context, $operation->{selections}, [[], {}], {}, ); DEBUG and _debug('_execute_operation(fields)', $fields, $root_value); my $path = []; my $execute = $op_type eq 'mutation' ? \&_execute_fields_serially : \&_execute_fields; my $result = eval { my $result = $execute->($context, $type, $root_value, $path, $fields); return $result if !is_Promise($result); $result->then(undef, sub { $context->{promise_code}{resolve}->( +{ data => undef, %{_wrap_error($_[0])} } ); }); }; return _wrap_error($@) if $@; $result; } fun _execute_fields( HashRef $context, (InstanceOf['GraphQL::Type']) $parent_type, Any $root_value, ArrayRef $path, FieldsGot $fields, ) :ReturnType(ExecutionPartialResult | Promise) { my (%name2executionresult, @errors); my $promise_present; DEBUG and _debug('_execute_fields', $parent_type->to_string, $fields, $root_value); my ($field_names, $nodes_defs) = @$fields; for my $result_name (@$field_names) { my $nodes = $nodes_defs->{$result_name}; my $field_node = $nodes->[0]; my $field_name = $field_node->{name}; my $field_def = _get_field_def($context->{schema}, $parent_type, $field_name); DEBUG and _debug('_execute_fields(resolve)', $parent_type->to_string, $nodes, $root_value, $field_def); next if !$field_def; my $resolve = $field_def->{resolve} || $context->{field_resolver}; my $info = _build_resolve_info( $context, $parent_type, $field_def, [ @$path, $result_name ], $nodes, ); my $result = _resolve_field_value_or_error( $context, $field_def, $nodes, $resolve, $root_value, $info, ); DEBUG and _debug('_execute_fields(resolved)', $parent_type->to_string, $result); $result = _complete_value_catching_error( $context, $field_def->{type}, $nodes, $info, [ @$path, $result_name ], $result, ); $promise_present ||= is_Promise($result); DEBUG and _debug("_execute_fields(complete)($result_name)", $result); $name2executionresult{$result_name} = $result; } DEBUG and _debug('_execute_fields(done)', \%name2executionresult, \@errors, $promise_present); return _promise_for_hash($context, \%name2executionresult, \@errors) if $promise_present; _merge_hash( [ keys %name2executionresult ], [ values %name2executionresult ], \@errors, ); } fun _merge_hash( ArrayRef[Str] $keys, ArrayRef[ExecutionPartialResult] $values, (ArrayRef[InstanceOf['GraphQL::Error']]) $errors, ) :ReturnType(ExecutionPartialResult) { DEBUG and _debug('_merge_hash', $keys, $values, $errors); my @errors = (@$errors, map @{$_->{errors} || []}, @$values); my %name2data; for (my $i = @$values - 1; $i >= 0; $i--) { $name2data{$keys->[$i]} = $values->[$i]{data}; } DEBUG and _debug('_merge_hash(after)', \%name2data, \@errors); +{ %name2data ? (data => \%name2data) : (), @errors ? (errors => \@errors) : () }; } fun _promise_for_hash( HashRef $context, HashRef $hash, (ArrayRef[InstanceOf['GraphQL::Error']]) $errors, ) :ReturnType(Promise) { my ($keys, $values) = ([ keys %$hash ], [ values %$hash ]); DEBUG and _debug('_promise_for_hash', $keys); die "Given a promise in object but no PromiseCode given\n" if !$context->{promise_code}; $context->{promise_code}{all}->(@$values)->then(sub { DEBUG and _debug('_promise_for_hash(all)', \@_); _merge_hash($keys, [ map $_->[0], @_ ], $errors); }); } fun _execute_fields_serially( HashRef $context, (InstanceOf['GraphQL::Type']) $parent_type, Any $root_value, ArrayRef $path, FieldsGot $fields, ) { DEBUG and _debug('_execute_fields_serially', $parent_type->to_string, $fields, $root_value); # TODO implement goto &_execute_fields; } use constant FIELDNAME2SPECIAL => { map { ($_->{name} => $_) } $SCHEMA_META_FIELD_DEF, $TYPE_META_FIELD_DEF }; fun _get_field_def( (InstanceOf['GraphQL::Schema']) $schema, (InstanceOf['GraphQL::Type']) $parent_type, StrNameValid $field_name, ) :ReturnType(Maybe[HashRef]) { return $TYPE_NAME_META_FIELD_DEF if $field_name eq $TYPE_NAME_META_FIELD_DEF->{name}; return FIELDNAME2SPECIAL->{$field_name} if FIELDNAME2SPECIAL->{$field_name} and $parent_type == $schema->query; $parent_type->fields->{$field_name}; } # NB similar ordering as _execute_fields - graphql-js switches fun _build_resolve_info( HashRef $context, (InstanceOf['GraphQL::Type']) $parent_type, HashRef $field_def, ArrayRef $path, ArrayRef[HashRef] $nodes, ) { { field_name => $nodes->[0]{name}, field_nodes => $nodes, return_type => $field_def->{type}, parent_type => $parent_type, path => $path, schema => $context->{schema}, fragments => $context->{fragments}, root_value => $context->{root_value}, operation => $context->{operation}, variable_values => $context->{variable_values}, promise_code => $context->{promise_code}, }; } fun _resolve_field_value_or_error( HashRef $context, HashRef $field_def, ArrayRef[HashRef] $nodes, Maybe[CodeLike] $resolve, Maybe[Any] $root_value, HashRef $info, ) { DEBUG and _debug('_resolve_field_value_or_error', $nodes, $field_def, eval { $JSON->encode($nodes->[0]) }); my $result = eval { my $args = _get_argument_values( $field_def, $nodes->[0], $context->{variable_values}, ); DEBUG and _debug("_resolve_field_value_or_error(args)", $args, eval { $JSON->encode($args) }); $resolve->($root_value, $args, $context->{context_value}, $info) }; return GraphQL::Error->coerce($@) if $@; $result; } fun _complete_value_catching_error( HashRef $context, (InstanceOf['GraphQL::Type']) $return_type, ArrayRef[HashRef] $nodes, HashRef $info, ArrayRef $path, Any $result, ) :ReturnType(ExecutionPartialResult | Promise) { DEBUG and _debug('_complete_value_catching_error(before)', $return_type->to_string, $result); if ($return_type->isa('GraphQL::Type::NonNull')) { return _complete_value_with_located_error(@_); } my $result = eval { my $c = _complete_value_with_located_error(@_); return $c if !is_Promise($c); $c->then(undef, sub { $context->{promise_code}{resolve}->(_wrap_error(@_)) }); }; DEBUG and _debug("_complete_value_catching_error(after)(@{[$return_type->to_string]})", $return_type->to_string, $result, $@); return _wrap_error($@) if $@; $result; } fun _complete_value_with_located_error( HashRef $context, (InstanceOf['GraphQL::Type']) $return_type, ArrayRef[HashRef] $nodes, HashRef $info, ArrayRef $path, Any $result, ) :ReturnType(ExecutionPartialResult | Promise) { my $result = eval { my $c = _complete_value(@_); return $c if !is_Promise($c); $c->then(undef, sub { $context->{promise_code}{reject}->( _located_error($_[0], $nodes, $path) ) }); }; DEBUG and _debug('_complete_value_with_located_error(after)', $return_type->to_string, $result, $@); die _located_error($@, $nodes, $path) if $@; $result; } fun _complete_value( HashRef $context, (InstanceOf['GraphQL::Type']) $return_type, ArrayRef[HashRef] $nodes, HashRef $info, ArrayRef $path, Any $result, ) :ReturnType(ExecutionPartialResult | Promise) { DEBUG and _debug('_complete_value', $return_type->to_string, $path, $result); if (is_Promise($result)) { my @outerargs = @_[0..4]; return $result->then(sub { _complete_value(@outerargs, $_[0]) }); } die $result if GraphQL::Error->is($result); if ($return_type->isa('GraphQL::Type::NonNull')) { my $completed = _complete_value( $context, $return_type->of, $nodes, $info, $path, $result, ); DEBUG and _debug('_complete_value(NonNull)', $return_type->to_string, $completed); # The !is_Promise is necessary unlike in the JS because there the # null-check will work fine on either a promise or a real value. die GraphQL::Error->coerce( "Cannot return null for non-nullable field @{[$info->{parent_type}->name]}.@{[$info->{field_name}]}." ) if !is_Promise($completed) and !defined $completed->{data}; return $completed; } return { data => undef } if !defined $result; $return_type->_complete_value( $context, $nodes, $info, $path, $result, ); } fun _located_error( Any $error, ArrayRef[HashRef] $nodes, ArrayRef $path, ) { DEBUG and _debug('_located_error', $error); $error = GraphQL::Error->coerce($error); return $error if $error->locations; GraphQL::Error->coerce($error)->but( locations => [ map $_->{location}, @$nodes ], path => $path, ); } fun _get_argument_values( (HashRef | InstanceOf['GraphQL::Directive']) $def, HashRef $node, Maybe[HashRef] $variable_values = {}, ) { my $arg_defs = $def->{args}; my $arg_nodes = $node->{arguments}; DEBUG and _debug("_get_argument_values", $arg_defs, $arg_nodes, $variable_values, eval { $JSON->encode($node) }); return {} if !$arg_defs; my @bad = grep { !exists $arg_nodes->{$_} and !defined $arg_defs->{$_}{default_value} and $arg_defs->{$_}{type}->isa('GraphQL::Type::NonNull') } keys %$arg_defs; die GraphQL::Error->new( message => "Argument '$bad[0]' of type ". "'@{[$arg_defs->{$bad[0]}{type}->to_string]}' not given.", nodes => [ $node ], ) if @bad; @bad = grep { ref($arg_nodes->{$_}) eq 'SCALAR' and $variable_values->{${$arg_nodes->{$_}}} and !_type_will_accept($arg_defs->{$_}{type}, $variable_values->{${$arg_nodes->{$_}}}{type}) } keys %$arg_defs; die GraphQL::Error->new( message => "Variable '\$${$arg_nodes->{$bad[0]}}' of type '@{[$variable_values->{${$arg_nodes->{$bad[0]}}}{type}->to_string]}'". " where expected '@{[$arg_defs->{$bad[0]}{type}->to_string]}'.", nodes => [ $node ], ) if @bad; my @novar = grep { ref($arg_nodes->{$_}) eq 'SCALAR' and (!$variable_values or !exists $variable_values->{${$arg_nodes->{$_}}}) and !defined $arg_defs->{$_}{default_value} and $arg_defs->{$_}{type}->isa('GraphQL::Type::NonNull') } keys %$arg_defs; die GraphQL::Error->new( message => "Argument '$novar[0]' of type ". "'@{[$arg_defs->{$novar[0]}{type}->to_string]}'". " was given variable '\$${$arg_nodes->{$novar[0]}}' but no runtime value.", nodes => [ $node ], ) if @novar; my @enumfail = grep { ref($arg_nodes->{$_}) eq 'REF' and ref(${$arg_nodes->{$_}}) eq 'SCALAR' and !$arg_defs->{$_}{type}->isa('GraphQL::Type::Enum') and !($arg_defs->{$_}{type}->isa('GraphQL::Type::NonNull') and $arg_defs->{$_}{type}->of->isa('GraphQL::Type::Enum')) } keys %$arg_defs; die GraphQL::Error->new( message => "Argument '$enumfail[0]' of type ". "'@{[$arg_defs->{$enumfail[0]}{type}->to_string]}'". " was given ${${$arg_nodes->{$enumfail[0]}}} which is enum value.", nodes => [ $node ], ) if @enumfail; my @enumstring = grep { defined($arg_nodes->{$_}) and !ref($arg_nodes->{$_}) } grep $arg_defs->{$_}{type}->isa('GraphQL::Type::Enum'), keys %$arg_defs; die GraphQL::Error->new( message => "Argument '$enumstring[0]' of type ". "'@{[$arg_defs->{$enumstring[0]}{type}->to_string]}'". " was given '$arg_nodes->{$enumstring[0]}' which is not enum value.", nodes => [ $node ], ) if @enumstring; return {} if !$arg_nodes; my %coerced_values; for my $name (keys %$arg_defs) { my $arg_def = $arg_defs->{$name}; my $arg_type = $arg_def->{type}; my $argument_node = $arg_nodes->{$name}; my $default_value = $arg_def->{default_value}; DEBUG and _debug("_get_argument_values($name)", $arg_def, $arg_type, $argument_node, $default_value); if (!exists $arg_nodes->{$name}) { # none given - apply type arg's default if any. already validated perl $coerced_values{$name} = $default_value if exists $arg_def->{default_value}; next; } elsif (ref($argument_node) eq 'SCALAR') { # scalar ref means it's a variable. already validated perl $coerced_values{$name} = ($variable_values && $variable_values->{$$argument_node} && $variable_values->{$$argument_node}{value}) // $default_value; next; } else { # query literal or variable. JSON land, needs convert/validate $coerced_values{$name} = _coerce_value( $argument_node, $variable_values, $default_value ); } next if !exists $coerced_values{$name}; DEBUG and _debug("_get_argument_values($name after initial)", $arg_def, $arg_type, $argument_node, $default_value, eval { $JSON->encode(\%coerced_values) }); eval { $coerced_values{$name} = $arg_type->graphql_to_perl($coerced_values{$name}) }; DEBUG and do { local $@; _debug("_get_argument_values($name after coerce)", eval { $JSON->encode(\%coerced_values) }) }; if ($@) { my $error = $@; $error =~ s#\s+at.*line\s+\d+\.#.#; # JSON can't encode scalar references my $jsonable = _coerce_for_error($coerced_values{$name}); die GraphQL::Error->new( message => "Argument '$name' got invalid value" . " @{[$JSON->encode($jsonable)]}.\nExpected '" . $arg_type->to_string . "'.\n$error", nodes => [ $node ], ); } } \%coerced_values; } fun _coerce_for_error(Any $value) { my $ref = ref $value; my $ret = 'SCALAR' eq $ref ? $$value : 'ARRAY' eq $ref ? [ map { _coerce_for_error($_) } @$value ] : 'HASH' eq $ref ? { map { $_ => _coerce_for_error($value->{$_}) } keys %$value } : $value ; return $ret; } fun _coerce_value( Any $argument_node, Maybe[HashRef] $variable_values, Any $default_value, ) { if (ref($argument_node) eq 'SCALAR') { # scalar ref means it's a variable. already validated perl but # revalidate again as may be in middle of array which would need # validate return ($variable_values && $variable_values->{$$argument_node} && $variable_values->{$$argument_node}{value}) // $default_value; } elsif (ref($argument_node) eq 'REF') { # double ref means it's an enum value. JSON land, needs convert/validate return $$$argument_node; } elsif (ref($argument_node) eq 'ARRAY') { # list. recurse return [ map _coerce_value( $_, $variable_values, $default_value ), @$argument_node ]; } elsif (ref($argument_node) eq 'HASH') { # hash. recurse return +{ map { $_ => _coerce_value( $argument_node->{$_}, $variable_values, $default_value ) } keys %$argument_node }; } else { # query literal. JSON land, needs convert/validate return $argument_node; } } fun _type_will_accept( (ConsumerOf['GraphQL::Role::Input']) $arg_type, (ConsumerOf['GraphQL::Role::Input']) $var_type, ) { return 1 if $arg_type == $var_type; $arg_type = $arg_type->of if $arg_type->isa('GraphQL::Type::NonNull'); $var_type = $var_type->of if $var_type->isa('GraphQL::Type::NonNull'); return 1 if $arg_type == $var_type; return 1 if $arg_type->to_string eq $var_type->to_string; ''; } # $root_value is either a hash with fieldnames as keys and either data # or coderefs as values # OR it's just a coderef itself # OR it's an object which gets tried for fieldname as method # any code gets called with obvious args fun _default_field_resolver( CodeLike | HashRef | InstanceOf $root_value, HashRef $args, Any $context, HashRef $info, ) { my $field_name = $info->{field_name}; my $property = is_HashRef($root_value) ? $root_value->{$field_name} : $root_value; DEBUG and _debug('_default_field_resolver', $root_value, $field_name, $args, $property); if (eval { CodeLike->($property); 1 }) { DEBUG and _debug('_default_field_resolver', 'codelike'); return $property->($args, $context, $info); } if (is_InstanceOf($root_value) and $root_value->can($field_name)) { DEBUG and _debug('_default_field_resolver', 'method'); return $root_value->$field_name($args, $context, $info); } $property; } 1; GraphQL-0.53/lib/GraphQL/Language/0000755000175000017500000000000014170415414016455 5ustar osboxesosboxesGraphQL-0.53/lib/GraphQL/Language/Grammar.pm0000644000175000017500000007226414006154243020412 0ustar osboxesosboxespackage GraphQL::Language::Grammar; use 5.014; use strict; use warnings; use base 'Pegex::Grammar'; use constant file => './graphql.pgx'; our $VERSION = '0.02'; =head1 NAME GraphQL::Language::Grammar - GraphQL grammar =head1 SYNOPSIS use Pegex::Parser; use GraphQL::Language::Grammar; use Pegex::Tree::Wrap; use Pegex::Input; my $parser = Pegex::Parser->new( grammar => GraphQL::Language::Grammar->new, receiver => Pegex::Tree::Wrap->new, ); my $text = 'query q { foo(name: "hi") { id } }'; my $input = Pegex::Input->new(string => $text); my $got = $parser->parse($input); =head1 DESCRIPTION This is a subclass of L, with the GraphQL grammar. =head1 METHODS =head2 make_tree Override method from L. =cut sub make_tree { # Generated/Inlined by Pegex::Grammar (0.72) { '+grammar' => 'graphql', '+include' => 'pegex-atoms', '+toprule' => 'graphql', '+version' => '0.01', 'LSQUARE' => { '.rgx' => qr/\G\[/ }, 'RSQUARE' => { '.rgx' => qr/\G\]/ }, '_' => { '.rgx' => qr/\G(?:\s|\x{FEFF}|,|[\ \t]*\#[\ \t]*[^\r\n]*(?:\r?\n|\r!NL|\z))*/ }, 'alias' => { '.all' => [ { '.ref' => 'name' }, { '-skip' => 1, '.rgx' => qr/\G(?:\s|\x{FEFF}|,|[\ \t]*\#[\ \t]*[^\r\n]*(?:\r?\n|\r!NL|\z))*:(?:\s|\x{FEFF}|,|[\ \t]*\#[\ \t]*[^\r\n]*(?:\r?\n|\r!NL|\z))*/ } ] }, 'argument' => { '.all' => [ { '.ref' => 'name' }, { '-skip' => 1, '.rgx' => qr/\G(?:\s|\x{FEFF}|,|[\ \t]*\#[\ \t]*[^\r\n]*(?:\r?\n|\r!NL|\z))*:(?:\s|\x{FEFF}|,|[\ \t]*\#[\ \t]*[^\r\n]*(?:\r?\n|\r!NL|\z))*/ }, { '.ref' => 'value' } ] }, 'arguments' => { '.all' => [ { '-skip' => 1, '.rgx' => qr/\G(?:\s|\x{FEFF}|,|[\ \t]*\#[\ \t]*[^\r\n]*(?:\r?\n|\r!NL|\z))*\((?:\s|\x{FEFF}|,|[\ \t]*\#[\ \t]*[^\r\n]*(?:\r?\n|\r!NL|\z))*/ }, { '+min' => 1, '.ref' => 'argument' }, { '-skip' => 1, '.rgx' => qr/\G(?:\s|\x{FEFF}|,|[\ \t]*\#[\ \t]*[^\r\n]*(?:\r?\n|\r!NL|\z))*\)(?:\s|\x{FEFF}|,|[\ \t]*\#[\ \t]*[^\r\n]*(?:\r?\n|\r!NL|\z))*/ } ] }, 'argumentsDefinition' => { '.all' => [ { '-skip' => 1, '.rgx' => qr/\G(?:\s|\x{FEFF}|,|[\ \t]*\#[\ \t]*[^\r\n]*(?:\r?\n|\r!NL|\z))*\(/ }, { '+min' => 1, '.ref' => 'inputValueDefinition' }, { '-skip' => 1, '.rgx' => qr/\G(?:\s|\x{FEFF}|,|[\ \t]*\#[\ \t]*[^\r\n]*(?:\r?\n|\r!NL|\z))*\)/ } ] }, 'blockStringValue' => { '.rgx' => qr/\G"""((?:(?:\\""")|[^\x00-\x1f"]|[\t\n\r]|(?:"(?!"")))*)"""/ }, 'boolean' => { '.rgx' => qr/\G(true|false)/ }, 'defaultValue' => { '.all' => [ { '-skip' => 1, '.rgx' => qr/\G(?:\s|\x{FEFF}|,|[\ \t]*\#[\ \t]*[^\r\n]*(?:\r?\n|\r!NL|\z))*=(?:\s|\x{FEFF}|,|[\ \t]*\#[\ \t]*[^\r\n]*(?:\r?\n|\r!NL|\z))*/ }, { '.ref' => 'value_const' } ] }, 'definition' => { '.any' => [ { '.ref' => 'operationDefinition' }, { '.ref' => 'fragment' }, { '.ref' => 'typeSystemDefinition' }, { '-skip' => 1, '.ref' => 'ws2' } ] }, 'description' => { '.any' => [ { '.all' => [ { '-skip' => 1, '.rgx' => qr/\G[\s\n]*/ }, { '.ref' => 'string' }, { '-skip' => 1, '.rgx' => qr/\G[\s\n]*/ } ] }, { '-skip' => 1, '.ref' => '_' } ] }, 'directive' => { '.all' => [ { '-skip' => 1, '.rgx' => qr/\Gdirective(?:\s|\x{FEFF}|,|[\ \t]*\#[\ \t]*[^\r\n]*(?:\r?\n|\r!NL|\z))*\@(?:\s|\x{FEFF}|,|[\ \t]*\#[\ \t]*[^\r\n]*(?:\r?\n|\r!NL|\z))*/ }, { '.ref' => 'name' }, { '-skip' => 1, '.ref' => '_' }, { '+max' => 1, '.ref' => 'argumentsDefinition' }, { '-skip' => 1, '.rgx' => qr/\G(?:\s|\x{FEFF}|,|[\ \t]*\#[\ \t]*[^\r\n]*(?:\r?\n|\r!NL|\z))*on(?:\s|\x{FEFF}|,|[\ \t]*\#[\ \t]*[^\r\n]*(?:\r?\n|\r!NL|\z))*/ }, { '.ref' => 'directiveLocations' } ] }, 'directiveLocations' => { '.all' => [ { '+max' => 1, '-skip' => 1, '.rgx' => qr/\G(?:\s|\x{FEFF}|,|[\ \t]*\#[\ \t]*[^\r\n]*(?:\r?\n|\r!NL|\z))*\|(?:\s|\x{FEFF}|,|[\ \t]*\#[\ \t]*[^\r\n]*(?:\r?\n|\r!NL|\z))*/ }, { '.all' => [ { '.ref' => 'name' }, { '+min' => 0, '-flat' => 1, '.all' => [ { '-skip' => 1, '.rgx' => qr/\G(?:\s|\x{FEFF}|,|[\ \t]*\#[\ \t]*[^\r\n]*(?:\r?\n|\r!NL|\z))*\|(?:\s|\x{FEFF}|,|[\ \t]*\#[\ \t]*[^\r\n]*(?:\r?\n|\r!NL|\z))*/ }, { '.ref' => 'name' } ] } ] } ] }, 'directiveactual' => { '.all' => [ { '-skip' => 1, '.rgx' => qr/\G(?:\s|\x{FEFF}|,|[\ \t]*\#[\ \t]*[^\r\n]*(?:\r?\n|\r!NL|\z))*\@/ }, { '.ref' => 'name' }, { '+max' => 1, '.ref' => 'arguments' } ] }, 'directives' => { '+min' => 1, '.ref' => 'directiveactual' }, 'enumTypeDefinition' => { '.all' => [ { '-skip' => 1, '.rgx' => qr/\Genum(?:\s|\x{FEFF}|,|[\ \t]*\#[\ \t]*[^\r\n]*(?:\r?\n|\r!NL|\z))*/ }, { '.ref' => 'name' }, { '-skip' => 1, '.ref' => '_' }, { '+max' => 1, '.ref' => 'directives' }, { '+max' => 1, '.all' => [ { '-skip' => 1, '.rgx' => qr/\G(?:\s|\x{FEFF}|,|[\ \t]*\#[\ \t]*[^\r\n]*(?:\r?\n|\r!NL|\z))*\{/ }, { '+min' => 1, '.ref' => 'enumValueDefinition' }, { '-skip' => 1, '.rgx' => qr/\G(?:\s|\x{FEFF}|,|[\ \t]*\#[\ \t]*[^\r\n]*(?:\r?\n|\r!NL|\z))*\}/ } ] } ] }, 'enumValue' => { '.any' => [ { '.all' => [ { '.rgx' => qr/\G(true|false|null)/ }, { '.err' => 'Invalid enum value' } ] }, { '.ref' => 'name' } ] }, 'enumValueDefinition' => { '.all' => [ { '+max' => 1, '.ref' => 'description' }, { '.ref' => 'enumValue' }, { '+max' => 1, '.all' => [ { '-skip' => 1, '.ref' => '_' }, { '.ref' => 'directives' } ] } ] }, 'field' => { '.all' => [ { '+max' => 1, '.ref' => 'alias' }, { '.ref' => 'name' }, { '-skip' => 1, '.ref' => '_' }, { '+max' => 1, '.ref' => 'arguments' }, { '-skip' => 1, '.ref' => '_' }, { '+max' => 1, '.ref' => 'directives' }, { '+max' => 1, '.ref' => 'selectionSet' } ] }, 'fieldDefinition' => { '.all' => [ { '+max' => 1, '.ref' => 'description' }, { '.ref' => 'name' }, { '-skip' => 1, '.ref' => '_' }, { '+max' => 1, '.ref' => 'argumentsDefinition' }, { '-skip' => 1, '.rgx' => qr/\G(?:\s|\x{FEFF}|,|[\ \t]*\#[\ \t]*[^\r\n]*(?:\r?\n|\r!NL|\z))*:(?:\s|\x{FEFF}|,|[\ \t]*\#[\ \t]*[^\r\n]*(?:\r?\n|\r!NL|\z))*/ }, { '.ref' => 'typedef' }, { '-skip' => 1, '.ref' => '_' }, { '+max' => 1, '.ref' => 'directives' } ] }, 'float' => { '.rgx' => qr/\G(\-?(?:0|[1-9][0-9]*)(?:(?:\.[0-9]+)(?:[eE][\-\+]?[0-9]+)|(?:\.[0-9]+)|(?:[eE][\-\+]?[0-9]+)))/ }, 'fragment' => { '.all' => [ { '-skip' => 1, '.rgx' => qr/\G(?:\s|\x{FEFF}|,|[\ \t]*\#[\ \t]*[^\r\n]*(?:\r?\n|\r!NL|\z))*fragment(?:\s|\x{FEFF}|,|[\ \t]*\#[\ \t]*[^\r\n]*(?:\r?\n|\r!NL|\z))*/ }, { '.ref' => 'fragmentName' }, { '-skip' => 1, '.ref' => '_' }, { '.any' => [ { '.ref' => 'typeCondition' }, { '.err' => 'Expected "on"' } ] }, { '-skip' => 1, '.ref' => '_' }, { '+max' => 1, '.ref' => 'directives' }, { '.ref' => 'selectionSet' } ] }, 'fragmentName' => { '.any' => [ { '.all' => [ { '.rgx' => qr/\Gon/ }, { '.err' => 'Unexpected Name "on"' } ] }, { '.ref' => 'name' } ] }, 'fragment_spread' => { '.all' => [ { '-skip' => 1, '.ref' => 'spread' }, { '.ref' => 'fragmentName' }, { '-skip' => 1, '.ref' => '_' }, { '+max' => 1, '.ref' => 'directives' } ] }, 'graphql' => { '+min' => 1, '.ref' => 'definition' }, 'implementsInterfaces' => { '.all' => [ { '-skip' => 1, '.rgx' => qr/\Gimplements(?:\s|\x{FEFF}|,|[\ \t]*\#[\ \t]*[^\r\n]*(?:\r?\n|\r!NL|\z))*/ }, { '+max' => 1, '-skip' => 1, '.rgx' => qr/\G(?:\s|\x{FEFF}|,|[\ \t]*\#[\ \t]*[^\r\n]*(?:\r?\n|\r!NL|\z))*&(?:\s|\x{FEFF}|,|[\ \t]*\#[\ \t]*[^\r\n]*(?:\r?\n|\r!NL|\z))*/ }, { '.all' => [ { '.ref' => 'namedType' }, { '+min' => 0, '-flat' => 1, '.all' => [ { '-skip' => 1, '.rgx' => qr/\G(?:\s|\x{FEFF}|,|[\ \t]*\#[\ \t]*[^\r\n]*(?:\r?\n|\r!NL|\z))*&(?:\s|\x{FEFF}|,|[\ \t]*\#[\ \t]*[^\r\n]*(?:\r?\n|\r!NL|\z))*/ }, { '.ref' => 'namedType' } ] } ] } ] }, 'inline_fragment' => { '.all' => [ { '-skip' => 1, '.ref' => 'spread' }, { '+max' => 1, '.ref' => 'typeCondition' }, { '-skip' => 1, '.ref' => '_' }, { '+max' => 1, '.ref' => 'directives' }, { '.ref' => 'selectionSet' } ] }, 'input' => { '.all' => [ { '-skip' => 1, '.rgx' => qr/\Ginput(?:\s|\x{FEFF}|,|[\ \t]*\#[\ \t]*[^\r\n]*(?:\r?\n|\r!NL|\z))*/ }, { '.ref' => 'name' }, { '-skip' => 1, '.ref' => '_' }, { '+max' => 1, '.ref' => 'directives' }, { '+max' => 1, '.all' => [ { '-skip' => 1, '.rgx' => qr/\G(?:\s|\x{FEFF}|,|[\ \t]*\#[\ \t]*[^\r\n]*(?:\r?\n|\r!NL|\z))*\{/ }, { '+min' => 1, '.ref' => 'inputValueDefinition' }, { '-skip' => 1, '.rgx' => qr/\G(?:\s|\x{FEFF}|,|[\ \t]*\#[\ \t]*[^\r\n]*(?:\r?\n|\r!NL|\z))*\}/ } ] } ] }, 'inputValueDefinition' => { '.all' => [ { '+max' => 1, '.ref' => 'description' }, { '.ref' => 'name' }, { '-skip' => 1, '.rgx' => qr/\G(?:\s|\x{FEFF}|,|[\ \t]*\#[\ \t]*[^\r\n]*(?:\r?\n|\r!NL|\z))*:(?:\s|\x{FEFF}|,|[\ \t]*\#[\ \t]*[^\r\n]*(?:\r?\n|\r!NL|\z))*/ }, { '.ref' => 'typedef' }, { '-skip' => 1, '.ref' => '_' }, { '+max' => 1, '.ref' => 'defaultValue' }, { '-skip' => 1, '.ref' => '_' }, { '+max' => 1, '.ref' => 'directives' }, { '-skip' => 1, '.ref' => '_' } ] }, 'int' => { '.rgx' => qr/\G(\-?(?:0|[1-9][0-9]*))/ }, 'interface' => { '.all' => [ { '-skip' => 1, '.rgx' => qr/\Ginterface(?:\s|\x{FEFF}|,|[\ \t]*\#[\ \t]*[^\r\n]*(?:\r?\n|\r!NL|\z))*/ }, { '.ref' => 'name' }, { '-skip' => 1, '.ref' => '_' }, { '+max' => 1, '.ref' => 'directives' }, { '+max' => 1, '.all' => [ { '-skip' => 1, '.rgx' => qr/\G(?:\s|\x{FEFF}|,|[\ \t]*\#[\ \t]*[^\r\n]*(?:\r?\n|\r!NL|\z))*\{/ }, { '+min' => 1, '.ref' => 'fieldDefinition' }, { '-skip' => 1, '.rgx' => qr/\G(?:\s|\x{FEFF}|,|[\ \t]*\#[\ \t]*[^\r\n]*(?:\r?\n|\r!NL|\z))*\}/ } ] } ] }, 'listType' => { '.all' => [ { '.ref' => 'LSQUARE' }, { '.ref' => 'typedef' }, { '.ref' => 'RSQUARE' } ] }, 'listValue' => { '.all' => [ { '-skip' => 1, '.rgx' => qr/\G(?:\s|\x{FEFF}|,|[\ \t]*\#[\ \t]*[^\r\n]*(?:\r?\n|\r!NL|\z))*\[(?:\s|\x{FEFF}|,|[\ \t]*\#[\ \t]*[^\r\n]*(?:\r?\n|\r!NL|\z))*/ }, { '+min' => 0, '.ref' => 'value' }, { '-skip' => 1, '.rgx' => qr/\G(?:\s|\x{FEFF}|,|[\ \t]*\#[\ \t]*[^\r\n]*(?:\r?\n|\r!NL|\z))*\](?:\s|\x{FEFF}|,|[\ \t]*\#[\ \t]*[^\r\n]*(?:\r?\n|\r!NL|\z))*/ } ] }, 'listValue_const' => { '.all' => [ { '-skip' => 1, '.rgx' => qr/\G(?:\s|\x{FEFF}|,|[\ \t]*\#[\ \t]*[^\r\n]*(?:\r?\n|\r!NL|\z))*\[(?:\s|\x{FEFF}|,|[\ \t]*\#[\ \t]*[^\r\n]*(?:\r?\n|\r!NL|\z))*/ }, { '+min' => 0, '.ref' => 'value_const' }, { '-skip' => 1, '.rgx' => qr/\G(?:\s|\x{FEFF}|,|[\ \t]*\#[\ \t]*[^\r\n]*(?:\r?\n|\r!NL|\z))*\](?:\s|\x{FEFF}|,|[\ \t]*\#[\ \t]*[^\r\n]*(?:\r?\n|\r!NL|\z))*/ } ] }, 'name' => { '.rgx' => qr/\G([_a-zA-Z][0-9A-Za-z_]*)/ }, 'namedType' => { '.all' => [ { '.ref' => 'name' }, { '-skip' => 1, '.ref' => '_' } ] }, 'nonNullType' => { '.any' => [ { '.all' => [ { '.ref' => 'namedType' }, { '.rgx' => qr/\G(?:\s|\x{FEFF}|,|[\ \t]*\#[\ \t]*[^\r\n]*(?:\r?\n|\r!NL|\z))*!/ } ] }, { '.all' => [ { '.ref' => 'listType' }, { '.rgx' => qr/\G(?:\s|\x{FEFF}|,|[\ \t]*\#[\ \t]*[^\r\n]*(?:\r?\n|\r!NL|\z))*!/ } ] } ] }, 'null' => { '.rgx' => qr/\G(null)/ }, 'objectField' => { '.all' => [ { '.ref' => 'name' }, { '-skip' => 1, '.rgx' => qr/\G(?:\s|\x{FEFF}|,|[\ \t]*\#[\ \t]*[^\r\n]*(?:\r?\n|\r!NL|\z))*:(?:\s|\x{FEFF}|,|[\ \t]*\#[\ \t]*[^\r\n]*(?:\r?\n|\r!NL|\z))*/ }, { '.ref' => 'value' }, { '-skip' => 1, '.ref' => '_' } ] }, 'objectField_const' => { '.all' => [ { '.ref' => 'name' }, { '-skip' => 1, '.rgx' => qr/\G(?:\s|\x{FEFF}|,|[\ \t]*\#[\ \t]*[^\r\n]*(?:\r?\n|\r!NL|\z))*:(?:\s|\x{FEFF}|,|[\ \t]*\#[\ \t]*[^\r\n]*(?:\r?\n|\r!NL|\z))*/ }, { '.ref' => 'value_const' } ] }, 'objectValue' => { '.all' => [ { '-skip' => 1, '.rgx' => qr/\G(?:\s|\x{FEFF}|,|[\ \t]*\#[\ \t]*[^\r\n]*(?:\r?\n|\r!NL|\z))*\{(?:\s|\x{FEFF}|,|[\ \t]*\#[\ \t]*[^\r\n]*(?:\r?\n|\r!NL|\z))*/ }, { '.any' => [ { '+min' => 1, '.ref' => 'objectField' }, { '.err' => 'Expected name' } ] }, { '-skip' => 1, '.rgx' => qr/\G(?:\s|\x{FEFF}|,|[\ \t]*\#[\ \t]*[^\r\n]*(?:\r?\n|\r!NL|\z))*\}(?:\s|\x{FEFF}|,|[\ \t]*\#[\ \t]*[^\r\n]*(?:\r?\n|\r!NL|\z))*/ } ] }, 'objectValue_const' => { '.all' => [ { '-skip' => 1, '.rgx' => qr/\G(?:\s|\x{FEFF}|,|[\ \t]*\#[\ \t]*[^\r\n]*(?:\r?\n|\r!NL|\z))*\{(?:\s|\x{FEFF}|,|[\ \t]*\#[\ \t]*[^\r\n]*(?:\r?\n|\r!NL|\z))*/ }, { '.any' => [ { '+min' => 1, '.ref' => 'objectField_const' }, { '.err' => 'Expected name or constant' } ] }, { '-skip' => 1, '.rgx' => qr/\G(?:\s|\x{FEFF}|,|[\ \t]*\#[\ \t]*[^\r\n]*(?:\r?\n|\r!NL|\z))*\}(?:\s|\x{FEFF}|,|[\ \t]*\#[\ \t]*[^\r\n]*(?:\r?\n|\r!NL|\z))*/ } ] }, 'operationDefinition' => { '.any' => [ { '.ref' => 'selectionSet' }, { '.all' => [ { '.ref' => 'operationType' }, { '-skip' => 1, '.ref' => '_' }, { '+max' => 1, '.ref' => 'name' }, { '-skip' => 1, '.ref' => '_' }, { '+max' => 1, '.ref' => 'variableDefinitions' }, { '-skip' => 1, '.ref' => '_' }, { '+max' => 1, '.ref' => 'directives' }, { '.ref' => 'selectionSet' } ] } ] }, 'operationType' => { '.rgx' => qr/\G(query|mutation|subscription)/ }, 'operationTypeDefinition' => { '.all' => [ { '.ref' => 'operationType' }, { '-skip' => 1, '.rgx' => qr/\G(?:\s|\x{FEFF}|,|[\ \t]*\#[\ \t]*[^\r\n]*(?:\r?\n|\r!NL|\z))*:(?:\s|\x{FEFF}|,|[\ \t]*\#[\ \t]*[^\r\n]*(?:\r?\n|\r!NL|\z))*/ }, { '.ref' => 'namedType' }, { '-skip' => 1, '.ref' => '_' } ] }, 'scalar' => { '.all' => [ { '-skip' => 1, '.rgx' => qr/\Gscalar(?:\s|\x{FEFF}|,|[\ \t]*\#[\ \t]*[^\r\n]*(?:\r?\n|\r!NL|\z))*/ }, { '.ref' => 'name' }, { '-skip' => 1, '.ref' => '_' }, { '+max' => 1, '.ref' => 'directives' } ] }, 'schema' => { '.all' => [ { '-skip' => 1, '.rgx' => qr/\Gschema(?:\s|\x{FEFF}|,|[\ \t]*\#[\ \t]*[^\r\n]*(?:\r?\n|\r!NL|\z))*/ }, { '+max' => 1, '.ref' => 'directives' }, { '+max' => 1, '.all' => [ { '-skip' => 1, '.rgx' => qr/\G(?:\s|\x{FEFF}|,|[\ \t]*\#[\ \t]*[^\r\n]*(?:\r?\n|\r!NL|\z))*\{(?:\s|\x{FEFF}|,|[\ \t]*\#[\ \t]*[^\r\n]*(?:\r?\n|\r!NL|\z))*/ }, { '+min' => 1, '.ref' => 'operationTypeDefinition' }, { '-skip' => 1, '.rgx' => qr/\G(?:\s|\x{FEFF}|,|[\ \t]*\#[\ \t]*[^\r\n]*(?:\r?\n|\r!NL|\z))*\}/ } ] } ] }, 'selection' => { '.all' => [ { '.any' => [ { '.ref' => 'field' }, { '.ref' => 'inline_fragment' }, { '.ref' => 'fragment_spread' } ] }, { '-skip' => 1, '.ref' => '_' } ] }, 'selectionSet' => { '.all' => [ { '-skip' => 1, '.rgx' => qr/\G(?:\s|\x{FEFF}|,|[\ \t]*\#[\ \t]*[^\r\n]*(?:\r?\n|\r!NL|\z))*\{(?:\s|\x{FEFF}|,|[\ \t]*\#[\ \t]*[^\r\n]*(?:\r?\n|\r!NL|\z))*/ }, { '.any' => [ { '+min' => 1, '.ref' => 'selection' }, { '.err' => 'Expected name' } ] }, { '-skip' => 1, '.rgx' => qr/\G(?:\s|\x{FEFF}|,|[\ \t]*\#[\ \t]*[^\r\n]*(?:\r?\n|\r!NL|\z))*\}(?:\s|\x{FEFF}|,|[\ \t]*\#[\ \t]*[^\r\n]*(?:\r?\n|\r!NL|\z))*/ } ] }, 'spread' => { '.all' => [ { '.rgx' => qr/\G\.{3}/ }, { '-skip' => 1, '.ref' => '_' } ] }, 'string' => { '.any' => [ { '.ref' => 'blockStringValue' }, { '.ref' => 'stringValue' } ] }, 'stringValue' => { '.rgx' => qr/\G"((?:\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})|[^"\x00-\x1f\\])*)"/ }, 'type' => { '.all' => [ { '-skip' => 1, '.rgx' => qr/\Gtype(?:\s|\x{FEFF}|,|[\ \t]*\#[\ \t]*[^\r\n]*(?:\r?\n|\r!NL|\z))*/ }, { '.ref' => 'name' }, { '-skip' => 1, '.ref' => '_' }, { '+max' => 1, '.ref' => 'implementsInterfaces' }, { '-skip' => 1, '.ref' => '_' }, { '+max' => 1, '.ref' => 'directives' }, { '+max' => 1, '.all' => [ { '-skip' => 1, '.rgx' => qr/\G(?:\s|\x{FEFF}|,|[\ \t]*\#[\ \t]*[^\r\n]*(?:\r?\n|\r!NL|\z))*\{/ }, { '+min' => 1, '.ref' => 'fieldDefinition' }, { '-skip' => 1, '.rgx' => qr/\G(?:\s|\x{FEFF}|,|[\ \t]*\#[\ \t]*[^\r\n]*(?:\r?\n|\r!NL|\z))*\}/ } ] } ] }, 'typeCondition' => { '.all' => [ { '-skip' => 1, '.rgx' => qr/\Gon(?:\s|\x{FEFF}|,|[\ \t]*\#[\ \t]*[^\r\n]*(?:\r?\n|\r!NL|\z))*/ }, { '.ref' => 'namedType' } ] }, 'typeDefinition' => { '.any' => [ { '.ref' => 'scalar' }, { '.ref' => 'type' }, { '.ref' => 'interface' }, { '.ref' => 'union' }, { '.ref' => 'enumTypeDefinition' }, { '.ref' => 'input' } ] }, 'typeExtensionDefinition' => { '.all' => [ { '-skip' => 1, '.rgx' => qr/\Gextend(?:\s|\x{FEFF}|,|[\ \t]*\#[\ \t]*[^\r\n]*(?:\r?\n|\r!NL|\z))*/ }, { '.any' => [ { '.ref' => 'schema' }, { '.ref' => 'typeDefinition' } ] } ] }, 'typeSystemDefinition' => { '.all' => [ { '+max' => 1, '.ref' => 'description' }, { '.any' => [ { '.ref' => 'schema' }, { '.ref' => 'typeDefinition' }, { '.ref' => 'typeExtensionDefinition' }, { '.ref' => 'directive' } ] } ] }, 'typedef' => { '.any' => [ { '.ref' => 'nonNullType' }, { '.ref' => 'namedType' }, { '.ref' => 'listType' } ] }, 'union' => { '.all' => [ { '-skip' => 1, '.rgx' => qr/\Gunion(?:\s|\x{FEFF}|,|[\ \t]*\#[\ \t]*[^\r\n]*(?:\r?\n|\r!NL|\z))*/ }, { '.ref' => 'name' }, { '-skip' => 1, '.ref' => '_' }, { '+max' => 1, '.ref' => 'directives' }, { '+max' => 1, '.all' => [ { '-skip' => 1, '.rgx' => qr/\G(?:\s|\x{FEFF}|,|[\ \t]*\#[\ \t]*[^\r\n]*(?:\r?\n|\r!NL|\z))*=(?:\s|\x{FEFF}|,|[\ \t]*\#[\ \t]*[^\r\n]*(?:\r?\n|\r!NL|\z))*/ }, { '.ref' => 'unionMembers' } ] } ] }, 'unionMembers' => { '.all' => [ { '+max' => 1, '-skip' => 1, '.rgx' => qr/\G(?:\s|\x{FEFF}|,|[\ \t]*\#[\ \t]*[^\r\n]*(?:\r?\n|\r!NL|\z))*\|(?:\s|\x{FEFF}|,|[\ \t]*\#[\ \t]*[^\r\n]*(?:\r?\n|\r!NL|\z))*/ }, { '.all' => [ { '.ref' => 'namedType' }, { '+min' => 0, '-flat' => 1, '.all' => [ { '-skip' => 1, '.rgx' => qr/\G(?:\s|\x{FEFF}|,|[\ \t]*\#[\ \t]*[^\r\n]*(?:\r?\n|\r!NL|\z))*\|(?:\s|\x{FEFF}|,|[\ \t]*\#[\ \t]*[^\r\n]*(?:\r?\n|\r!NL|\z))*/ }, { '.ref' => 'namedType' } ] } ] } ] }, 'value' => { '.all' => [ { '.any' => [ { '.ref' => 'variable' }, { '.ref' => 'float' }, { '.ref' => 'int' }, { '.ref' => 'string' }, { '.ref' => 'boolean' }, { '.ref' => 'null' }, { '.ref' => 'enumValue' }, { '.ref' => 'listValue' }, { '.ref' => 'objectValue' } ] }, { '-skip' => 1, '.ref' => '_' } ] }, 'value_const' => { '.all' => [ { '.any' => [ { '.ref' => 'float' }, { '.ref' => 'int' }, { '.ref' => 'string' }, { '.ref' => 'boolean' }, { '.ref' => 'null' }, { '.ref' => 'enumValue' }, { '.ref' => 'listValue_const' }, { '.ref' => 'objectValue_const' } ] }, { '-skip' => 1, '.ref' => '_' } ] }, 'variable' => { '.all' => [ { '-skip' => 1, '.rgx' => qr/\G(?:\s|\x{FEFF}|,|[\ \t]*\#[\ \t]*[^\r\n]*(?:\r?\n|\r!NL|\z))*\$/ }, { '.ref' => 'name' } ] }, 'variableDefinition' => { '.all' => [ { '.ref' => 'variable' }, { '-skip' => 1, '.rgx' => qr/\G(?:\s|\x{FEFF}|,|[\ \t]*\#[\ \t]*[^\r\n]*(?:\r?\n|\r!NL|\z))*:(?:\s|\x{FEFF}|,|[\ \t]*\#[\ \t]*[^\r\n]*(?:\r?\n|\r!NL|\z))*/ }, { '.ref' => 'typedef' }, { '+max' => 1, '.ref' => 'defaultValue' }, { '-skip' => 1, '.ref' => '_' } ] }, 'variableDefinitions' => { '.all' => [ { '-skip' => 1, '.rgx' => qr/\G(?:\s|\x{FEFF}|,|[\ \t]*\#[\ \t]*[^\r\n]*(?:\r?\n|\r!NL|\z))*\((?:\s|\x{FEFF}|,|[\ \t]*\#[\ \t]*[^\r\n]*(?:\r?\n|\r!NL|\z))*/ }, { '.any' => [ { '+min' => 1, '.ref' => 'variableDefinition' }, { '.err' => 'Expected $argument: Type' } ] }, { '-skip' => 1, '.rgx' => qr/\G(?:\s|\x{FEFF}|,|[\ \t]*\#[\ \t]*[^\r\n]*(?:\r?\n|\r!NL|\z))*\)(?:\s|\x{FEFF}|,|[\ \t]*\#[\ \t]*[^\r\n]*(?:\r?\n|\r!NL|\z))*/ } ] }, 'ws2' => { '.rgx' => qr/\G(?:\s|\x{FEFF}|,|[\ \t]*\#[\ \t]*[^\r\n]*(?:\r?\n|\r!NL|\z))+/ } } } 1; GraphQL-0.53/lib/GraphQL/Language/Parser.pm0000644000175000017500000000412614070317276020260 0ustar osboxesosboxespackage GraphQL::Language::Parser; use 5.014; use strict; use warnings; use base qw(Pegex::Parser); use Exporter 'import'; use Types::Standard -all; use GraphQL::MaybeTypeCheck; use GraphQL::Language::Grammar; use GraphQL::Language::Receiver; use GraphQL::Error; our $VERSION = '0.02'; our @EXPORT_OK = qw( parse ); =head1 NAME GraphQL::Language::Parser - GraphQL Pegex parser =head1 SYNOPSIS use GraphQL::Language::Parser qw(parse); my $parsed = parse( $source ); =head1 DESCRIPTION Provides both an outside-accessible point of entry into the GraphQL parser (see above), and a subclass of L to parse a document into an AST usable by GraphQL. =head1 METHODS =head2 parse parse($source, $noLocation); B that unlike in C this is a function, not an instance method. This achieves hiding of Pegex implementation details. =cut my $GRAMMAR = GraphQL::Language::Grammar->new; # singleton fun parse( Str $source, Bool $noLocation = undef, ) :ReturnType(ArrayRef[HashRef]) { my $parser = __PACKAGE__->SUPER::new( grammar => $GRAMMAR, receiver => GraphQL::Language::Receiver->new, ); my $input = Pegex::Input->new(string => $source); scalar $parser->SUPER::parse($input); } =head2 format_error Override of parent method. Returns a L. =cut sub format_error :ReturnType(InstanceOf['GraphQL::Error']) { my ($self, $msg) = @_; my $buffer = $self->{buffer}; my $position = $self->{farthest}; my $real_pos = $self->{position}; my ($line, $column) = @{$self->line_column($position)}; my $pretext = substr( $$buffer, $position < 50 ? 0 : $position - 50, $position < 50 ? $position : 50 ); my $context = substr($$buffer, $position, 50); $pretext =~ s/.*\n//gs; $context =~ s/\n/\\n/g; return GraphQL::Error->new( locations => [ { line => $line, column => $column } ], message => <new->allow_nonref->canonical; my @KINDHASH = qw( scalar union field inline_fragment fragment_spread fragment directive ); my %KINDHASH21 = map { ($_ => 1) } @KINDHASH; my @KINDFIELDS = qw( type input interface ); my %KINDFIELDS21 = map { ($_ => 1) } @KINDFIELDS; =head1 NAME GraphQL::Language::Receiver - GraphQL Pegex AST constructor =head1 VERSION Version 0.02 =cut our $VERSION = '0.02'; =head1 SYNOPSIS # this class used internally by: use GraphQL::Language::Parser qw(parse); my $parsed = parse($source); =head1 DESCRIPTION Subclass of L to turn Pegex parsing events into data usable by GraphQL. =cut method gotrule (Any $param = undef) { return unless defined $param; if ($KINDHASH21{$self->{parser}{rule}}) { return {kind => $self->{parser}{rule}, %{$self->_locate_hash(_merge_hash($param))}}; } elsif ($KINDFIELDS21{$self->{parser}{rule}}) { return {kind => $self->{parser}{rule}, %{$self->_locate_hash(_merge_hash($param, 'fields'))}}; } return {$self->{parser}{rule} => $param}; } method _locate_hash(HashRef $hash) { my ($line, $column) = @{$self->{parser}->line_column($self->{parser}{farthest})}; +{ %$hash, location => { line => $line, column => $column } }; } fun _merge_hash (Any $param = undef, Any $arraykey = undef) { my %def = map %$_, grep ref eq 'HASH', @$param; if ($arraykey) { my @arrays = grep ref eq 'ARRAY', @$param; Carp::confess "More than one array found\n" if @arrays > 1; Carp::confess "No arrays found but \$arraykey given\n" if !@arrays; my %fields = map %$_, @{$arrays[0]}; $def{$arraykey} = \%fields; } \%def; } fun _unescape (Str $str) { # https://facebook.github.io/graphql/June2018/#EscapedCharacter $str =~ s|\\(["\\/bfnrt])|"qq!\\$1!"|gee; return $str; } fun _blockstring_value (Str $str) { # https://facebook.github.io/graphql/June2018/#BlockStringValue() my @lines = split(/(?:\n|\r(?!\r)|\r\n)/s, $str); if (1 < @lines) { my $common_indent; for my $line (@lines[1..$#lines]) { my $length = length($line); my $indent = length(($line =~ /^([\t ]*)/)[0] || ''); if ($indent < $length && (!defined($common_indent) || $indent < $common_indent)) { $common_indent = $indent; } } if (defined $common_indent) { for my $line (@lines[1..$#lines]) { $line =~ s/^[\t ]{$common_indent}//; } } } my ($start, $end); for ($start = 0; $start < @lines && $lines[$start] =~ /^[\t ]*$/; ++$start) {} for ($end = $#lines; $end >= 0 && $lines[$end] =~ /^[\t ]*$/; --$end) {} @lines = @lines[$start..$end]; my $formatted = join("\n", @lines); $formatted =~ s/\\"""/"""/g; return $formatted; } method got_arguments (Any $param = undef) { return unless defined $param; my %args = map { ($_->[0]{name} => $_->[1]) } @$param; return {$self->{parser}{rule} => \%args}; } method got_argument (Any $param = undef) { return unless defined $param; $param; } method got_objectField (Any $param = undef) { return unless defined $param; return {$param->[0]{name} => $param->[1]}; } method got_objectValue (Any $param = undef) { return unless defined $param; _merge_hash($param); } method got_objectField_const (Any $param = undef) { unshift @_, $self; goto &got_objectField; } method got_objectValue_const (Any $param = undef) { unshift @_, $self; goto &got_objectValue; } method got_listValue (Any $param = undef) { return unless defined $param; return $param; } method got_listValue_const (Any $param = undef) { unshift @_, $self; goto &got_listValue; } method got_directiveactual (Any $param = undef) { return unless defined $param; _merge_hash($param); } method got_inputValueDefinition (Any $param = undef) { return unless defined $param; my $def = _merge_hash($param); my $name = delete $def->{name}; return { $name => $def }; } method got_directiveLocations (Any $param = undef) { return unless defined $param; return {locations => [ map $_->{name}, @$param ]}; } method got_namedType (Any $param = undef) { return unless defined $param; return $param->{name}; } method got_enumValueDefinition (Any $param = undef) { return unless defined $param; my @copy = @$param; my $rest = pop @copy; my $value = pop @copy; my $description = $copy[0] // {}; $rest = ref $rest eq 'HASH' ? [ $rest ] : $rest; my %def = (%$description, value => $value, map %$_, @$rest); return \%def; } method got_defaultValue (Any $param = undef) { # the value can be undef return { default_value => $param }; } method got_implementsInterfaces (Any $param = undef) { return unless defined $param; return { interfaces => $param }; } method got_argumentsDefinition (Any $param = undef) { return unless defined $param; return { args => _merge_hash($param) }; } method got_fieldDefinition (Any $param = undef) { return unless defined $param; my $def = _merge_hash($param); my $name = delete $def->{name}; return { $name => $def }; } method got_typeExtensionDefinition (Any $param = undef) { return unless defined $param; return {kind => 'extend', %{$self->_locate_hash($param)}}; } method got_enumTypeDefinition (Any $param = undef) { return unless defined $param; my $def = _merge_hash($param); my %values; map { my $name = ${${delete $_->{value}}}; $values{$name} = $_; } @{(grep ref eq 'ARRAY', @$param)[0]}; $def->{values} = \%values; return {kind => 'enum', %{$self->_locate_hash($def)}}; } method got_unionMembers (Any $param = undef) { return unless defined $param; return { types => $param }; } method got_boolean (Any $param = undef) { return unless defined $param; return $param eq 'true' ? JSON->true : JSON->false; } method got_null (Any $param = undef) { return unless defined $param; return undef; } method got_string (Any $param = undef) { return unless defined $param; return $param; } method got_stringValue (Any $param = undef) { return unless defined $param; return _unescape($param); } method got_blockStringValue (Any $param = undef) { return unless defined $param; return _blockstring_value($param); } method got_int (Any $param = undef) { $param+0; } method got_float (Any $param = undef) { $param+0; } method got_enumValue (Any $param = undef) { return unless defined $param; my $varname = $param->{name}; return \\$varname; } # not returning empty list if undef method got_value_const (Any $param = undef) { return $param; } method got_value (Any $param = undef) { unshift @_, $self; goto &got_value_const; } method got_variableDefinitions (Any $param = undef) { return unless defined $param; my %def; map { my $name = ${ shift @$_ }; $def{$name} = { map %$_, @$_ }; # merge } @$param; return {variables => \%def}; } method got_variableDefinition (Any $param = undef) { return unless defined $param; return $param; } method got_selection (Any $param = undef) { unshift @_, $self; goto &got_value_const; } method got_typedef (Any $param = undef) { return unless defined $param; $param = $param->{name} if ref($param) eq 'HASH'; return {type => $param}; } method got_alias (Any $param = undef) { return unless defined $param; return {$self->{parser}{rule} => $param->{name}}; } method got_typeCondition (Any $param = undef) { return unless defined $param; return {on => $param}; } method got_fragmentName (Any $param = undef) { return unless defined $param; return $param; } method got_selectionSet (Any $param = undef) { return unless defined $param; return {selections => $param}; } method got_operationDefinition (Any $param = undef) { return unless defined $param; $param = [ $param ] unless ref $param eq 'ARRAY'; # bare selectionSet return {kind => 'operation', %{$self->_locate_hash(_merge_hash($param))}}; } method got_directives (Any $param = undef) { return unless defined $param; return {$self->{parser}{rule} => $param}; } method got_graphql (Any $param = undef) { return unless defined $param; return $param; } method got_definition (Any $param = undef) { return unless defined $param; return $param; } method got_operationTypeDefinition (Any $param = undef) { return unless defined $param; return { map { ref($_) ? values %$_ : $_ } @$param }; } method got_comment (Any $param = undef) { return unless defined $param; return $param; } method got_description (Any $param = undef) { return unless defined $param; my $string = ref($param) eq 'ARRAY' ? join("\n", @$param) : $param; return $string ? {$self->{parser}{rule} => $string} : {}; } method got_schema (Any $param = undef) { return unless defined $param; my $directives = {}; if (ref $param->[1] eq 'ARRAY') { # got directives $directives = shift @$param; } my %type2count; $type2count{(keys %$_)[0]}++ for @{$param->[0]}; $type2count{$_} > 1 and die "Must provide only one $_ type in schema.\n" for keys %type2count; return {kind => $self->{parser}{rule}, %{$self->_locate_hash(_merge_hash($param->[0]))}, %$directives}; } method got_typeSystemDefinition (Any $param = undef) { return unless defined $param; my @copy = @$param; my $node = pop @copy; my $description = $copy[0] // {}; +{ %$node, %$description }; } method got_typeDefinition (Any $param = undef) { return unless defined $param; return $param; } method got_variable (Any $param = undef) { return unless defined $param; my $varname = $param->{name}; return \$varname; } method got_nonNullType (Any $param = undef) { return unless defined $param; $param = $param->[0]; # zap first useless layer $param = { type => $param } if ref $param ne 'HASH'; return [ 'non_null', $param ]; } method got_listType (Any $param = undef) { return unless defined $param; $param = $param->[0]; # zap first useless layer $param = { type => $param } if ref $param ne 'HASH'; return [ 'list', $param ]; } 1; GraphQL-0.53/lib/GraphQL/Introspection.pm0000644000175000017500000004220013461415731020132 0ustar osboxesosboxespackage GraphQL::Introspection; use 5.014; use strict; use warnings; use Exporter 'import'; use GraphQL::Type::Object; use GraphQL::Type::Enum; use GraphQL::Type::Scalar qw($String $Boolean); use GraphQL::Debug qw(_debug); use JSON::MaybeXS; =head1 NAME GraphQL::Introspection - Perl implementation of GraphQL =cut our $VERSION = '0.02'; our @EXPORT_OK = qw( $QUERY $TYPE_KIND_META_TYPE $DIRECTIVE_LOCATION_META_TYPE $ENUM_VALUE_META_TYPE $INPUT_VALUE_META_TYPE $FIELD_META_TYPE $DIRECTIVE_META_TYPE $TYPE_META_TYPE $SCHEMA_META_TYPE $SCHEMA_META_FIELD_DEF $TYPE_META_FIELD_DEF $TYPE_NAME_META_FIELD_DEF ); use constant DEBUG => $ENV{GRAPHQL_DEBUG}; my $JSON_noutf8 = JSON::MaybeXS->new->utf8(0)->allow_nonref; =head1 SYNOPSIS use GraphQL::Introspection qw($QUERY); my $schema_data = execute($schema, $QUERY); =head1 DESCRIPTION Provides infrastructure implementing GraphQL's introspection. =head1 EXPORT =head2 $QUERY The GraphQL query to introspect the schema. =cut our $QUERY = ' query IntrospectionQuery { __schema { queryType { name } mutationType { name } subscriptionType { name } types { ...FullType } directives { name description locations args { ...InputValue } } } } fragment FullType on __Type { kind name description fields(includeDeprecated: true) { name description args { ...InputValue } type { ...TypeRef } isDeprecated deprecationReason } inputFields { ...InputValue } interfaces { ...TypeRef } enumValues(includeDeprecated: true) { name description isDeprecated deprecationReason } possibleTypes { ...TypeRef } } fragment InputValue on __InputValue { name description type { ...TypeRef } defaultValue } fragment TypeRef on __Type { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name } } } } } } } } '; =head2 $TYPE_KIND_META_TYPE The enum type describing kinds of type. The second-most-meta type here, after C<$TYPE_META_TYPE> itself. =cut # TODO sort out is_introspection our $TYPE_KIND_META_TYPE = GraphQL::Type::Enum->new( name => '__TypeKind', is_introspection => 1, description => 'An enum describing what kind of type a given `__Type` is.', values => { SCALAR => { description => 'Indicates this type is a scalar.' }, OBJECT => { description => 'Indicates this type is an object. ' . '`fields` and `interfaces` are valid fields.' }, INTERFACE => { description => 'Indicates this type is an interface. ' . '`fields` and `possibleTypes` are valid fields.' }, UNION => { description => 'Indicates this type is a union. ' . '`possibleTypes` is a valid field.' }, ENUM => { description => 'Indicates this type is an enum. ' . '`enumValues` is a valid field.' }, INPUT_OBJECT => { description => 'Indicates this type is an input object. ' . '`inputFields` is a valid field.' }, LIST => { description => 'Indicates this type is a list. ' . '`ofType` is a valid field.' }, NON_NULL => { description => 'Indicates this type is a non-null. ' . '`ofType` is a valid field.' }, }, ); =head2 $DIRECTIVE_LOCATION_META_TYPE The enum type describing directive locations. =cut # TODO sort out is_introspection our $DIRECTIVE_LOCATION_META_TYPE = GraphQL::Type::Enum->new( name => '__DirectiveLocation', is_introspection => 1, description => 'A Directive can be adjacent to many parts of the GraphQL language, a ' . '__DirectiveLocation describes one such possible adjacencies.', values => { QUERY => { description => 'Location adjacent to a query operation.' }, MUTATION => { description => 'Location adjacent to a mutation operation.' }, SUBSCRIPTION => { description => 'Location adjacent to a subscription operation.' }, FIELD => { description => 'Location adjacent to a field.' }, FRAGMENT_DEFINITION => { description => 'Location adjacent to a fragment definition.' }, FRAGMENT_SPREAD => { description => 'Location adjacent to a fragment spread.' }, INLINE_FRAGMENT => { description => 'Location adjacent to an inline fragment.' }, SCHEMA => { description => 'Location adjacent to a schema definition.' }, SCALAR => { description => 'Location adjacent to a scalar definition.' }, OBJECT => { description => 'Location adjacent to an object type definition.' }, FIELD_DEFINITION => { description => 'Location adjacent to a field definition.' }, ARGUMENT_DEFINITION => { description => 'Location adjacent to an argument definition.' }, INTERFACE => { description => 'Location adjacent to an interface definition.' }, UNION => { description => 'Location adjacent to a union definition.' }, ENUM => { description => 'Location adjacent to an enum definition.' }, ENUM_VALUE => { description => 'Location adjacent to an enum value definition.' }, INPUT_OBJECT => { description => 'Location adjacent to an input object type definition.' }, INPUT_FIELD_DEFINITION => { description => 'Location adjacent to an input object field definition.' }, }, ); =head2 $ENUM_VALUE_META_TYPE The type describing enum values. =cut # makes field-resolver that takes resolver args and calls Moo accessor # returns field_def sub _make_moo_field { my ($field_name, $type) = @_; ($field_name => { resolve => sub { my ($root_value, $args, $context, $info) = @_; return undef unless $root_value->can($field_name); my @passon = %$args ? ($args) : (); $root_value->$field_name(@passon); }, type => $type }); } # makes field-resolver that takes resolver args and looks up "real" hash val # returns field_def sub _make_hash_bool_field { my ($field_name, $type, $real) = @_; ($field_name => { resolve => sub { my ($root_value, $args, $context, $info) = @_; !!$root_value->{$real}; }, type => $type }); } # makes field-resolver that takes resolver args and looks up "real" hash val # returns field_def sub _make_hash_field { my ($field_name, $type, $real) = @_; ($field_name => { resolve => sub { my ($root_value, $args, $context, $info) = @_; $root_value->{$real}; }, type => $type }); } # hash, returns array-ref of hashes with keys put in as 'name' sub _hash2array { [ map { +{ name => $_, %{$_[0]->{$_}} } } sort keys %{$_[0]} ]; } our $ENUM_VALUE_META_TYPE = GraphQL::Type::Object->new( name => '__EnumValue', is_introspection => 1, description => 'One possible value for a given Enum. Enum values are unique values, not ' . 'a placeholder for a string or numeric value. However an Enum value is ' . 'returned in a JSON response as a string.', fields => { name => { type => $String->non_null }, description => { type => $String }, _make_hash_bool_field(isDeprecated => $Boolean->non_null, 'isDeprecated'), _make_hash_field(deprecationReason => $String, 'deprecationReason'), }, ); =head2 $INPUT_VALUE_META_TYPE The type describing input values. =cut our $TYPE_META_TYPE; # predeclare so available for thunk our $INPUT_VALUE_META_TYPE = GraphQL::Type::Object->new( name => '__InputValue', is_introspection => 1, description => 'Arguments provided to Fields or Directives and the input fields of an ' . 'InputObject are represented as Input Values which describe their type ' . 'and optionally a default value.', fields => sub { { name => { type => $String->non_null }, description => { type => $String }, type => { type => $TYPE_META_TYPE->non_null }, defaultValue => { type => $String, description => 'A GraphQL-formatted string representing the default value for this ' . 'input value.', resolve => sub { DEBUG and _debug('__InputValue.defaultValue.resolve', @_); # must be JSON-encoded one time extra as buildClientSchema wants # it parseable as though literal in query - hence "GraphQL-formatted" return unless defined(my $value = $_[0]->{default_value}); my $gql = $_[0]->{type}->perl_to_graphql($value); return $gql if $_[0]->{type}->isa('GraphQL::Type::Enum'); $JSON_noutf8->encode($gql); }, }, } }, ); =head2 $FIELD_META_TYPE The type describing fields. =cut our $FIELD_META_TYPE = GraphQL::Type::Object->new( name => '__Field', is_introspection => 1, description => 'Object and Interface types are described by a list of Fields, each of ' . 'which has a name, potentially a list of arguments, and a return type.', fields => sub { { name => { type => $String->non_null }, description => { type => $String }, args => { type => $INPUT_VALUE_META_TYPE->non_null->list->non_null, resolve => sub { _hash2array($_[0]->{args}||{}) }, }, type => { type => $TYPE_META_TYPE->non_null }, _make_hash_bool_field(isDeprecated => $Boolean->non_null, 'isDeprecated'), _make_hash_field(deprecationReason => $String, 'deprecationReason'), } }, ); =head2 $DIRECTIVE_META_TYPE The type describing directives. =cut our $DIRECTIVE_META_TYPE = GraphQL::Type::Object->new( name => '__Directive', is_introspection => 1, description => 'A Directive provides a way to describe alternate runtime execution and ' . 'type validation behavior in a GraphQL document.' . "\n\nIn some cases, you need to provide options to alter GraphQL's " . 'execution behavior in ways field arguments will not suffice, such as ' . 'conditionally including or skipping a field. Directives provide this by ' . 'describing additional information to the executor.', fields => { _make_moo_field(name => $String->non_null), _make_moo_field(description => $String), _make_moo_field(locations => $DIRECTIVE_LOCATION_META_TYPE->non_null->list->non_null), args => { type => $INPUT_VALUE_META_TYPE->non_null->list->non_null, resolve => sub { _hash2array($_[0]->args) }, }, # NOTE onOperation onFragment onField not part of spec -> not implemented }, ); =head2 $TYPE_META_TYPE The type describing a type. "Yo dawg..." =cut use constant CLASS2KIND => { 'GraphQL::Type::Enum' => 'ENUM', 'GraphQL::Type::Interface' => 'INTERFACE', 'GraphQL::Type::List' => 'LIST', 'GraphQL::Type::Object' => 'OBJECT', 'GraphQL::Type::Union' => 'UNION', 'GraphQL::Type::InputObject' => 'INPUT_OBJECT', 'GraphQL::Type::NonNull' => 'NON_NULL', 'GraphQL::Type::Scalar' => 'SCALAR', }; $TYPE_META_TYPE = GraphQL::Type::Object->new( name => '__Type', is_introspection => 1, # and then some description => 'The fundamental unit of any GraphQL Schema is the type. There are ' . 'many kinds of types in GraphQL as represented by the `__TypeKind` enum.' . "\n\nDepending on the kind of a type, certain fields describe " . 'information about that type. Scalar types provide no information ' . 'beyond a name and description, while Enum types provide their values. ' . 'Object and Interface types provide the fields they describe. Abstract ' . 'types, Union and Interface, provide the Object types possible ' . 'at runtime. List and NonNull types compose other types.', fields => sub { { kind => { type => $TYPE_KIND_META_TYPE->non_null, resolve => sub { my $c = ref $_[0]; $c =~ s#__.*##; CLASS2KIND->{$c} // die "Unknown kind of type => ".ref $_[0] }, }, name => { resolve => sub { my ($root_value, $args, $context, $info) = @_; # if not a "real" name return undef if $root_value->can('of'); my @passon = %$args ? ($args) : (); $root_value->name(@passon); }, type => $String }, _make_moo_field(description => $String), fields => { type => $FIELD_META_TYPE->non_null->list, args => { includeDeprecated => { type => $Boolean, default_value => 0 } }, resolve => sub { my ($type, $args) = @_; return undef if !$type->DOES('GraphQL::Role::FieldsOutput'); my $map = $type->fields; $map = { map { ($_ => $map->{$_}) } grep !$map->{$_}{deprecation_reason}, keys %$map } if !$args->{includeDeprecated}; [ map { +{ name => $_, description => $map->{$_}{description}, args => $map->{$_}{args}, type => $map->{$_}{type}, isDeprecated => $map->{$_}{is_deprecated}, deprecationReason => $map->{$_}{deprecation_reason}, } } sort keys %{$map} ]; } }, interfaces => { type => $TYPE_META_TYPE->non_null->list, resolve => sub { my ($type) = @_; return if !$type->isa('GraphQL::Type::Object'); $type->interfaces || []; } }, possibleTypes => { type => $TYPE_META_TYPE->non_null->list, resolve => sub { return if !$_[0]->DOES('GraphQL::Role::Abstract'); $_[3]->{schema}->get_possible_types($_[0]); }, }, enumValues => { type => $ENUM_VALUE_META_TYPE->non_null->list, args => { includeDeprecated => { type => $Boolean, default_value => 0 } }, resolve => sub { my ($type, $args) = @_; return if !$type->isa('GraphQL::Type::Enum'); my $values = $type->values; DEBUG and _debug('enumValues.resolve', $type, $args, $values); $values = { map { ($_ => $values->{$_}) } grep !$values->{$_}{is_deprecated}, keys %$values } if !$args->{includeDeprecated}; [ map { +{ name => $_, description => $values->{$_}{description}, isDeprecated => $values->{$_}{is_deprecated}, deprecationReason => $values->{$_}{deprecation_reason}, } } sort keys %{$values} ]; }, }, inputFields => { type => $INPUT_VALUE_META_TYPE->non_null->list, resolve => sub { my ($type) = @_; return if !$type->isa('GraphQL::Type::InputObject'); _hash2array($type->fields || {}); }, }, ofType => { type => $TYPE_META_TYPE, resolve => sub { return unless $_[0]->can('of'); $_[0]->of }, }, } }, ); =head2 $SCHEMA_META_TYPE The type describing the schema itself. =cut our $SCHEMA_META_TYPE = GraphQL::Type::Object->new( name => '__Schema', is_introspection => 1, description => 'A GraphQL Schema defines the capabilities of a GraphQL server. It ' . 'exposes all available types and directives on the server, as well as ' . 'the entry points for query, mutation, and subscription operations.', fields => { types => { description => 'A list of all types supported by this server.', type => $TYPE_META_TYPE->non_null->list->non_null, resolve => sub { [ sort { $a->name cmp $b->name } values %{ $_[0]->name2type } ] }, }, queryType => { description => 'The type that query operations will be rooted at.', type => $TYPE_META_TYPE->non_null, resolve => sub { $_[0]->query }, }, mutationType => { description => 'If this server supports mutation, the type that ' . 'mutation operations will be rooted at.', type => $TYPE_META_TYPE, resolve => sub { $_[0]->mutation }, }, subscriptionType => { description => 'If this server support subscription, the type that ' . 'subscription operations will be rooted at.', type => $TYPE_META_TYPE, resolve => sub { $_[0]->subscription }, }, directives => { description => 'A list of all directives supported by this server.', type => $DIRECTIVE_META_TYPE->non_null->list->non_null, resolve => sub { $_[0]->directives }, } }, ); =head2 $SCHEMA_META_FIELD_DEF The meta-field existing on the top query. =cut our $SCHEMA_META_FIELD_DEF = { name => '__schema', type => $SCHEMA_META_TYPE->non_null, description => 'Access the current type schema of this server.', resolve => sub { $_[3]->{schema} }, # the $info }; =head2 $TYPE_META_FIELD_DEF The meta-field existing on the top query, describing a named type. =cut our $TYPE_META_FIELD_DEF = { name => '__type', type => $TYPE_META_TYPE, description => 'Request the type information of a single type.', args => { name => { type => $String->non_null } }, resolve => sub { $_[3]->{schema}->name2type->{$_[1]->{name}} }, # the $args, $info }; =head2 $TYPE_NAME_META_FIELD_DEF The meta-field existing on each object field, naming its type. =cut our $TYPE_NAME_META_FIELD_DEF = { name => '__typename', type => $String->non_null, description => 'The name of the current Object type at runtime.', resolve => sub { $_[3]->{parent_type}->name }, # the $info }; 1; GraphQL-0.53/lib/GraphQL/MaybeTypeCheck.pm0000644000175000017500000000372514070317276020142 0ustar osboxesosboxespackage GraphQL::MaybeTypeCheck; use 5.014; use strict; use warnings; use Attribute::Handlers; use Devel::StrictMode; use Import::Into; =head1 NAME GraphQL::MaybeTypeCheck - Conditional type-checking at runtime =head1 SYNOPSIS use GraphQL::MaybeTypeCheck; method foo( $arg1 Str, $arg2 Int ) :ReturnType(Map[Str, Int]) { # ... } =head1 DESCRIPTION This module B enables type-checking in the caller as implemented by L and L depending on whether L is activated. =head3 C ON When L is active, this module will import L into the caller with its default configuration. As of writing, this includes checking both argument count and type. When in strict mode this also Cs L which registers the C attribute. =head3 C OFF When strict mode is inactive this module still imports C into the caller however it sets C and C to L and disables argument type checking. This also installs a no-op C attribute so the existing syntax isn't broken. =cut sub ReturnType : ATTR(CODE) { my ($package, $symbol, $referent, $attr, $data) = @_; # If strict mode is enabled, wrap the sub so the return type is checked if (STRICT) { my %args = (@$data % 2) ? (scalar => @$data) : @$data; Return::Type->wrap_sub($referent, %args); } } sub import { return unless $_[0] eq __PACKAGE__; my $caller = caller; { no strict 'refs'; push @{"${caller}::ISA"}, __PACKAGE__; } if (STRICT) { Function::Parameters->import::into($caller, ':strict'); require Return::Type; } else { Function::Parameters->import::into($caller, { fun => {defaults => 'function_lax', check_argument_types => 0}, method => {defaults => 'method_lax', check_argument_types => 0}, }); } } 1; GraphQL-0.53/lib/GraphQL/Error.pm0000644000175000017500000000513614070317276016374 0ustar osboxesosboxespackage GraphQL::Error; use 5.014; use strict; use warnings; use Moo; use Types::Standard -all; use GraphQL::Type::Library -all; use GraphQL::MaybeTypeCheck; use GraphQL::Debug qw(_debug); our $VERSION = '0.02'; use constant DEBUG => $ENV{GRAPHQL_DEBUG}; my %NONENUM = map { ($_ => 1) } qw(original_error); use overload '""' => 'to_string'; =head1 NAME GraphQL::Error - GraphQL error object =head1 SYNOPSIS use GraphQL::Error; die GraphQL::Error->new(message => 'Something is not right...'); =head1 DESCRIPTION Class implementing GraphQL error object. =head1 ATTRIBUTES =head2 message =cut has message => (is => 'ro', isa => Str, required => 1); =head2 original_error If there is an original error to be preserved. =cut has original_error => (is => 'ro', isa => Any); =head2 locations Array-ref of Ls. =cut has locations => (is => 'ro', isa => ArrayRef[DocumentLocation]); =head2 path Array-ref of Ls or Cs describing the path from the top operation (being either fields, or a List offset). =cut has path => (is => 'ro', isa => ArrayRef[StrNameValid | Int]); =head2 extensions Hash-ref of Ls providing additional information. =cut has extensions => (is => 'ro', isa => Optional[HashRef[JSONable]]); =head1 METHODS =head2 is Is the supplied scalar an error object? =cut method is(Any $item) :ReturnType(Bool) { ref $item eq __PACKAGE__ } =head2 coerce If supplied scalar is an error object, return. If not, return one with it as message. If an object, message will be stringified version of that, it will be preserved as C. =cut method coerce( Any $item ) :ReturnType(InstanceOf[__PACKAGE__]) { DEBUG and _debug('Error.coerce', $item); return $item if __PACKAGE__->is($item); $item ||= 'Unknown error'; !is_Str($item) ? $self->new(message => $item.'', original_error => $item) : $self->new(message => $item); } =head2 but Returns a copy of the error object, but with the given properties (as with a C method, not coincidentally) overriding the existing ones. =cut sub but :ReturnType(InstanceOf[__PACKAGE__]) { my $self = shift; $self->new(%$self, @_); } =head2 to_string Converts to string. =cut method to_string(@ignore) :ReturnType(Str) { $self->message; } =head2 to_json Converts to a JSON-able hash, in the format to send back as a member of the C array in the results. =cut method to_json() :ReturnType(HashRef) { +{ map { ($_ => $self->{$_}) } grep !$NONENUM{$_}, keys %$self }; } __PACKAGE__->meta->make_immutable(); 1; GraphQL-0.53/lib/GraphQL/Directive.pm0000644000175000017500000001203614070317276017216 0ustar osboxesosboxespackage GraphQL::Directive; use 5.014; use strict; use warnings; use Moo; use MooX::Thunking; use GraphQL::MaybeTypeCheck; use GraphQL::Debug qw(_debug); use Types::Standard -all; use GraphQL::Type::Library -all; use GraphQL::Type::Scalar qw($Boolean $String); with qw( GraphQL::Role::Named GraphQL::Role::FieldsEither ); our $VERSION = '0.02'; use constant DEBUG => $ENV{GRAPHQL_DEBUG}; my @LOCATIONS = qw( QUERY MUTATION SUBSCRIPTION FIELD FRAGMENT_DEFINITION FRAGMENT_SPREAD INLINE_FRAGMENT SCHEMA SCALAR OBJECT FIELD_DEFINITION ARGUMENT_DEFINITION INTERFACE UNION ENUM ENUM_VALUE INPUT_OBJECT INPUT_FIELD_DEFINITION ); =head1 NAME GraphQL::Directive - GraphQL directive =head1 SYNOPSIS use GraphQL::Directive; my $directive = GraphQL::Directive->new( name => 'Object', interfaces => [ $interfaceType ], fields => { field_name => { type => $scalar_type, resolve => sub { '' } }}, ); =head1 ATTRIBUTES Has C, C from L. =head2 locations Array-ref of locations where the directive can occur. Must be one of these strings: QUERY MUTATION SUBSCRIPTION FIELD FRAGMENT_DEFINITION FRAGMENT_SPREAD INLINE_FRAGMENT SCHEMA SCALAR OBJECT FIELD_DEFINITION ARGUMENT_DEFINITION INTERFACE UNION ENUM ENUM_VALUE INPUT_OBJECT INPUT_FIELD_DEFINITION =cut has locations => (is => 'ro', isa => ArrayRef[Enum[@LOCATIONS]], required => 1); =head2 args Hash-ref of arguments. See L. =cut has args => (is => 'thunked', isa => FieldMapInput, required => 1); =head1 METHODS =head2 from_ast See L. =cut method from_ast( HashRef $name2type, HashRef $ast_node, ) :ReturnType(InstanceOf[__PACKAGE__]) { DEBUG and _debug('Directive.from_ast', $ast_node); $self->new( $self->_from_ast_named($ast_node), locations => $ast_node->{locations}, $self->_from_ast_fields($name2type, $ast_node, 'args'), ); } has to_doc => (is => 'lazy', isa => Str); sub _build_to_doc { my ($self) = @_; DEBUG and _debug('Directive.to_doc', $self); my @start = ( $self->_description_doc_lines($self->description), "directive \@@{[$self->name]}(", ); my @argtuples = $self->_make_fieldtuples($self->args); DEBUG and _debug('Directive.to_doc(args)', \@argtuples); my $end = ") on " . join(' | ', @{$self->locations}); return join("\n", @start).join( ', ', map $_->[0], @argtuples ).$end."\n" if !grep $_->[1], @argtuples; # no descriptions # if descriptions join '', map "$_\n", @start, (map { my ($main, @description) = @$_; ( map length() ? " $_" : "", @description, $main, ) } @argtuples), $end; } =head1 PACKAGE VARIABLES =head2 $GraphQL::Directive::DEPRECATED =cut $GraphQL::Directive::DEPRECATED = GraphQL::Directive->new( name => 'deprecated', description => 'Marks an element of a GraphQL schema as no longer supported.', locations => [ qw(FIELD_DEFINITION ENUM_VALUE) ], args => { reason => { type => $String, description => 'Explains why this element was deprecated, usually also including ' . 'a suggestion for how to access supported similar data. Formatted ' . 'in [Markdown](https://daringfireball.net/projects/markdown/).', default_value => 'No longer supported', }, }, ); =head2 $GraphQL::Directive::INCLUDE =cut $GraphQL::Directive::INCLUDE = GraphQL::Directive->new( name => 'include', description => 'Directs the executor to include this field or fragment only when the `if` argument is true.', locations => [ qw(FIELD FRAGMENT_SPREAD INLINE_FRAGMENT) ], args => { if => { type => $Boolean->non_null, description => 'Included when true.', }, }, ); =head2 $GraphQL::Directive::SKIP =cut $GraphQL::Directive::SKIP = GraphQL::Directive->new( name => 'skip', description => 'Directs the executor to skip this field or fragment when the `if` argument is true.', locations => [ qw(FIELD FRAGMENT_SPREAD INLINE_FRAGMENT) ], args => { if => { type => $Boolean->non_null, description => 'Skipped when true.', }, }, ); =head2 @GraphQL::Directive::SPECIFIED_DIRECTIVES Not exported. Contains the three GraphQL-specified directives: C<@skip>, C<@include>, C<@deprecated>, each of which are available with the variables above. Use if you want to have these plus your own directives in your schema: my $schema = GraphQL::Schema->new( # ... directives => [ @GraphQL::Directive::SPECIFIED_DIRECTIVES, $my_directive ], ); =cut @GraphQL::Directive::SPECIFIED_DIRECTIVES = ( $GraphQL::Directive::INCLUDE, $GraphQL::Directive::SKIP, $GraphQL::Directive::DEPRECATED, ); method _get_directive_values( HashRef $node, HashRef $variables, ) { DEBUG and _debug('_get_directive_values', $self->name, $node, $variables); my ($d) = grep $_->{name} eq $self->name, @{$node->{directives} || []}; return if !$d; GraphQL::Execution::_get_argument_values($self, $d, $variables); } __PACKAGE__->meta->make_immutable(); 1; GraphQL-0.53/lib/GraphQL/Role/0000755000175000017500000000000014170415414015633 5ustar osboxesosboxesGraphQL-0.53/lib/GraphQL/Role/Composite.pm0000644000175000017500000000066013160770160020135 0ustar osboxesosboxespackage GraphQL::Role::Composite; use 5.014; use strict; use warnings; use Moo::Role; our $VERSION = '0.02'; =head1 NAME GraphQL::Role::Composite - GraphQL object role =head1 SYNOPSIS with qw(GraphQL::Role::Composite); # or runtime Role::Tiny->apply_roles_to_object($foo, qw(GraphQL::Role::Composite)); =head1 DESCRIPTION Allows type constraints for composite objects. =cut __PACKAGE__->meta->make_immutable(); 1; GraphQL-0.53/lib/GraphQL/Role/FieldsInput.pm0000644000175000017500000000127113160770160020420 0ustar osboxesosboxespackage GraphQL::Role::FieldsInput; use 5.014; use strict; use warnings; use Moo::Role; use MooX::Thunking; use GraphQL::Type::Library -all; our $VERSION = '0.02'; =head1 NAME GraphQL::Role::FieldsInput - GraphQL object role implementing input fields =head1 SYNOPSIS with qw(GraphQL::Role::FieldsInput); # or runtime Role::Tiny->apply_roles_to_object($foo, qw(GraphQL::Role::FieldsInput)); =head1 DESCRIPTION Implements input fields. =head1 ATTRIBUTES =head2 fields Thunk of hash-ref mapping fields to their types. See L. =cut has fields => (is => 'thunked', isa => FieldMapInput, required => 1); __PACKAGE__->meta->make_immutable(); 1; GraphQL-0.53/lib/GraphQL/Role/FieldsOutput.pm0000644000175000017500000000167014070317276020632 0ustar osboxesosboxespackage GraphQL::Role::FieldsOutput; use 5.014; use strict; use warnings; use Moo::Role; use Types::Standard -all; use GraphQL::Type::Library -all; use MooX::Thunking; with qw( GraphQL::Role::FieldDeprecation GraphQL::Role::FieldsEither ); our $VERSION = '0.02'; =head1 NAME GraphQL::Role::FieldsOutput - GraphQL object role implementing output fields =head1 SYNOPSIS with qw(GraphQL::Role::FieldsOutput); # or runtime Role::Tiny->apply_roles_to_object($foo, qw(GraphQL::Role::FieldsOutput)); =head1 DESCRIPTION Implements output fields. =head1 ATTRIBUTES =head2 fields Thunk of hash-ref mapping fields to their types. See L. =cut has fields => (is => 'thunked', isa => FieldMapOutput, required => 1); around fields => sub { my ($orig, $self) = @_; $self->$orig; # de-thunk $self->_fields_deprecation_apply('fields'); $self->{fields}; }; __PACKAGE__->meta->make_immutable(); 1; GraphQL-0.53/lib/GraphQL/Role/Listable.pm0000644000175000017500000000133713160770160017734 0ustar osboxesosboxespackage GraphQL::Role::Listable; use 5.014; use strict; use warnings; use Types::Standard -all; use Moo::Role; use GraphQL::Type::List; our $VERSION = '0.02'; =head1 NAME GraphQL::Role::Listable - GraphQL object role =head1 SYNOPSIS with qw(GraphQL::Role::Listable); # or runtime Role::Tiny->apply_roles_to_object($foo, qw(GraphQL::Role::Listable)); =head1 DESCRIPTION Provides shortcut method for getting a type object's list wrapper. =head1 METHODS =head2 list Returns a wrapped version of the type using L. =cut has list => ( is => 'lazy', isa => InstanceOf['GraphQL::Type::List'], builder => sub { GraphQL::Type::List->new(of => $_[0]) }, ); __PACKAGE__->meta->make_immutable(); 1; GraphQL-0.53/lib/GraphQL/Role/Named.pm0000644000175000017500000000201314070317276017217 0ustar osboxesosboxespackage GraphQL::Role::Named; use 5.014; use strict; use warnings; use Moo::Role; use Types::Standard -all; use GraphQL::MaybeTypeCheck; use GraphQL::Type::Library qw(StrNameValid); our $VERSION = '0.02'; =head1 NAME GraphQL::Role::Named - GraphQL "named" object role =head1 SYNOPSIS with qw(GraphQL::Role::Named); =head1 DESCRIPTION Allows type constraints for named objects, providing also C and C attributes. =head1 ATTRIBUTES =head2 name =cut has name => (is => 'ro', isa => StrNameValid, required => 1); =head2 description Optional description. =cut has description => (is => 'ro', isa => Str); =head1 METHODS =head2 to_string Part of serialisation. =cut has to_string => (is => 'lazy', isa => Str, init_arg => undef, builder => sub { my ($self) = @_; $self->name; }); method _from_ast_named( HashRef $ast_node, ) { ( name => $ast_node->{name}, ($ast_node->{description} ? (description => $ast_node->{description}) : ()), ); } __PACKAGE__->meta->make_immutable(); 1; GraphQL-0.53/lib/GraphQL/Role/FieldsEither.pm0000644000175000017500000000575114070317276020556 0ustar osboxesosboxespackage GraphQL::Role::FieldsEither; use 5.014; use strict; use warnings; use Moo::Role; use GraphQL::Debug qw(_debug); use GraphQL::MaybeTypeCheck; use Types::Standard -all; use JSON::MaybeXS; with qw(GraphQL::Role::FieldDeprecation); our $VERSION = '0.02'; use constant DEBUG => $ENV{GRAPHQL_DEBUG}; my $JSON_noutf8 = JSON::MaybeXS->new->utf8(0)->allow_nonref; =head1 NAME GraphQL::Role::FieldsEither - GraphQL object role with code common to all fields =head1 SYNOPSIS with qw(GraphQL::Role::FieldsEither); # or runtime Role::Tiny->apply_roles_to_object($foo, qw(GraphQL::Role::FieldsEither)); =head1 DESCRIPTION Provides code useful to either type of fields. =cut method _make_field_def( HashRef $name2type, Str $field_name, HashRef $field_def, ) { DEBUG and _debug('FieldsEither._make_field_def', $field_def); require GraphQL::Schema; my %args; %args = (args => +{ map $self->_make_field_def($name2type, $_, $field_def->{args}{$_}), keys %{$field_def->{args}} }) if $field_def->{args}; ($_ => { %$field_def, type => GraphQL::Schema::lookup_type($field_def, $name2type), %args, }); } method _from_ast_fields( HashRef $name2type, HashRef $ast_node, Str $key, ) { my $fields = $ast_node->{$key}; $fields = $self->_from_ast_field_deprecate($_, $fields) for keys %$fields; ( $key => sub { +{ map { my @pair = eval { $self->_make_field_def($name2type, $_, $fields->{$_}) }; die "Error in field '$_': $@" if $@; @pair; } keys %$fields } }, ); } method _description_doc_lines( Maybe[Str] $description, ) { DEBUG and _debug('FieldsEither._description_doc_lines', $description); return if !$description; my @lines = $description ? split /\n/, $description : (); return if !@lines; if (@lines == 1) { return '"' . ($lines[0] =~ s#"#\\"#gr) . '"'; } elsif (@lines > 1) { return ( '"""', (map s#"""#\\"""#gr, @lines), '"""', ); } } method _make_fieldtuples( HashRef $fields, ) { DEBUG and _debug('FieldsEither._make_fieldtuples', $fields); map { my $field = $fields->{$_}; my @argtuples = map $_->[0], $self->_make_fieldtuples($field->{args} || {}); my $type = $field->{type}; my $line = $_; $line .= '('.join(', ', @argtuples).')' if @argtuples; $line .= ': ' . $type->to_string; $line .= ' = ' . $JSON_noutf8->encode( $type->perl_to_graphql($field->{default_value}) ) if exists $field->{default_value}; my @directives = map { my $args = $_->{arguments}; my @argtuples = map { "$_: " . $JSON_noutf8->encode($args->{$_}) } keys %$args; '@' . $_->{name} . (@argtuples ? '(' . join(', ', @argtuples) . ')' : ''); } @{ $field->{directives} || [] }; $line .= join(' ', ('', @directives)) if @directives; [ $self->_to_doc_field_deprecate($line, $field), $self->_description_doc_lines($field->{description}), ] } sort keys %$fields; } __PACKAGE__->meta->make_immutable(); 1; GraphQL-0.53/lib/GraphQL/Role/Nullable.pm0000644000175000017500000000137113160770160017731 0ustar osboxesosboxespackage GraphQL::Role::Nullable; use 5.014; use strict; use warnings; use Types::Standard -all; use Moo::Role; use GraphQL::Type::NonNull; our $VERSION = '0.02'; =head1 NAME GraphQL::Role::Nullable - GraphQL object role =head1 SYNOPSIS with qw(GraphQL::Role::Nullable); # or runtime Role::Tiny->apply_roles_to_object($foo, qw(GraphQL::Role::Nullable)); =head1 DESCRIPTION Allows type constraints for nullable objects. =head1 METHODS =head2 non_null Returns a wrapped version of the type using L, i.e. that may not be null. =cut has non_null => ( is => 'lazy', isa => InstanceOf['GraphQL::Type::NonNull'], builder => sub { GraphQL::Type::NonNull->new(of => $_[0]) }, ); __PACKAGE__->meta->make_immutable(); 1; GraphQL-0.53/lib/GraphQL/Role/FieldDeprecation.pm0000644000175000017500000000356414070317276021410 0ustar osboxesosboxespackage GraphQL::Role::FieldDeprecation; use 5.014; use strict; use warnings; use Moo::Role; use GraphQL::MaybeTypeCheck; use Types::Standard -all; use JSON::MaybeXS; our $VERSION = '0.02'; my $JSON_noutf8 = JSON::MaybeXS->new->utf8(0)->allow_nonref; =head1 NAME GraphQL::Role::FieldDeprecation - object role implementing deprecation of fields =head1 SYNOPSIS with qw(GraphQL::Role::FieldDeprecation); # or runtime Role::Tiny->apply_roles_to_object($foo, qw(GraphQL::Role::FieldDeprecation)); =head1 DESCRIPTION Implements deprecation of fields. =cut has _fields_deprecation_applied => (is => 'rw'); sub _fields_deprecation_apply { my ($self, $key) = @_; return if $self->_fields_deprecation_applied; $self->_fields_deprecation_applied(1); my $v = $self->{$key} = { %{$self->{$key}} }; # copy on write for my $name (keys %$v) { if (defined $v->{$name}{deprecation_reason}) { $v->{$name} = { %{$v->{$name}}, is_deprecated => 1 }; # copy on write } } }; method _from_ast_field_deprecate( Str $key, HashRef $values, ) { my $value = +{ %{$values->{$key}} }; my $directives = delete $value->{directives}; # ok as copy return $values unless $directives and @$directives; my ($deprecated) = grep $_->{name} eq 'deprecated', @$directives; return $values unless $deprecated; my $reason = $deprecated->{arguments}{reason} // $GraphQL::Directive::DEPRECATED->args->{reason}{default_value}; +{ %$values, $key => { %$value, deprecation_reason => $reason }, }; } method _to_doc_field_deprecate( Str $line, HashRef $value, ) { return $line if !$value->{is_deprecated}; $line .= ' @deprecated'; $line .= '(reason: ' . $JSON_noutf8->encode($value->{deprecation_reason}) . ')' if $value->{deprecation_reason} ne $GraphQL::Directive::DEPRECATED->args->{reason}{default_value}; $line; } __PACKAGE__->meta->make_immutable(); 1; GraphQL-0.53/lib/GraphQL/Role/HashMappable.pm0000644000175000017500000000306114070317276020524 0ustar osboxesosboxespackage GraphQL::Role::HashMappable; use 5.014; use strict; use warnings; use Moo::Role; use Types::Standard -all; use GraphQL::MaybeTypeCheck; our $VERSION = '0.02'; =head1 NAME GraphQL::Role::HashMappable - GraphQL object role =head1 SYNOPSIS with qw(GraphQL::Role::HashMappable); # or runtime Role::Tiny->apply_roles_to_object($foo, qw(GraphQL::Role::HashMappable)); =head1 DESCRIPTION Provides method for mapping code over a hash-ref. =head1 METHODS =head2 hashmap Given a hash-ref, returns a modified copy of the data. Returns undef if given that. Parameters: =over =item $item Hash-ref. =item $source Hash-ref of the source data for this hash. Will be used only for its keys. =item $code Code-ref. =back Each value will be the original value returned by the given code-ref, which is called with C<$keyname>, C<$value>. Will call the code for all given keys, but not copy over any values not existing in original item. If code throws an exception, the message will have added to it information about which data element caused it. =cut method hashmap(Maybe[HashRef] $item, HashRef $source, CodeRef $code) :ReturnType(Maybe[HashRef]) { return $item if !defined $item; my @errors = map qq{In field "$_": Unknown field.\n}, grep !exists $source->{$_}, sort keys %$item; my %newvalue = map { my @pair = eval { ($_ => scalar $code->($_, $item->{$_})) }; push @errors, qq{In field "$_": $@} if $@; exists $item->{$_} ? @pair : (); } sort keys %$source; die @errors if @errors; \%newvalue; } __PACKAGE__->meta->make_immutable(); 1; GraphQL-0.53/lib/GraphQL/Role/Output.pm0000644000175000017500000000065213160770160017474 0ustar osboxesosboxespackage GraphQL::Role::Output; use 5.014; use strict; use warnings; use Moo::Role; our $VERSION = '0.02'; =head1 NAME GraphQL::Role::Output - GraphQL "output" object role =head1 SYNOPSIS with qw(GraphQL::Role::Output); # or runtime Role::Tiny->apply_roles_to_object($foo, qw(GraphQL::Role::Output)); =head1 DESCRIPTION Allows type constraints for output objects. =cut __PACKAGE__->meta->make_immutable(); 1; GraphQL-0.53/lib/GraphQL/Role/Abstract.pm0000644000175000017500000000424514070317276017747 0ustar osboxesosboxespackage GraphQL::Role::Abstract; use 5.014; use strict; use warnings; use Moo::Role; use GraphQL::MaybeTypeCheck; use Types::Standard -all; our $VERSION = '0.02'; =head1 NAME GraphQL::Role::Abstract - GraphQL object role =head1 SYNOPSIS with qw(GraphQL::Role::Abstract); # or runtime Role::Tiny->apply_roles_to_object($foo, qw(GraphQL::Role::Abstract)); =head1 DESCRIPTION Allows type constraints for abstract objects. =cut method _complete_value( HashRef $context, ArrayRef[HashRef] $nodes, HashRef $info, ArrayRef $path, Any $result, ) { my $runtime_type = ($self->resolve_type || \&_default_resolve_type)->( $result, $context->{context_value}, $info, $self ); # TODO promise stuff $self->_ensure_valid_runtime_type( $runtime_type, $context, $nodes, $info, $result, )->_complete_value(@_); } method _ensure_valid_runtime_type( (Str | InstanceOf['GraphQL::Type::Object']) $runtime_type_or_name, HashRef $context, ArrayRef[HashRef] $nodes, HashRef $info, Any $result, ) :ReturnType(InstanceOf['GraphQL::Type::Object']) { my $runtime_type = is_InstanceOf($runtime_type_or_name) ? $runtime_type_or_name : $context->{schema}->name2type->{$runtime_type_or_name}; die GraphQL::Error->new( message => "Abstract type @{[$self->name]} must resolve to an " . "Object type at runtime for field @{[$info->{parent_type}->name]}." . "@{[$info->{field_name}]} with value $result, received '@{[$runtime_type->name]}'.", nodes => [ $nodes ], ) if !$runtime_type->isa('GraphQL::Type::Object'); die GraphQL::Error->new( message => "Runtime Object type '@{[$runtime_type->name]}' is not a possible type for " . "'@{[$self->name]}'.", nodes => [ $nodes ], ) if !$context->{schema}->is_possible_type($self, $runtime_type); $runtime_type; } fun _default_resolve_type( Any $value, Any $context, HashRef $info, (ConsumerOf['GraphQL::Role::Abstract']) $abstract_type, ) { my @possibles = @{ $info->{schema}->get_possible_types($abstract_type) }; # TODO promise stuff (grep $_->is_type_of->($value, $context, $info), grep $_->is_type_of, @possibles)[0]; } __PACKAGE__->meta->make_immutable(); 1; GraphQL-0.53/lib/GraphQL/Role/Input.pm0000644000175000017500000000063413160770160017273 0ustar osboxesosboxespackage GraphQL::Role::Input; use 5.014; use strict; use warnings; use Moo::Role; our $VERSION = '0.02'; =head1 NAME GraphQL::Role::Input - GraphQL object role =head1 SYNOPSIS with qw(GraphQL::Role::Input); # or runtime Role::Tiny->apply_roles_to_object($foo, qw(GraphQL::Role::Input)); =head1 DESCRIPTION Allows type constraints for input objects. =cut __PACKAGE__->meta->make_immutable(); 1; GraphQL-0.53/lib/GraphQL/Role/Leaf.pm0000644000175000017500000000166414070317276017055 0ustar osboxesosboxespackage GraphQL::Role::Leaf; use 5.014; use strict; use warnings; use Moo::Role; use GraphQL::MaybeTypeCheck; use Types::Standard -all; use GraphQL::Debug qw(_debug); our $VERSION = '0.02'; use constant DEBUG => $ENV{GRAPHQL_DEBUG}; =head1 NAME GraphQL::Role::Leaf - GraphQL "leaf" object role =head1 SYNOPSIS with qw(GraphQL::Role::Leaf); # or runtime Role::Tiny->apply_roles_to_object($foo, qw(GraphQL::Role::Leaf)); =head1 DESCRIPTION Allows type constraints for leaf objects. =cut method _complete_value( HashRef $context, ArrayRef[HashRef] $nodes, HashRef $info, ArrayRef $path, Any $result, ) { DEBUG and _debug('Leaf._complete_value', $self->to_string, $result); my $serialised = $self->perl_to_graphql($result); die GraphQL::Error->new(message => "Expected a value of type '@{[$self->to_string]}' but received: '$result'.\n$@") if $@; +{ data => $serialised }; } __PACKAGE__->meta->make_immutable(); 1; GraphQL-0.53/lib/GraphQL/Subscription.pm0000644000175000017500000001172214070317276017765 0ustar osboxesosboxespackage GraphQL::Subscription; use 5.014; use strict; use warnings; use Types::Standard -all; use Types::TypeTiny -all; use GraphQL::Type::Library -all; use GraphQL::MaybeTypeCheck; use GraphQL::Language::Parser qw(parse); use GraphQL::Error; use GraphQL::Debug qw(_debug); require GraphQL::Execution; use Exporter 'import'; =head1 NAME GraphQL::Subscription - Implement GraphQL subscriptions =cut our @EXPORT_OK = qw( subscribe ); our $VERSION = '0.02'; use constant DEBUG => $ENV{GRAPHQL_DEBUG}; # "DEBUG and" gets optimised out if false =head1 SYNOPSIS use GraphQL::Subscription qw(subscribe); my $result = subscribe($schema, $doc, $root_value); =head1 DESCRIPTION Implements a GraphQL "subscription" - a feed of events, commonly of others' mutations. =head1 METHODS =head2 subscribe my $result = subscribe( $schema, $doc, # can also be AST $root_value, $context_value, $variable_values, $operation_name, $field_resolver, $promise_code, $subscribe_resolver, ); Takes the same args as L, except that the C<$promise_code> is mandatory, and there is the optional C<$subscribe_resolver> which, supplied or not, will be used instead of the C<$field_resolver> to resolve the initial subscription. The C<$field_resolver> will still be used for resolving each result. Returns a promise of either: =over =item * an error result if the subscription fails (generated by GraphQL) =item * a L instance generated by a resolver, probably hooked up to a L instance =back The event source returns values by using L. To communicate errors, resolvers can throw errors in the normal way, or at the top level, use L. The values will be resolved in the normal GraphQL fashion, including that if there is no specific-to-field resolver, it must be a value acceptable to the supplied or default field-resolver, typically a hash-ref with a key of the field's name. =cut fun subscribe( (InstanceOf['GraphQL::Schema']) $schema, Str | ArrayRef[HashRef] $doc, Any $root_value, Any $context_value, Maybe[HashRef] $variable_values, Maybe[Str] $operation_name, Maybe[CodeLike] $field_resolver, PromiseCode $promise_code, Maybe[CodeLike] $subscribe_resolver = undef, ) :ReturnType(Promise) { die 'Must supply $promise_code' if !$promise_code; my $result = eval { my $ast; my $context = eval { $ast = ref($doc) ? $doc : parse($doc); GraphQL::Execution::_build_context( $schema, $ast, $root_value, $context_value, $variable_values, $operation_name, $subscribe_resolver, $promise_code, ); }; DEBUG and _debug('subscribe', $context, $@); die $@ if $@; # from GraphQL::Execution::_execute_operation my $operation = $context->{operation}; my $op_type = $operation->{operationType} || 'subscription'; my $type = $context->{schema}->$op_type; my ($fields) = $type->_collect_fields( $context, $operation->{selections}, [[], {}], {}, ); DEBUG and _debug('subscribe(fields)', $fields, $root_value); # from GraphQL::Execution::_execute_fields my ($field_names, $nodes_defs) = @$fields; die "Subscription needs to have only one field; got (@$field_names)\n" if @$field_names != 1; my $result_name = $field_names->[0]; my $nodes = $nodes_defs->{$result_name}; my $field_node = $nodes->[0]; my $field_name = $field_node->{name}; my $field_def = GraphQL::Execution::_get_field_def($context->{schema}, $type, $field_name); DEBUG and _debug('subscribe(resolve)', $type->to_string, $nodes, $root_value, $field_def); die "The subscription field '$field_name' is not defined\n" if !$field_def; my $resolve = $field_def->{subscribe} || $context->{field_resolver}; my $path = [ $result_name ]; my $info = GraphQL::Execution::_build_resolve_info( $context, $type, $field_def, $path, $nodes, ); my $result = GraphQL::Execution::_resolve_field_value_or_error( $context, $field_def, $nodes, $resolve, $root_value, $info, ); DEBUG and _debug('subscribe(result)', $result, $@); die "The subscription field '$field_name' returned non-AsyncIterator '$result'\n" if !is_AsyncIterator($result); $result->map_then( sub { GraphQL::Execution::execute( $schema, $ast, $_[0], $context_value, $variable_values, $operation_name, $field_resolver, $promise_code, ) }, sub { my ($error) = @_; die $error if !GraphQL::Error->is($error); GraphQL::Execution::_build_response(GraphQL::Execution::_wrap_error($error)); }, ); }; $result = GraphQL::Execution::_build_response(GraphQL::Execution::_wrap_error($@)) if $@; $promise_code->{resolve}->($result); } 1; GraphQL-0.53/lib/GraphQL/Type/0000755000175000017500000000000014170415414015653 5ustar osboxesosboxesGraphQL-0.53/lib/GraphQL/Type/List.pm0000644000175000017500000001013514070317276017132 0ustar osboxesosboxespackage GraphQL::Type::List; use 5.014; use strict; use warnings; use Moo; use Types::Standard -all; use GraphQL::Type::Library -all; use GraphQL::MaybeTypeCheck; use GraphQL::Debug qw(_debug); extends qw(GraphQL::Type); with qw(GraphQL::Role::Nullable); # A-ha my @TAKE_ON_ME = qw( GraphQL::Role::Input GraphQL::Role::Output ); our $VERSION = '0.02'; use constant DEBUG => $ENV{GRAPHQL_DEBUG}; =head1 NAME GraphQL::Type::List - GraphQL type that is a list of another type =head1 SYNOPSIS use GraphQL::Type::List; my $type = GraphQL::Type::List->new(of => $other_type); =head1 DESCRIPTION Type that is a wrapper for the type it is a list of. If the wrapped type has any of these roles, it will assume them: L, L. It is always L. =head1 ATTRIBUTES =head2 of GraphQL type object of which this is a list. =cut has of => ( is => 'ro', isa => InstanceOf['GraphQL::Type'], required => 1, handles => [ qw(name) ], ); =head1 METHODS =head2 BUILD L method that applies the relevant roles. =cut sub BUILD { my ($self, $args) = @_; my $of = $self->of; Role::Tiny->apply_roles_to_object($self, grep $of->DOES($_), @TAKE_ON_ME); } =head2 to_string Part of serialisation. =cut has to_string => (is => 'lazy', isa => Str, init_arg => undef, builder => sub { my ($self) = @_; '[' . $self->of->to_string . ']'; }); =head2 is_valid True if given Perl array-ref is a valid value for this type. =cut method is_valid(Any $item) :ReturnType(Bool) { return 1 if !defined $item; my $of = $self->of; return if grep !$of->is_valid($_), @{ $self->uplift($item) }; 1; } =head2 uplift Turn given Perl entity into valid value for this type if possible. Mainly to promote single value into a list if type dictates. =cut # This is a crime against God. graphql-js does it however. method uplift(Any $item) :ReturnType(Any) { return $item if ref($item) eq 'ARRAY' or !defined $item; [ $item ]; } method graphql_to_perl(Any $item) :ReturnType(Maybe[ArrayRef]) { return $item if !defined $item; $item = $self->uplift($item); my $of = $self->of; my $i = 0; my @errors; my @values = map { my $value = eval { $of->graphql_to_perl($_) }; push @errors, qq{In element #$i: $@} if $@; $i++; $value; } @$item; die @errors if @errors; \@values; } method perl_to_graphql(Any $item) :ReturnType(Maybe[ArrayRef]) { return $item if !defined $item; $item = $self->uplift($item); my $of = $self->of; my $i = 0; my @errors; my @values = map { my $value = eval { $of->perl_to_graphql($_) }; push @errors, qq{In element #$i: $@} if $@; $i++; $value; } @$item; die @errors if @errors; \@values; } method _complete_value( HashRef $context, ArrayRef[HashRef] $nodes, HashRef $info, ArrayRef $path, ArrayRef $result, ) { # TODO promise stuff my $item_type = $self->of; my $index = 0; my @errors; my @completed = map GraphQL::Execution::_complete_value_catching_error( $context, $item_type, $nodes, $info, [ @$path, $index++ ], $_, ), @$result; DEBUG and _debug("List._complete_value(done)", \@completed); (grep is_Promise($_), @completed) ? _promise_for_list($context, \@completed) : _merge_list(\@completed); } fun _merge_list( ArrayRef[ExecutionPartialResult] $list, ) :ReturnType(ExecutionPartialResult) { DEBUG and _debug("List._merge_list", $list); my @errors = map @{ $_->{errors} || [] }, @$list; my @data = map $_->{data}, @$list; DEBUG and _debug("List._merge_list(after)", \@data, \@errors); +{ data => \@data, @errors ? (errors => \@errors) : () }; } fun _promise_for_list( HashRef $context, ArrayRef $list, ) :ReturnType(Promise) { DEBUG and _debug('_promise_for_list', $list); die "Given a promise in list but no PromiseCode given\n" if !$context->{promise_code}; return $context->{promise_code}{all}->(@$list)->then(sub { DEBUG and _debug('_promise_for_list(all)', @_); _merge_list([ map $_->[0], @_ ]); }); } =head2 name The C of the type this object is a list of. =cut __PACKAGE__->meta->make_immutable(); 1; GraphQL-0.53/lib/GraphQL/Type/InputObject.pm0000644000175000017500000000550314070317276020450 0ustar osboxesosboxespackage GraphQL::Type::InputObject; use 5.014; use strict; use warnings; use Moo; use Types::Standard -all; use GraphQL::Type::Library -all; use GraphQL::MaybeTypeCheck; use GraphQL::Debug qw(_debug); extends qw(GraphQL::Type); with qw( GraphQL::Role::Input GraphQL::Role::Nullable GraphQL::Role::Named GraphQL::Role::FieldsInput GraphQL::Role::HashMappable GraphQL::Role::FieldsEither ); our $VERSION = '0.02'; use constant DEBUG => $ENV{GRAPHQL_DEBUG}; =head1 NAME GraphQL::Type::InputObject - GraphQL input object type =head1 SYNOPSIS use GraphQL::Type::InputObject; my $type = GraphQL::Type::InputObject->new( name => 'InputObject', fields => { field_name => { type => $scalar_type, resolve => sub { '' } }}, ); =head1 ATTRIBUTES Has C, C from L. Has C from L. =head1 METHODS =head2 is_valid True if given Perl hash-ref is a valid value for this type. =cut method is_valid(Maybe[HashRef] $item) :ReturnType(Bool) { return 1 if !defined $item; my $fields = $self->fields; return if grep !$fields->{$_}{type}->is_valid( $item->{$_} // $fields->{$_}{default_value} ), keys %$fields; 1; } =head2 uplift Turn given Perl entity into valid value for this type if possible. Applies default values. =cut method uplift(Maybe[HashRef] $item) :ReturnType(Maybe[HashRef]) { return $item if !defined $item; my $fields = $self->fields; $self->hashmap($item, $fields, sub { my ($key, $value) = @_; $fields->{$key}{type}->uplift( $value // $fields->{$key}{default_value} ); }); } method graphql_to_perl(ExpectObject $item) :ReturnType(Maybe[HashRef]) { return $item if !defined $item; $item = $self->uplift($item); my $fields = $self->fields; $self->hashmap($item, $fields, sub { $fields->{$_[0]}{type}->graphql_to_perl($_[1]); }); } method perl_to_graphql(ExpectObject $item) :ReturnType(Maybe[HashRef]) { return $item if !defined $item; $item = $self->uplift($item); my $fields = $self->fields; $self->hashmap($item, $fields, sub { $fields->{$_[0]}{type}->perl_to_graphql($_[1]); }); } method from_ast( HashRef $name2type, HashRef $ast_node, ) :ReturnType(InstanceOf[__PACKAGE__]) { $self->new( $self->_from_ast_named($ast_node), $self->_from_ast_fields($name2type, $ast_node, 'fields'), ); } has to_doc => (is => 'lazy', isa => Str); sub _build_to_doc { my ($self) = @_; DEBUG and _debug('InputObject.to_doc', $self); my @fieldlines = map { my ($main, @description) = @$_; ( @description, $main, ) } $self->_make_fieldtuples($self->fields); join '', map "$_\n", $self->_description_doc_lines($self->description), "input @{[$self->name]} {", (map length() ? " $_" : "", @fieldlines), "}"; } __PACKAGE__->meta->make_immutable(); 1; GraphQL-0.53/lib/GraphQL/Type/Enum.pm0000644000175000017500000000736014070317276017131 0ustar osboxesosboxespackage GraphQL::Type::Enum; use 5.014; use strict; use warnings; use Moo; use Types::Standard -all; use GraphQL::Type::Library -all; use GraphQL::Debug qw(_debug); use GraphQL::MaybeTypeCheck; extends qw(GraphQL::Type); with qw( GraphQL::Role::Input GraphQL::Role::Output GraphQL::Role::Leaf GraphQL::Role::Nullable GraphQL::Role::Named GraphQL::Role::FieldDeprecation GraphQL::Role::FieldsEither ); our $VERSION = '0.02'; use constant DEBUG => $ENV{GRAPHQL_DEBUG}; =head1 NAME GraphQL::Type::Enum - GraphQL enum type =head1 SYNOPSIS use GraphQL::Type::Enum; my %text2value; my $type = GraphQL::Type::Enum->new( name => 'Enum', values => { value1 => {}, value2 => { value => 'yo' } }, ); =head1 ATTRIBUTES Has C, C from L. =head2 values Hash-ref mapping value labels to a hash-ref description. Description keys, all optional: =over =item value Perl value of that item. If not specified, will be the string name of the value. Integers are often useful. =item deprecation_reason Reason if deprecated. If supplied, the hash for that value will also have a key C with a true value. =item description Description. =back =cut has values => ( is => 'ro', isa => Map[ StrNameValid, Dict[ value => Optional[Any], deprecation_reason => Optional[Str], description => Optional[Str], ] ], required => 1, ); =head1 METHODS =head2 is_valid True if given Perl entity is valid value for this type. Relies on unique stringification of the value. =cut has _name2value => (is => 'lazy', isa => Map[StrNameValid, Any]); sub _build__name2value { my ($self) = @_; my $v = $self->values; +{ map { ($_ => $v->{$_}{value}) } keys %$v }; } has _value2name => (is => 'lazy', isa => Map[Str, StrNameValid]); sub _build__value2name { my ($self) = @_; my $n2v = $self->_name2value; DEBUG and _debug('_build__value2name', $self, $n2v); +{ reverse %$n2v }; } method is_valid(Any $item) :ReturnType(Bool) { DEBUG and _debug('is_valid', $item, $item.'', $self->_value2name); return 1 if !defined $item; !!$self->_value2name->{$item}; } method graphql_to_perl(Maybe[Str | ScalarRef] $item) { DEBUG and _debug('graphql_to_perl', $item, $self->_name2value); return undef if !defined $item; $item = $$$item if ref($item) eq 'REF'; # Handle unquoted enum values $self->_name2value->{$item} // die "Expected type '@{[$self->to_string]}', found $item.\n"; } method perl_to_graphql(Any $item) { DEBUG and _debug('perl_to_graphql', $item, $self->_value2name); return undef if !defined $item; $self->_value2name->{$item} // die "Expected a value of type '@{[$self->to_string]}' but received: @{[ref($item)||qq{'$item'}]}.\n"; } =head2 BUILD Internal method. =cut sub BUILD { my ($self, $args) = @_; $self->_fields_deprecation_apply('values'); my $v = $self->values; for my $name (keys %$v) { $v->{$name}{value} = $name if !exists $v->{$name}{value}; # undef valid } } method from_ast( HashRef $name2type, HashRef $ast_node, ) :ReturnType(InstanceOf[__PACKAGE__]) { my $values = +{ %{$ast_node->{values}} }; $values = $self->_from_ast_field_deprecate($_, $values) for keys %$values; $self->new( $self->_from_ast_named($ast_node), values => $values, ); } has to_doc => (is => 'lazy', isa => Str); sub _build_to_doc { my ($self) = @_; my $v = $self->values; my @valuelines = map { ( $self->_description_doc_lines($v->{$_}{description}), $self->_to_doc_field_deprecate($_, $v->{$_}), ) } sort keys %$v; join '', map "$_\n", $self->_description_doc_lines($self->description), "enum @{[$self->name]} {", (map length() ? " $_" : "", @valuelines), "}"; } __PACKAGE__->meta->make_immutable(); 1; GraphQL-0.53/lib/GraphQL/Type/Union.pm0000644000175000017500000000506614070317276017316 0ustar osboxesosboxespackage GraphQL::Type::Union; use 5.014; use strict; use warnings; use Moo; use MooX::Thunking; use Types::Standard -all; use GraphQL::Type::Library -all; use GraphQL::MaybeTypeCheck; use GraphQL::Debug qw(_debug); extends qw(GraphQL::Type); with qw( GraphQL::Role::Output GraphQL::Role::Composite GraphQL::Role::Abstract GraphQL::Role::Nullable GraphQL::Role::Named ); use constant DEBUG => $ENV{GRAPHQL_DEBUG}; our $VERSION = '0.02'; =head1 NAME GraphQL::Type::Union - GraphQL union type =head1 SYNOPSIS use GraphQL::Type::Union; my $union_type = GraphQL::Type::Union->new( name => 'Union', types => [ $type1, $type2 ], resolve_type => sub { return $type1 if ref $_[0] eq 'Type1'; return $type2 if ref $_[0] eq 'Type2'; }, ); =head1 ATTRIBUTES Inherits C, C from L. =head2 types Thunked array-ref of L objects. =cut has types => ( is => 'thunked', isa => UniqueByProperty['name'] & ArrayRefNonEmpty[InstanceOf['GraphQL::Type::Object']], required => 1, ); =head2 resolve_type Optional code-ref. Input is a value, returns a GraphQL type object for it. If not given, relies on its possible type objects having a provided C. =cut has resolve_type => (is => 'ro', isa => CodeRef); =head1 METHODS =head2 get_types Returns list of Ls of which the object is a union, performing validation. =cut has _types_validated => (is => 'rw', isa => Bool); method get_types() :ReturnType(ArrayRefNonEmpty[InstanceOf['GraphQL::Type::Object']]) { my @types = @{ $self->types }; DEBUG and _debug('Union.get_types', $self->name, \@types); return \@types if $self->_types_validated; # only do once $self->_types_validated(1); if (!$self->resolve_type) { my @bad = map $_->name, grep !$_->is_type_of, @types; die $self->name." no resolve_type and no is_type_of for @bad" if @bad; } \@types; } method from_ast( HashRef $name2type, HashRef $ast_node, ) :ReturnType(InstanceOf[__PACKAGE__]) { DEBUG and _debug('Union.from_ast', $ast_node); $self->new( $self->_from_ast_named($ast_node), resolve_type => sub {}, # fake $self->_from_ast_maptype($name2type, $ast_node, 'types'), ); } has to_doc => (is => 'lazy', isa => Str); sub _build_to_doc { my ($self) = @_; DEBUG and _debug('Union.to_doc', $self); join '', map "$_\n", ($self->description ? (map "# $_", split /\n/, $self->description) : ()), "union @{[$self->name]} = " . join(' | ', map $_->name, @{$self->{types}}); } __PACKAGE__->meta->make_immutable(); 1; GraphQL-0.53/lib/GraphQL/Type/Object.pm0000644000175000017500000001400414070317276017424 0ustar osboxesosboxespackage GraphQL::Type::Object; use 5.014; use strict; use warnings; use Moo; use GraphQL::Debug qw(_debug); use Types::Standard -all; use GraphQL::Type::Library -all; use MooX::Thunking; use GraphQL::MaybeTypeCheck; extends qw(GraphQL::Type); with qw( GraphQL::Role::Output GraphQL::Role::Composite GraphQL::Role::Nullable GraphQL::Role::Named GraphQL::Role::FieldsOutput GraphQL::Role::HashMappable ); our $VERSION = '0.02'; use constant DEBUG => $ENV{GRAPHQL_DEBUG}; =head1 NAME GraphQL::Type::Object - GraphQL object type =head1 SYNOPSIS use GraphQL::Type::Object; my $interface_type; my $implementing_type = GraphQL::Type::Object->new( name => 'Object', interfaces => [ $interface_type ], fields => { field_name => { type => $scalar_type, resolve => sub { '' } }}, ); =head1 ATTRIBUTES Has C, C from L. Has C from L. =head2 interfaces Optional, thunked array-ref of interface type objects implemented. =cut has interfaces => (is => 'thunked', isa => ArrayRef[InstanceOf['GraphQL::Type::Interface']]); =head2 is_type_of Optional code-ref. Input is a value, an execution context hash-ref, and resolve-info hash-ref. =cut has is_type_of => (is => 'ro', isa => CodeRef); method graphql_to_perl(Maybe[HashRef] $item) :ReturnType(Maybe[HashRef]) { return $item if !defined $item; $item = $self->uplift($item); my $fields = $self->fields; $self->hashmap($item, $fields, sub { my ($key, $value) = @_; $fields->{$key}{type}->graphql_to_perl( $value // $fields->{$key}{default_value} ); }); } has to_doc => (is => 'lazy', isa => Str); sub _build_to_doc { my ($self) = @_; DEBUG and _debug('Object.to_doc', $self); my @fieldlines = map { my ($main, @description) = @$_; ( @description, $main, ) } $self->_make_fieldtuples($self->fields); my $implements = join ' & ', map $_->name, @{ $self->interfaces || [] }; $implements &&= 'implements ' . $implements . ' '; join '', map "$_\n", $self->_description_doc_lines($self->description), "type @{[$self->name]} $implements\{", (map length() ? " $_" : "", @fieldlines), "}"; } method from_ast( HashRef $name2type, HashRef $ast_node, ) :ReturnType(InstanceOf[__PACKAGE__]) { $self->new( $self->_from_ast_named($ast_node), $self->_from_ast_maptype($name2type, $ast_node, 'interfaces'), $self->_from_ast_fields($name2type, $ast_node, 'fields'), ); } method _collect_fields( HashRef $context, ArrayRef $selections, FieldsGot $fields_got, Map[StrNameValid,Bool] $visited_fragments, ) { DEBUG and _debug('_collect_fields', $self->to_string, $fields_got, $selections); for my $selection (@$selections) { my $node = $selection; next if !_should_include_node($context->{variable_values}, $node); if ($selection->{kind} eq 'field') { my $use_name = $node->{alias} || $node->{name}; my ($field_names, $nodes_defs) = @$fields_got; $field_names = [ @$field_names, $use_name ] if !exists $nodes_defs->{$use_name}; $nodes_defs = { %$nodes_defs, $use_name => [ @{$nodes_defs->{$use_name} || []}, $node ], }; $fields_got = [ $field_names, $nodes_defs ]; # no mutation } elsif ($selection->{kind} eq 'inline_fragment') { next if !$self->_fragment_condition_match($context, $node); ($fields_got, $visited_fragments) = $self->_collect_fields( $context, $node->{selections}, $fields_got, $visited_fragments, ); } elsif ($selection->{kind} eq 'fragment_spread') { my $frag_name = $node->{name}; next if $visited_fragments->{$frag_name}; $visited_fragments = { %$visited_fragments, $frag_name => 1 }; # !mutate my $fragment = $context->{fragments}{$frag_name}; next if !$fragment; next if !$self->_fragment_condition_match($context, $fragment); DEBUG and _debug('_collect_fields(fragment_spread)', $fragment); ($fields_got, $visited_fragments) = $self->_collect_fields( $context, $fragment->{selections}, $fields_got, $visited_fragments, ); } } ($fields_got, $visited_fragments); } method _fragment_condition_match( HashRef $context, HashRef $node, ) :ReturnType(Bool) { DEBUG and _debug('_fragment_condition_match', $self->to_string, $node); return 1 if !$node->{on}; return 1 if $node->{on} eq $self->name; my $condition_type = $context->{schema}->name2type->{$node->{on}} // die GraphQL::Error->new( message => "Unknown type for fragment condition '$node->{on}'." ); return '' if !$condition_type->DOES('GraphQL::Role::Abstract'); $context->{schema}->is_possible_type($condition_type, $self); } fun _should_include_node( HashRef $variables, HashRef $node, ) :ReturnType(Bool) { DEBUG and _debug('_should_include_node', $variables, $node); my $skip = $GraphQL::Directive::SKIP->_get_directive_values($node, $variables); return '' if $skip and $skip->{if}; my $include = $GraphQL::Directive::INCLUDE->_get_directive_values($node, $variables); return '' if $include and !$include->{if}; 1; } method _complete_value( HashRef $context, ArrayRef[HashRef] $nodes, HashRef $info, ArrayRef $path, Any $result, ) { if ($self->is_type_of) { my $is_type_of = $self->is_type_of->($result, $context->{context_value}, $info); # TODO promise stuff die GraphQL::Error->new(message => "Expected a value of type '@{[$self->to_string]}' but received: '@{[ref($result)||$result]}'.") if !$is_type_of; } my $subfield_nodes = [[], {}]; my $visited_fragment_names = {}; for (grep $_->{selections}, @$nodes) { ($subfield_nodes, $visited_fragment_names) = $self->_collect_fields( $context, $_->{selections}, $subfield_nodes, $visited_fragment_names, ); } DEBUG and _debug('Object._complete_value', $self->to_string, $subfield_nodes, $result); GraphQL::Execution::_execute_fields($context, $self, $result, $path, $subfield_nodes); } __PACKAGE__->meta->make_immutable(); 1; GraphQL-0.53/lib/GraphQL/Type/Scalar.pm0000644000175000017500000001236614170415131017422 0ustar osboxesosboxespackage GraphQL::Type::Scalar; use 5.014; use strict; use warnings; use Moo; use GraphQL::Type::Library -all; use GraphQL::Debug qw(_debug); use Types::Standard -all; use JSON::MaybeXS qw(JSON is_bool); use Exporter 'import'; extends qw(GraphQL::Type); with qw( GraphQL::Role::Input GraphQL::Role::Output GraphQL::Role::Leaf GraphQL::Role::Nullable GraphQL::Role::Named GraphQL::Role::FieldsEither ); use GraphQL::MaybeTypeCheck; use GraphQL::Plugin::Type; our $VERSION = '0.02'; our @EXPORT_OK = qw($Int $Float $String $Boolean $ID); use constant DEBUG => $ENV{GRAPHQL_DEBUG}; my $JSON = JSON::MaybeXS->new->allow_nonref->canonical; =head1 NAME GraphQL::Type::Scalar - GraphQL scalar type =head1 SYNOPSIS use GraphQL::Type::Scalar; my $int_type = GraphQL::Type::Scalar->new( name => 'Int', description => 'The `Int` scalar type represents non-fractional signed whole numeric ' . 'values. Int can represent values between -(2^31) and 2^31 - 1. ', serialize => \&coerce_int, parse_value => \&coerce_int, ); =head1 ATTRIBUTES Has C, C from L. =head2 serialize Code-ref. Required. Coerces B a Perl entity of the required type, B a GraphQL entity, or throws an exception. Must throw an exception if passed a defined (i.e. non-null) but invalid Perl object of the relevant type. C must always be valid. =cut has serialize => (is => 'ro', isa => CodeRef, required => 1); =head2 parse_value Code-ref. Required if is for an input type. Coerces B a GraphQL entity, B a Perl entity of the required type, or throws an exception. =cut has parse_value => (is => 'ro', isa => CodeRef); =head1 METHODS =head2 is_valid True if given Perl entity is valid value for this type. Uses L attribute. =cut method is_valid(Any $item) :ReturnType(Bool) { return 1 if !defined $item; eval { $self->serialize->($item); 1 }; } method graphql_to_perl(Any $item) :ReturnType(Any) { $self->parse_value->($item); } method perl_to_graphql(Any $item) :ReturnType(Any) { $self->serialize->($item); } method from_ast( HashRef $name2type, HashRef $ast_node, ) :ReturnType(InstanceOf[__PACKAGE__]) { DEBUG and _debug('Scalar.from_ast', $ast_node); $self->new( $self->_from_ast_named($ast_node), serialize => sub { require Carp; Carp::croak "Fake serialize called" }, parse_value => sub { require Carp; Carp::croak "Fake parse_value called" }, ); } has to_doc => (is => 'lazy', isa => Str); sub _build_to_doc { my ($self) = @_; DEBUG and _debug('Scalar.to_doc', $self); join '', map "$_\n", $self->_description_doc_lines($self->description), "scalar @{[$self->name]}"; } =head1 EXPORTED VARIABLES =head2 $Int =cut sub _leave_undef { my ($closure) = @_; sub { return undef if !defined $_[0]; goto &$closure; }; } our $Int = GraphQL::Type::Scalar->new( name => 'Int', description => 'The `Int` scalar type represents non-fractional signed whole numeric ' . 'values. Int can represent values between -(2^31) and 2^31 - 1.', serialize => _leave_undef(sub { !is_Int32Signed($_[0]) and die "Not an Int.\n"; $_[0]+0 }), parse_value => _leave_undef(sub { !is_Int32Signed($_[0]) and die "Not an Int.\n"; $_[0]+0 }), ); =head2 $Float =cut our $Float = GraphQL::Type::Scalar->new( name => 'Float', description => 'The `Float` scalar type represents signed double-precision fractional ' . 'values as specified by ' . '[IEEE 754](http://en.wikipedia.org/wiki/IEEE_floating_point).', serialize => _leave_undef(sub { !is_Num($_[0]) and die "Not a Float.\n"; $_[0]+0 }), parse_value => _leave_undef(sub { !is_Num($_[0]) and die "Not a Float.\n"; $_[0]+0 }), ); =head2 $String =cut our $String = GraphQL::Type::Scalar->new( name => 'String', description => 'The `String` scalar type represents textual data, represented as UTF-8 ' . 'character sequences. The String type is most often used by GraphQL to ' . 'represent free-form human-readable text.', serialize => _leave_undef(sub { !is_Str($_[0]) and die "Not a String.\n"; $_[0].'' }), parse_value => _leave_undef(sub { !is_Str($_[0]) and die "Not a String.\n"; $_[0] }), ); =head2 $Boolean =cut our $Boolean = GraphQL::Type::Scalar->new( name => 'Boolean', description => 'The `Boolean` scalar type represents `true` or `false`.', serialize => _leave_undef(sub { !is_Bool($_[0]) and !is_bool($_[0]) and die "Not a Boolean.\n"; $_[0] ? JSON->true : JSON->false }), parse_value => _leave_undef(sub { !is_Bool($_[0]) and !is_bool($_[0]) and die "Not a Boolean.\n"; $_[0]+0 }), ); =head2 $ID =cut our $ID = GraphQL::Type::Scalar->new( name => 'ID', description => 'The `ID` scalar type represents a unique identifier, often used to ' . 'refetch an object or as key for a cache. The ID type appears in a JSON ' . 'response as a String; however, it is not intended to be human-readable. ' . 'When expected as an input type, any string (such as `"4"`) or integer ' . '(such as `4`) input value will be accepted as an ID.', serialize => _leave_undef(sub { Str->(@_); $_[0].'' }), parse_value => _leave_undef(sub { Str->(@_); $_[0] }), ); GraphQL::Plugin::Type->register($_) for ($Int, $Float, $String, $Boolean, $ID); __PACKAGE__->meta->make_immutable(); 1; GraphQL-0.53/lib/GraphQL/Type/Interface.pm0000644000175000017500000000331014070317276020114 0ustar osboxesosboxespackage GraphQL::Type::Interface; use 5.014; use strict; use warnings; use Moo; use Types::Standard -all; use GraphQL::MaybeTypeCheck; extends qw(GraphQL::Type); with qw( GraphQL::Role::Output GraphQL::Role::Composite GraphQL::Role::Abstract GraphQL::Role::Nullable GraphQL::Role::Named GraphQL::Role::FieldsOutput GraphQL::Role::FieldsEither ); our $VERSION = '0.02'; use constant DEBUG => $ENV{GRAPHQL_DEBUG}; =head1 NAME GraphQL::Type::Interface - GraphQL interface type =head1 SYNOPSIS use GraphQL::Type::Interface; my $ImplementingType; my $InterfaceType = GraphQL::Type::Interface->new( name => 'Interface', fields => { field_name => { type => $scalar_type } }, resolve_type => sub { return $ImplementingType; }, ); =head1 ATTRIBUTES Has C, C from L. Has C from L. =head2 resolve_type Optional code-ref to resolve types. =cut has resolve_type => (is => 'ro', isa => CodeRef); method from_ast( HashRef $name2type, HashRef $ast_node, ) :ReturnType(InstanceOf[__PACKAGE__]) { $self->new( $self->_from_ast_named($ast_node), $self->_from_ast_fields($name2type, $ast_node, 'fields'), ); } has to_doc => (is => 'lazy', isa => Str); sub _build_to_doc { my ($self) = @_; DEBUG and _debug('Interface.to_doc', $self); my @fieldlines = map { my ($main, @description) = @$_; ( @description, $main, ) } $self->_make_fieldtuples($self->fields); join '', map "$_\n", $self->_description_doc_lines($self->description), "interface @{[$self->name]} {", (map length() ? " $_" : "", @fieldlines), "}"; } __PACKAGE__->meta->make_immutable(); 1; GraphQL-0.53/lib/GraphQL/Type/NonNull.pm0000644000175000017500000000316614070317276017612 0ustar osboxesosboxespackage GraphQL::Type::NonNull; use 5.014; use strict; use warnings; use Moo; use Types::Standard -all; use GraphQL::MaybeTypeCheck; extends qw(GraphQL::Type); our $VERSION = '0.02'; # A-ha my @TAKE_ON_ME = qw( GraphQL::Role::Input GraphQL::Role::Output ); =head1 NAME GraphQL::Type::NonNull - GraphQL type that is a non-null version of another type =head1 SYNOPSIS use GraphQL::Type::NonNull; my $type = GraphQL::Type::NonNull->new(of => $other_type); =head1 DESCRIPTION Type that is a wrapper for another type. Means data cannot be a null value. =head1 ATTRIBUTES =head2 of GraphQL type object of which this is a non-null version. =cut has of => ( is => 'ro', isa => InstanceOf['GraphQL::Type'], required => 1, handles => [ qw(name) ], ); =head1 METHODS =head2 BUILD L method that applies the relevant roles. =cut sub BUILD { my ($self, $args) = @_; my $of = $self->of; Role::Tiny->apply_roles_to_object($self, grep $of->DOES($_), @TAKE_ON_ME); } =head2 to_string Part of serialisation. =cut has to_string => (is => 'lazy', isa => Str, init_arg => undef, builder => sub { my ($self) = @_; $self->of->to_string . '!'; }); =head2 is_valid True if given Perl value is a valid value for this type. =cut method is_valid(Any $item) :ReturnType(Bool) { return if !defined $item or !$self->of->is_valid($item); 1; } method graphql_to_perl(Any $item) :ReturnType(Any) { my $of = $self->of; $of->graphql_to_perl($item) // die $self->to_string . " given null value.\n"; } =head2 name The C of the type this object is a non-null version of. =cut __PACKAGE__->meta->make_immutable(); 1; GraphQL-0.53/lib/GraphQL/Type/Library.pm0000644000175000017500000002331714070317276017631 0ustar osboxesosboxespackage GraphQL::Type::Library; use 5.014; use strict; use warnings; use Type::Library -base, -declare => qw( StrNameValid FieldMapInput ValuesMatchTypes DocumentLocation JSONable ErrorResult FieldsGot ); use Type::Utils -all; use Types::TypeTiny -all; use Types::Standard -all; use JSON::MaybeXS; our $VERSION = '0.02'; my $JSON = JSON::MaybeXS->new->allow_nonref; =head1 NAME GraphQL::Type::Library - GraphQL type library =head1 SYNOPSIS use GraphQL::Type::Library -all; has name => (is => 'ro', isa => StrNameValid, required => 1); =head1 DESCRIPTION Provides L types. =head1 TYPES =head2 StrNameValid If called with a string that is not a valid GraphQL name, will throw an exception. Suitable for passing to an C constraint in L. =cut declare "StrNameValid", as StrMatch[ qr/^[_a-zA-Z][_a-zA-Z0-9]*$/ ]; =head2 ValuesMatchTypes Subtype of L, whose values are hash-refs. Takes two parameters: =over =item value keyname Optional within the second-level hashes. =item type keyname Values will be a L. Mandatory within the second-level hashes. =back In the second-level hashes, the values (if given) must pass the GraphQL type constraint. =cut declare "ValuesMatchTypes", constraint_generator => sub { my ($value_key, $type_key) = @_; declare as HashRef[Dict[ $type_key => ConsumerOf['GraphQL::Role::Input'], slurpy Any, ]], where { !grep { $_->{$value_key} and !$_->{$type_key}->is_valid($_->{$value_key}) } values %$_ }, inline_as { (undef, <{$value_key} and !\$_->{$type_key}->is_valid(\$_->{$value_key}) } values %{$_[1]} EOF }; }; =head2 FieldsGot Data item describing the fields found in a particular object in a query. Preserves their order. =cut declare "FieldsGot", as Tuple[ArrayRef[StrNameValid], Map[ StrNameValid, ArrayRef[HashRef] ]]; =head2 FieldMapInput Hash-ref mapping field names to a hash-ref description. Description keys, all optional except C: =over =item type GraphQL input type for the field. =item default_value Default value for this argument if none supplied. Must be same type as the C (implemented with type L. B this is a Perl value, not a JSON/GraphQL value. =item description Description. =back =cut declare "FieldMapInput", as Map[ StrNameValid, Dict[ type => ConsumerOf['GraphQL::Role::Input'], default_value => Optional[Any], directives => Optional[ArrayRef[HashRef]], description => Optional[Str], ] ] & ValuesMatchTypes['default_value', 'type' ]; =head2 FieldMapOutput Hash-ref mapping field names to a hash-ref description. Description keys, all optional except C: =head3 type GraphQL output type for the field. =head3 args A L. =head3 subscribe Code-ref to return a subscription to the field from a given source-object. See L. =head3 deprecation_reason Reason if deprecated. If given, also sets a boolean key of C to true. =head3 description Description. =head3 resolve Code-ref to return a given property from a given source-object. A key concept is to remember that the "object" on which these fields exist, were themselves returned by other fields. There are no restrictions on what you can return, so long as it is a scalar, and if your return type is a L, that scalar is an array-ref. Emphasis has been put on there being Perl values here. Conversion between Perl and GraphQL values is taken care of by L types, and it is only scalar information that will be returned to the client, albeit in the shape dictated by the object types. An example function that takes a name and GraphQL type, and returns a field definition, with a resolver that calls read-only L accessors, suitable for placing (several of) inside the hash-ref defining a type's fields: sub _make_moo_field { my ($field_name, $type) = @_; ($field_name => { resolve => sub { my ($root_value, $args, $context, $info) = @_; my @passon = %$args ? ($args) : (); return undef unless $root_value->can($field_name); $root_value->$field_name(@passon); }, type => $type }); } # ... fields => { _make_moo_field(name => $String), _make_moo_field(description => $String), }, # ... The code-ref will be called with these parameters: =head4 $source The Perl entity (possibly a blessed object) returned by the resolver that conjured up this GraphQL object. =head4 $args Hash-ref of the arguments passed to the field. The values will be Perl values. =head4 $context The "context" value supplied to the call to L. Can be used for authenticated user information, or a per-request cache. =head4 $info A hash-ref describing this node of the request; see L below. =head3 info hash =head4 field_name The real name of this field. =head4 field_nodes The array of Abstract Syntax Tree (AST) nodes that refer to this field in this "selection set" (set of fields) on this object. There may be more than one such set for a given field, if it is requested more than once with a given name (not with an alias) - the results will be combined into one reply. =head4 return_type The return type. =head4 parent_type The type of which this field is part. =head4 path The hierarchy of fields from the query root to this field-resolution. =head4 schema L object. =head4 fragments Any fragments applying to this request. =head4 root_value The "root value" given to C. =head4 operation A hash-ref describing the operation (C, etc) being executed. =head4 variable_values the operation's arguments, filled out with the variables hash supplied to the request. =head4 promise_code A hash-ref. The relevant value supplied to the C function. =cut declare "FieldMapOutput", as Map[ StrNameValid, Dict[ type => ConsumerOf['GraphQL::Role::Output'], args => Optional[FieldMapInput], resolve => Optional[CodeRef], subscribe => Optional[CodeRef], directives => Optional[ArrayRef[HashRef]], deprecation_reason => Optional[Str], description => Optional[Str], ] ]; =head2 Int32Signed 32-bit signed integer. =cut declare "Int32Signed", as Int, where { $_ >= -2147483648 and $_ <= 2147483647 }; =head2 ArrayRefNonEmpty Like L but requires at least one entry. =cut declare "ArrayRefNonEmpty", constraint_generator => sub { intersection [ ArrayRef[@_], Tuple[Any, slurpy Any] ] }; =head2 UniqueByProperty An ArrayRef, its members' property (the one in the parameter) can occur only once. use Moo; use GraphQL::Type::Library -all; has types => ( is => 'ro', isa => UniqueByProperty['name'] & ArrayRef[InstanceOf['GraphQL::Type::Object']], required => 1, ); =cut declare "UniqueByProperty", constraint_generator => sub { die "must give one property name" unless @_ == 1; my ($prop) = @_; declare as ArrayRef[HasMethods[$prop]], where { my %seen; !grep $seen{$_->$prop}++, @$_; }, inline_as { (undef, "my %seen; !grep \$seen{\$_->$prop}++, \@{$_[1]};"); }; }; =head2 ExpectObject A C that produces a GraphQL-like message if it fails, saying "found not an object". =cut declare "ExpectObject", as Maybe[HashRef], message { "found not an object" }; =head2 DocumentLocation Hash-ref that has keys C and C which are C. =cut declare "DocumentLocation", as Dict[ line => Int, column => Int, ]; =head2 JSONable A value that will be JSON-able. =cut declare "JSONable", as Any, where { $JSON->encode($_); 1 }; =head2 ErrorResult Hash-ref that has keys C, C, C, C. =cut declare "ErrorResult", as Dict[ message => Str, path => Optional[ArrayRef[Str]], locations => Optional[ArrayRef[DocumentLocation]], extensions => Optional[HashRef[JSONable]], ]; =head2 ExecutionResult Hash-ref that has keys C and/or C. The C, if present, will be an array-ref of C. The C if present will be the return data, being a hash-ref whose values are either further hashes, array-refs, or scalars. It will be JSON-able. =cut declare "ExecutionResult", as Dict[ data => Optional[JSONable], errors => Optional[ArrayRef[ErrorResult]], ]; =head2 ExecutionPartialResult Hash-ref that has keys C and/or C. Like L above, but the C, if present, will be an array-ref of L objects. =cut declare "ExecutionPartialResult", as Dict[ data => Optional[JSONable], errors => Optional[ArrayRef[InstanceOf['GraphQL::Error']]], ]; =head2 Promise An object that has a C method. =cut declare "Promise", as HasMethods[qw(then)]; =head2 PromiseCode A hash-ref with three keys: C, C, C. The values are all code-refs that take one value (for C, an array-ref), and create the given kind of Promise. An example, enabling interoperation with L: use Promises qw(collect resolved rejected); { all => \&collect, resolve => \&resolved, reject => \&rejected, }, Must also have a C key for use with L, with code returning a promise that can then have C or C called on it. =cut declare "PromiseCode", as Dict[ resolve => CodeLike, all => CodeLike, reject => CodeLike, new => Optional[CodeLike], ]; =head2 AsyncIterator An instance of L. =cut declare "AsyncIterator", as InstanceOf['GraphQL::AsyncIterator']; =head1 AUTHOR Ed J, C<< >> =cut 1; GraphQL-0.53/lib/GraphQL/Validation.pm0000644000175000017500000000651013160770160017364 0ustar osboxesosboxespackage GraphQL::Validation; use 5.014; use strict; use warnings; =head1 NAME GraphQL::Validation - Perl implementation =head1 VERSION Version 0.02 =cut our $VERSION = '0.02'; =head1 SYNOPSIS Perhaps a little code snippet. use GraphQL; my $foo = GraphQL->new(); ... =head1 EXPORT A list of functions that can be exported. You can delete this section if you don't export anything, such as for a purely object-oriented module. =head1 SUBROUTINES/METHODS =head2 function1 =cut sub function1 { } =head2 function2 =cut sub function2 { } =head1 AUTHOR Ed J, C<< >> =head1 BUGS Please report any bugs or feature requests to C, or through the web interface at L. I will be notified, and then you'll automatically be notified of progress on your bug as I make changes. =head1 SUPPORT You can find documentation for this module with the perldoc command. perldoc GraphQL You can also look for information at: =over 4 =item * RT: CPAN's request tracker (report bugs here) L =item * AnnoCPAN: Annotated CPAN documentation L =item * CPAN Ratings L =item * Search CPAN L =back =head1 ACKNOWLEDGEMENTS =head1 LICENSE AND COPYRIGHT Copyright 2017 Ed J. This program is free software; you can redistribute it and/or modify it under the terms of the the Artistic License (2.0). You may obtain a copy of the full license at: L Any use, modification, and distribution of the Standard or Modified Versions is governed by this Artistic License. By using, modifying or distributing the Package, you accept this license. Do not use, modify, or distribute the Package, if you do not accept this license. If your Modified Version has been derived from a Modified Version made by someone other than you, you are nevertheless required to ensure that your Modified Version complies with the requirements of this license. This license does not grant you the right to use any trademark, service mark, tradename, or logo of the Copyright Holder. This license includes the non-exclusive, worldwide, free-of-charge patent license to make, have made, use, offer to sell, sell, import and otherwise transfer the Package with respect to any patent claims licensable by the Copyright Holder that are necessarily infringed by the Package. If you institute patent litigation (including a cross-claim or counterclaim) against any party alleging that the Package constitutes direct or contributory patent infringement, then this Artistic License to you shall terminate on the date that such litigation is filed. Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES. THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. =cut 1; GraphQL-0.53/lib/GraphQL/Type.pm0000644000175000017500000000726614070317276016232 0ustar osboxesosboxespackage GraphQL::Type; use 5.014; use strict; use warnings; use Moo; use GraphQL::MaybeTypeCheck; use Types::Standard qw(InstanceOf Any HashRef Str); # if -all causes objects to be class 'Object'! with 'GraphQL::Role::Listable'; our $VERSION = '0.02'; =head1 NAME GraphQL::Type - GraphQL type object =head1 SYNOPSIS extends qw(GraphQL::Type); =head1 DESCRIPTION Superclass for other GraphQL type classes to inherit from. =head1 ENCODING Those Perl classes each implement a GraphQL type. Each item of GraphQL data has a GraphQL type. Such an item of data can also be represented within Perl. Objects of that Perl class take responsibility for translating between the Perl representation and the "GraphQL representation". A "GraphQL representation" means something JSON-encodeable: an "object" (in Perl terms, a hash), an array (Perl: array-reference), string, number, boolean, or null. See L for generic methods to translate back and forth between these worlds. Code that you provide to do this translation must return things that I be JSON-encoded, not things that I so encoded: this means, among other things, do not surround strings in C<">, and for boolean values, use the mechanism in L: Ctrue> etc. =head1 SUBCLASSES These subclasses implement part of the GraphQL language specification. Objects of these classes implement user-defined types used to implement a GraphQL API. =over =item L =item L =item L =item L =item L =item L =item L - also implements example types such as C =item L =back =head1 ROLES These roles implement part of the GraphQL language specification. They are applied to objects of L classes, either to facilitate type constrants, or as noted below. =over =item L - provides C attribute for an input type =item L - provides C attribute for an output type =item L - abstract type =item L - type has fields =item L - type can be an input =item L - simple type - enum or scalar =item L - can be list-wrapped; provides convenience method =item L - has a C and C, provided by this role =item L - can be null-valued =item L - type can be an output =back =head1 TYPE LIBRARY L - implements various L type constraints, for use in L attributes, and L/L methods and functions. =head1 METHODS =head2 uplift Turn given Perl entity into valid Perl value for this type if possible. =cut method uplift(Any $item) :ReturnType(Any) { $item; } =head2 graphql_to_perl Turn given GraphQL entity into Perl entity. =head2 perl_to_graphql Turn given Perl entity into GraphQL entity. =cut =head2 from_ast($name2type, $ast_node) Class method. C<$name2type> is a hash-ref populated by L. Takes a hash-ref node from an AST made by L. Returns a type object. =head2 to_doc($doc) Returns Schema Definition Language (SDL) document that describes this object. =cut method _from_ast_maptype( HashRef $name2type, HashRef $ast_node, Str $key, ) { return if !$ast_node->{$key}; ($key => sub { [ map { $name2type->{$_} // die "Unknown type '$_'.\n" } @{$ast_node->{$key}} ] }); } __PACKAGE__->meta->make_immutable(); 1; GraphQL-0.53/lib/GraphQL.pm0000644000175000017500000001445414170415173015302 0ustar osboxesosboxespackage GraphQL; use 5.014; use strict; use warnings; =head1 NAME GraphQL - Perl implementation of GraphQL =cut our $VERSION = '0.53'; =begin markdown # PROJECT STATUS | OS | Build status | |:-------:|--------------:| | Linux | [![Build Status](https://travis-ci.org/graphql-perl/graphql-perl.svg?branch=master)](https://travis-ci.org/graphql-perl/graphql-perl) | [![CPAN version](https://badge.fury.io/pl/GraphQL.svg)](https://metacpan.org/pod/GraphQL) [![Coverage Status](https://coveralls.io/repos/github/graphql-perl/graphql-perl/badge.svg?branch=master)](https://coveralls.io/github/graphql-perl/graphql-perl?branch=master) =end markdown =head1 SYNOPSIS use GraphQL::Schema; use GraphQL::Type::Object; use GraphQL::Type::Scalar qw($String); use GraphQL::Execution qw(execute); my $schema = GraphQL::Schema->from_doc(<<'EOF'); type Query { helloWorld: String } EOF post '/graphql' => sub { send_as JSON => execute( $schema, body_parameters->{query}, { helloWorld => 'Hello world!' }, undef, body_parameters->{variables}, body_parameters->{operationName}, undef, ); }; The above is from L. =head1 DESCRIPTION This module is a port of the GraphQL reference implementation, L, to Perl 5. It now supports Promises, allowing asynchronous operation. See L for an example of how to take advantage of this. As of 0.39, supports GraphQL subscriptions. See L for description of how to create GraphQL types. =head2 Introduction to GraphQL GraphQL is a technology that lets clients talk to APIs via a single endpoint, which acts as a single "source of the truth". This means clients do not need to seek the whole picture from several APIs. Additionally, it makes this efficient in network traffic, time, and programming effort: =over =item Network traffic The request asks for exactly what it wants, which it gets, and no more. No wasted traffic. =item Time It gets all the things it needs in one go, including any connected resources, so it does not need to make several requests to fill its information requirement. =item Programming effort With "fragments" that can be attached to user-interface components, keeping track of what information a whole page needs to request can be automated. See L or L for more on this. =back =head2 Basic concepts GraphQL implements a system featuring a L, which features various classes of L, some of which are L. Special objects provide the roots of queries (mandatory), and mutations and subscriptions (both optional). Objects have fields, each of which can be specified to take arguments, and which have a return type. These are effectively the properties and/or methods on the type. If they return an object, then a query can specify subfields of that object, and so on - as alluded to in the "time-saving" point above. For more, see the JavaScript tutorial in L. =head2 Hooking your system up to GraphQL You will need to decide how to model your system in GraphQL terms. This will involve deciding on what L you have, what fields they have, and what arguments and return-types those fields have. Additionally, you will need to design mutations if you want to be able to update/create/delete data. This requires some thought for return types, to ensure you can get all the information you need to proceed to avoid extra round-trips. The easiest way to achieve these things is to make a L subclass, to encapsulate the specifics of your system. See the documentation for further information. Finally, you should consider whether you need "subscriptions". These are designed to hook into WebSockets. Apollo has a L for this. Specifying types and fields is straightforward. See L for how to make resolvers. =head1 DEBUGGING To debug, set environment variable C to a true value. =head1 EXPORT None yet. =head1 SEE ALSO L - produce GraphQL schemas from a L (or in fact any SQL database) L - produce working GraphQL schema from a L L - produce working GraphQL schema from an OpenAPI specification L L L L L L - GraphQL specification L - Tutorial on the JavaScript version, highly recommended. L. =head1 AUTHOR Ed J, C<< >> =head1 BUGS Please report any bugs or feature requests on L. Or, if you prefer email and/or RT: to C, or through the web interface at L. I will be notified, and then you'll automatically be notified of progress on your bug as I make changes. =head1 ACKNOWLEDGEMENTS The creation of this work has been sponsored by Perl Careers: L. Artur Khabibullin C<< >> contributed valuable ports of the JavaScript tests. The creation of the subscriptions functionality in this work has been sponsored by Sanctus.app: L. =head1 LICENSE AND COPYRIGHT Copyright 2017 Ed J. This program is free software; you can redistribute it and/or modify it under the terms of the the Artistic License (2.0). You may obtain a copy of the full license at: L =cut 1; GraphQL-0.53/xt/0000755000175000017500000000000014170415414013321 5ustar osboxesosboxesGraphQL-0.53/xt/pod.t0000644000175000017500000000051113351051036014261 0ustar osboxesosboxesuse strict; use warnings; use Test::More; unless ( $ENV{RELEASE_TESTING} ) { plan( skip_all => "Author tests not required for installation" ); } # Ensure a recent version of Test::Pod my $min_tp = 1.22; eval "use Test::Pod $min_tp"; plan skip_all => "Test::Pod $min_tp required for testing POD" if $@; all_pod_files_ok(); GraphQL-0.53/xt/manifest.t0000644000175000017500000000047213433615450015322 0ustar osboxesosboxesuse strict; use warnings; use Test::More; use ExtUtils::Manifest; unless ( $ENV{RELEASE_TESTING} ) { plan( skip_all => "Author tests not required for installation" ); } plan tests => 2; is_deeply [ ExtUtils::Manifest::manicheck() ], [], 'missing'; is_deeply [ ExtUtils::Manifest::filecheck() ], [], 'extra'; GraphQL-0.53/xt/pod-coverage.t0000644000175000017500000000136614070317276016075 0ustar osboxesosboxesuse strict; use warnings; use Test::More; unless ( $ENV{RELEASE_TESTING} ) { plan( skip_all => "Author tests not required for installation" ); } # Ensure a recent version of Test::Pod::Coverage my $min_tpc = 1.08; eval "use Test::Pod::Coverage $min_tpc"; plan skip_all => "Test::Pod::Coverage $min_tpc required for testing POD coverage" if $@; # Test::Pod::Coverage doesn't require a minimum Pod::Coverage version, # but older versions don't recognize some common documentation styles my $min_pc = 0.18; eval "use Pod::Coverage $min_pc"; plan skip_all => "Pod::Coverage $min_pc required for testing POD coverage" if $@; all_pod_coverage_ok({ trustme => [qr/^(final|got|DEBUG|ReturnType)/], coverage_class => 'Pod::Coverage::CountParents' }); GraphQL-0.53/MANIFEST0000644000175000017500000000400514170415415014017 0ustar osboxesosboxesChanges Dockerfile Dockerfile.prereq graphql.pgx lib/GraphQL.pm lib/GraphQL/AsyncIterator.pm lib/GraphQL/Debug.pm lib/GraphQL/Directive.pm lib/GraphQL/Error.pm lib/GraphQL/Execution.pm lib/GraphQL/Introspection.pm lib/GraphQL/Language/Grammar.pm lib/GraphQL/Language/Parser.pm lib/GraphQL/Language/Receiver.pm lib/GraphQL/MaybeTypeCheck.pm lib/GraphQL/Plugin/Convert.pm lib/GraphQL/Plugin/Convert/Test.pm lib/GraphQL/Plugin/Type.pm lib/GraphQL/Plugin/Type/DateTime.pm lib/GraphQL/PubSub.pm lib/GraphQL/Role/Abstract.pm lib/GraphQL/Role/Composite.pm lib/GraphQL/Role/FieldDeprecation.pm lib/GraphQL/Role/FieldsEither.pm lib/GraphQL/Role/FieldsInput.pm lib/GraphQL/Role/FieldsOutput.pm lib/GraphQL/Role/HashMappable.pm lib/GraphQL/Role/Input.pm lib/GraphQL/Role/Leaf.pm lib/GraphQL/Role/Listable.pm lib/GraphQL/Role/Named.pm lib/GraphQL/Role/Nullable.pm lib/GraphQL/Role/Output.pm lib/GraphQL/Schema.pm lib/GraphQL/Subscription.pm lib/GraphQL/Type.pm lib/GraphQL/Type/Enum.pm lib/GraphQL/Type/InputObject.pm lib/GraphQL/Type/Interface.pm lib/GraphQL/Type/Library.pm lib/GraphQL/Type/List.pm lib/GraphQL/Type/NonNull.pm lib/GraphQL/Type/Object.pm lib/GraphQL/Type/Scalar.pm lib/GraphQL/Type/Union.pm lib/GraphQL/Validation.pm Makefile.PL MANIFEST This list of files README.md t/00-load.t t/00-report-prereqs.t t/directives.t t/execution-abstract.t t/execution-directives.t t/execution-execute.t t/execution-lists.t t/execution-nonnull.t t/execution-resolve.t t/execution-schema.t t/execution-sync.t t/execution-variables.t t/kitchen-sink.graphql t/language-block-string-value.t t/language-lexer.t t/language-parser.t t/language-schema-parser.t t/lib/GQLTest.pm t/perl.t t/schema-kitchen-sink.graphql t/subscribe.t t/type-definition.t t/type-enum.t t/type-interface.t t/type-introspection.t t/type-schema.t t/util-buildschema.t util/benchmark.pl xt/manifest.t xt/pod-coverage.t xt/pod.t META.yml Module YAML meta-data (added by MakeMaker) META.json Module JSON meta-data (added by MakeMaker) GraphQL-0.53/Makefile.PL0000644000175000017500000000471214070317276014652 0ustar osboxesosboxesuse 5.014; use strict; use warnings; use ExtUtils::MakeMaker; WriteMakefile( NAME => 'GraphQL', AUTHOR => q{Ed J }, VERSION_FROM => 'lib/GraphQL.pm', ABSTRACT_FROM => 'lib/GraphQL.pm', LICENSE => 'artistic_2', MIN_PERL_VERSION => '5.014', CONFIGURE_REQUIRES => { 'ExtUtils::MakeMaker' => '6.64', # TEST_REQUIRES }, TEST_REQUIRES => { 'Test::More' => '0.88', # done_testing 'Test::Exception' => '0.42', 'Test::Deep' => '1.127', }, PREREQ_PM => { 'Moo' => '0', 'Attribute::Handlers' => '0', 'Import::Into' => '1.002003', # loads modules 'Type::Tiny' => '0', 'Module::Runtime' => '0', 'Function::Parameters' => '2.001001', 'Return::Type' => '0', 'Pegex' => '0.64', 'MooX::Thunking' => '0.07', # takes care of Thunk in isa 'JSON::MaybeXS' => '1.003009', # is_bool, also . @INC 'JSON::PP' => '2.92', # number detection 'DateTime' => 0, 'DateTime::Format::ISO8601' => 0, 'Devel::StrictMode' => 0, 'curry' => 0, }, dist => { COMPRESS => 'gzip -9f', SUFFIX => 'gz', }, clean => { FILES => 'GraphQL-*' }, META_MERGE => { "meta-spec" => { version => 2 }, dynamic_config => 0, resources => { x_IRC => 'irc://irc.perl.org/#graphql-perl', repository => { type => 'git', url => 'git@github.com:graphql-perl/graphql-perl.git', web => 'https://github.com/graphql-perl/graphql-perl', }, bugtracker => { web => 'https://github.com/graphql-perl/graphql-perl/issues', }, license => [ 'http://dev.perl.org/licenses/' ], }, prereqs => { develop => { requires => { 'Test::Pod::Coverage' => '1.08', 'Test::Pod' => '1.22', 'Pod::Markdown' => 0, }, }, runtime => { suggests => { 'Cpanel::JSON::XS' => '3.0237', 'JSON::XS' => 0, }, }, }, }, ); sub MY::postamble { return '' if !-e '.git'; my $container = 'graphqlperl/graphql'; my $prereq = "${container}-prereq"; <\$\@ containerprereq : \tdocker build -f Dockerfile.prereq -t $prereq . containerprereqpush : \tdocker push $prereq container : \tdocker build -t $container:\$(VERSION) . containerpush : \tdocker push $container:\$(VERSION) EOF } GraphQL-0.53/util/0000755000175000017500000000000014170415414013643 5ustar osboxesosboxesGraphQL-0.53/util/benchmark.pl0000644000175000017500000000403014070317276016135 0ustar osboxesosboxesuse strict; use warnings; use lib './lib'; use Benchmark; use GraphQL::Schema; use GraphQL::Type::Object; use GraphQL::Type::Scalar qw($String $Int $Boolean); use GraphQL::Execution qw(execute); use GraphQL::Language::Parser qw(parse); ########### # Prep schema my ($deep_data, $data); $data = { a => sub {'Apple'}, b => sub {'Banana'}, c => sub {'Cookie'}, d => sub {'Donut'}, e => sub {'Egg'}, f => 'Fish', pic => sub { my $size = shift; return 'Pic of size: ' . ($size || 50); }, deep => sub {$deep_data}, promise => sub { FakePromise->resolve($data) }, }; $deep_data = { a => sub {'Already Been Done'}, b => sub {'Boring'}, c => sub { [ 'Contrived', undef, 'Confusing' ] }, deeper => sub { [ $data, undef, $data ] } }; my ($DeepDataType, $DataType); $DataType = GraphQL::Type::Object->new( name => 'DataType', fields => sub { { a => {type => $String}, b => {type => $String}, c => {type => $String}, d => {type => $String}, e => {type => $String}, f => {type => $String}, pic => { args => {size => {type => $Int}}, type => $String, resolve => sub { my ($obj, $args) = @_; return $obj->{pic}->($args->{size}); } }, deep => {type => $DeepDataType}, promise => {type => $DataType}, } } ); $DeepDataType = GraphQL::Type::Object->new( name => 'DeepDataType', fields => { a => {type => $String}, b => {type => $String}, c => {type => $String->list}, deeper => {type => $DataType->list}, } ); my $schema = GraphQL::Schema->new(query => $DataType); my $doc = <<'EOF'; query Example($size: Int) { a, b, x: c ...c f ...on DataType { pic(size: $size) promise { a } } deep { a b c deeper { a b } } } fragment c on DataType { d e } EOF my $ast = parse($doc); timethis(-5, sub { execute($schema, $ast, $data, undef, {size => 100}, 'Example') });