1 package GraphQL
::Client
;
2 # ABSTRACT: A GraphQL client
7 use Module
::Load
qw(load);
8 use Scalar
::Util
qw(reftype);
11 our $VERSION = '0.601'; # VERSION
13 sub _croak
{ require Carp
; goto &Carp
::croak
}
14 sub _throw
{ GraphQL
::Client
::Error-
>throw(@_) }
23 my ($query, $variables, $operation_name, $options) = @_;
25 if ((reftype
($operation_name) || '') eq 'HASH') {
26 $options = $operation_name;
27 $operation_name = undef;
32 ($variables && %$variables) ? (variables
=> $variables) : (),
33 $operation_name ? (operationName
=> $operation_name) : (),
36 return $self->_handle_result($self->transport->execute($request, $options));
43 my $handle_result = sub {
45 my $resp = $result->{response
};
46 if (my $exception = $result->{error
}) {
47 unshift @{$resp->{errors
}}, {
48 message
=> "$exception",
52 if ($resp->{errors
}) {
53 _throw
$resp->{errors
}[0]{message
}, {
56 details
=> $result->{details
},
64 if (eval { $result->isa('Future') }) {
65 return $result->transform(
68 my $resp = eval { $handle_result->($result) };
70 Future
::Exception-
>throw("$err", $err->{type
}, $err->{response
}, $err->{details
});
77 return $handle_result->($result);
88 $self->{transport_class
};
93 $self->{transport
} //= do {
94 my $class = $self->_autodetermine_transport_class;
96 if ((my $err = $@) || !$class->can('execute')) {
97 $err ||= "Loaded $class, but it doesn't look like a proper transport.\n";
98 warn $err if $ENV{GRAPHQL_CLIENT_DEBUG
};
99 _croak
"Failed to load transport for \"${class}\"";
107 $self->{unpack} //= 0;
113 my $url = $self->url;
114 my ($protocol) = $url =~ /^([^+:]+)/;
119 sub _autodetermine_transport_class
{
122 my $class = $self->transport_class;
123 return _expand_class
($class) if $class;
125 my $protocol = $self->_url_protocol;
126 _croak
'Failed to determine transport from URL' if !$protocol;
128 $class = lc($protocol);
129 $class =~ s/[^a-z]/_/g;
131 return _expand_class
($class);
136 $class = "GraphQL::Client::$class" unless $class =~ s/^\+//;
141 package GraphQL
::Client
::Error
;
146 use overload
'""' => \
&error
, fallback
=> 1;
148 sub new
{ bless {%{$_[2] || {}}, error
=> $_[1] || 'Something happened'}, $_[0] }
150 sub error
{ "$_[0]->{error}" }
151 sub type
{ "$_[0]->{type}" }
155 die $self if ref $self;
170 GraphQL::Client - A GraphQL client
178 my $graphql = GraphQL::Client->new(url => 'http://localhost:4000/graphql');
180 # Example: Hello world!
182 my $response = $graphql->execute('{hello}');
184 # Example: Kitchen sink
188 human(id: $human_id) {
197 my $operation_name = 'GetHuman';
198 my $transport_options = {
200 authorization => 'Bearer s3cr3t',
203 my $response = $graphql->execute($query, $variables, $operation_name, $transport_options);
205 # Example: Asynchronous with Mojo::UserAgent (promisify requires Future::Mojo)
207 my $ua = Mojo::UserAgent->new;
208 my $graphql = GraphQL::Client->new(ua => $ua, url => 'http://localhost:4000/graphql');
210 my $future = $graphql->execute('{hello}');
212 $future->promisify->then(sub {
213 my $response = shift;
219 C<GraphQL::Client> provides a simple way to execute L<GraphQL|https://graphql.org/> queries and
220 mutations on a server.
222 This module is the programmatic interface. There is also a L<"CLI program"|graphql>.
224 GraphQL servers are usually served over HTTP. The provided transport, L<GraphQL::Client::http>, lets
225 you plug in your own user agent, so this client works naturally with L<HTTP::Tiny>,
226 L<Mojo::UserAgent>, and more. You can also use L<HTTP::AnyUA> middleware.
232 The URL of a GraphQL endpoint, e.g. C<"http://myapiserver/graphql">.
236 Whether or not to "unpack" the response, which enables a different style for error-handling.
240 See L</ERROR HANDLING>.
242 =head2 transport_class
244 The package name of a transport.
246 This is optional if the correct transport can be correctly determined from the L</url>.
250 The transport object.
252 By default this is automatically constructed based on L</transport_class> or L</url>.
258 $graphql = GraphQL::Client->new(%attributes);
260 Construct a new client.
264 $response = $graphql->execute($query);
265 $response = $graphql->execute($query, \%variables);
266 $response = $graphql->execute($query, \%variables, $operation_name);
267 $response = $graphql->execute($query, \%variables, $operation_name, \%transport_options);
268 $response = $graphql->execute($query, \%variables, \%transport_options);
270 Execute a request on a GraphQL server, and get a response.
272 By default, the response will either be a hashref with the following structure or a L<Future> that
273 resolves to such a hashref, depending on the transport and how it is configured.
277 field1 => {...}, # or [...]
281 { message => 'some error message blah blah blah' },
286 Note: Setting the L</unpack> attribute affects the response shape.
288 =head1 ERROR HANDLING
290 There are two different styles for handling errors.
292 If L</unpack> is 0 (off, the default), every response -- whether success or failure -- is enveloped
300 where C<data> might be missing or undef if errors occurred (though not necessarily) and C<errors>
301 will be missing if the response completed without error.
303 It is up to you to check for errors in the response, so your code might look like this:
305 my $response = $graphql->execute(...);
306 if (my $errors = $response->{errors}) {
310 my $data = $response->{data};
311 # do something with $data
314 If C<unpack> is 1 (on), then L</execute> will return just the data if there were no errors,
315 otherwise it will throw an exception. So your code would instead look like this:
317 my $data = eval { $graphql->execute(...) };
318 if (my $error = $@) {
319 my $resp = $error->{response};
323 # do something with $data
326 Or if you want to handle errors in a different stack frame, your code is simply this:
328 my $data = $graphql->execute(...);
329 # do something with $data
331 Both styles map to L<Future> responses intuitively. If C<unpack> is 0, the response always resolves
332 to the envelope structure. If C<unpack> is 1, successful responses will resolve to just the data and
333 errors will fail/reject.
341 L<graphql> - CLI program
345 L<GraphQL> - Perl implementation of a GraphQL server
349 L<https://graphql.org/> - GraphQL project website
355 Please report any bugs or feature requests on the bugtracker website
356 L<https://github.com/chazmcgarvey/graphql-client/issues>
358 When submitting a bug or request, please include a test-file or a
359 patch to an existing test-file that illustrates the bug or desired
364 Charles McGarvey <chazmcgarvey@brokenzipper.com>
366 =head1 COPYRIGHT AND LICENSE
368 This software is copyright (c) 2020 by Charles McGarvey.
370 This is free software; you can redistribute it and/or modify it under
371 the same terms as the Perl 5 programming language system itself.