3 # ABSTRACT: Command-line GraphQL client
5 # FATPACK - Do not remove this line.
14 our $VERSION = '0.601'; # VERSION
24 my $format = 'json:pretty';
28 'version' => \$version,
30 'manual|man' => \$manual,
32 'query|mutation=s' => \$query,
33 'variables|vars|V=s' => \$variables,
34 'variable|var|d=s%' => \$variables,
35 'operation-name|n=s' => \$operation_name,
36 'transport|t=s%' => \$transport,
37 'format|f=s' => \$format,
38 'unpack!' => \$unpack,
39 'output|o=s' => \$outfile,
43 print "graphql $VERSION\n";
47 pod2usage(-exitval => 0, -verbose => 99, -sections => [qw(NAME SYNOPSIS OPTIONS)]);
50 pod2usage(-exitval => 0, -verbose => 2);
53 $url = shift if !$url;
54 $query = shift if !$query || $query eq '-';
57 print STDERR "The <URL> or --url option argument is required.\n";
61 $transport = expand_vars($transport);
64 $variables = expand_vars($variables);
67 $variables = JSON::MaybeXS->new->decode($variables);
70 my $client = GraphQL::Client->new(url => $url);
72 eval { $client->transport };
74 warn $err if $ENV{GRAPHQL_CLIENT_DEBUG};
75 print STDERR "Could not construct a transport for URL: $url\n";
76 print STDERR "Is this URL correct?\n";
80 if (!$query || $query eq '-') {
81 print STDERR "Interactive mode engaged! Waiting for a query on <STDIN>...\n"
82 if -t STDIN; ## no critic (InputOutput::ProhibitInteractiveTest)
83 $query = do { local $/; <> };
86 my $resp = $client->execute($query, $variables, $operation_name, $transport);
87 my $err = $resp->{errors};
89 my $data = $unpack ? $resp->{data} : $resp;
92 open(my $out, '>', $outfile) or die "Open $outfile failed: $!";
96 print_data($data, $format);
98 exit($unpack && $err ? 1 : 0);
101 my ($data, $format) = @_;
102 $format = lc($format || 'json:pretty');
103 if ($format eq 'json' || $format eq 'json:pretty') {
104 my %opts = (canonical => 1, utf8 => 1);
105 $opts{pretty} = 1 if $format eq 'json:pretty';
106 print JSON::MaybeXS->new(%opts)->encode($data);
108 elsif ($format eq 'yaml') {
109 eval { require YAML } or die "Missing dependency: YAML\n";
110 print YAML::Dump($data);
112 elsif ($format eq 'csv' || $format eq 'tsv' || $format eq 'table') {
113 my $sep = $format eq 'tsv' ? "\t" : ',';
115 my $unpacked = $data;
116 $unpacked = $data->{data} if !$unpack && !$err;
118 # check the response to see if it can be formatted
121 if (keys %$unpacked == 1) {
122 my ($val) = values %$unpacked;
123 if (ref $val eq 'ARRAY') {
124 my $first = $val->[0];
125 if ($first && ref $first eq 'HASH') {
126 @columns = sort keys %$first;
128 map { [@{$_}{@columns}] } @$val
132 @columns = keys %$unpacked;
133 $rows = [map { [$_] } @$val];
139 if ($format eq 'table') {
140 eval { require Text::Table::Any } or die "Missing dependency: Text::Table::Any\n";
141 my $table = Text::Table::Any::table(
143 rows => [[@columns], @$rows],
144 backend => $ENV{PERL_TEXT_TABLE},
149 eval { require Text::CSV } or die "Missing dependency: Text::CSV\n";
150 my $csv = Text::CSV->new({binary => 1, sep => $sep, eol => $/});
151 $csv->print(*STDOUT, [@columns]);
152 for my $row (@$rows) {
153 $csv->print(*STDOUT, $row);
159 print STDERR sprintf("Error: Response could not be formatted as %s.\n", uc($format));
163 elsif ($format eq 'perl') {
164 eval { require Data::Dumper } or die "Missing dependency: Data::Dumper\n";
165 print Data::Dumper::Dumper($data);
168 print STDERR "Error: Format not supported: $format\n";
178 while (my ($key, $value) = each %$vars) {
180 my @segments = split(/\./, $key);
181 for my $segment (reverse @segments) {
183 if ($segment =~ /^(\d+)$/) {
185 $var->[$segment] = $saved;
189 $var->{$segment} = $saved;
192 %out = (%out, %$var);
199 eval { require Pod::Usage };
201 my $ref = $VERSION eq '999.999' ? 'master' : "v$VERSION";
202 my $exit = (@_ == 1 && $_[0] =~ /^\d+$/ && $_[0]) //
203 (@_ % 2 == 0 && {@_}->{'-exitval'}) // 2;
205 Online documentation is available at:
207 https://github.com/chazmcgarvey/graphql-client/blob/$ref/README.md
209 Tip: To enable inline documentation, install the Pod::Usage module.
215 goto &Pod::Usage::pod2usage;
227 graphql - Command-line GraphQL client
235 graphql <URL> <QUERY> [ [--variables JSON] | [--variable KEY=VALUE]... ]
236 [--operation-name NAME] [--transport KEY=VALUE]...
237 [--[no-]unpack] [--format json|json:pretty|yaml|perl|csv|tsv|table]
240 graphql --version|--help|--manual
244 C<graphql> is a command-line program for executing queries and mutations on
245 a L<GraphQL|https://graphql.org/> server.
249 There are several ways to install F<graphql> to your system.
253 You can install F<graphql> using L<cpanm>:
255 cpanm GraphQL::Client
259 You can also choose to download F<graphql> as a self-contained executable:
261 curl -OL https://raw.githubusercontent.com/chazmcgarvey/graphql-client/solo/graphql
264 To hack on the code, clone the repo instead:
266 git clone https://github.com/chazmcgarvey/graphql-client.git
268 make bootstrap # installs dependencies; requires cpanm
274 The URL of the GraphQL server endpoint.
276 If no C<--url> option is given, the first argument is assumed to be the URL.
278 This option is required.
282 =head2 C<--query STR>
284 The query or mutation to execute.
286 If no C<--query> option is given, the next argument (after URL) is assumed to be the query.
288 If the value is "-" (which is the default), the query will be read from C<STDIN>.
290 See: L<https://graphql.org/learn/queries/>
294 =head2 C<--variables JSON>
296 Provide the variables as a JSON object.
298 Aliases: C<--vars>, C<-V>
300 =head2 C<--variable KEY=VALUE>
302 An alternative way to provide variables. Repeat this option to provide multiple variables.
304 If used in combination with L</"--variables JSON">, this option is silently ignored.
306 See: L<https://graphql.org/learn/queries/#variables>
308 Aliases: C<--var>, C<-d>
310 =head2 C<--operation-name NAME>
312 Inform the server which query/mutation to execute.
316 =head2 C<--output FILE>
318 Write the response to a file instead of STDOUT.
322 =head2 C<--transport KEY=VALUE>
324 Key-value pairs for configuring the transport (usually HTTP).
328 =head2 C<--format STR>
330 Specify the output format to use. See L</FORMAT>.
338 By default, the response structure is printed as-is from the server, and the program exits 0.
340 When unpack mode is enabled, if the response completes with no errors, only the data section of
341 the response is printed and the program exits 0. If the response has errors, the whole response
342 structure is printed as-is and the program exits 1.
348 The argument for L</"--format STR"> can be one of:
354 C<csv> - Comma-separated values (requires L<Text::CSV>)
358 C<json:pretty> - Human-readable JSON (default)
366 C<perl> - Perl code (requires L<Data::Dumper>)
370 C<table> - Table (requires L<Text::Table::Any>)
374 C<tsv> - Tab-separated values (requires L<Text::CSV>)
378 C<yaml> - YAML (requires L<YAML>)
382 The C<csv>, C<tsv>, and C<table> formats will only work if the response has a particular shape:
407 If the response cannot be formatted, the default format will be used instead, an error message will
408 be printed to STDERR, and the program will exit 3.
410 Table formatting can be done by one of several different modules, each with its own features and
411 bugs. The default module is L<Text::Table::Tiny>, but this can be overridden using the
412 C<PERL_TEXT_TABLE> environment variable if desired, like this:
414 PERL_TEXT_TABLE=Text::Table::HTML graphql ... -f table
416 The list of supported modules is at L<Text::Table::Any/@BACKENDS>.
420 Different ways to provide the query/mutation to execute:
422 graphql http://myserver/graphql {hello}
424 echo {hello} | graphql http://myserver/graphql
426 graphql http://myserver/graphql <<END
430 graphql http://myserver/graphql
431 Interactive mode engaged! Waiting for a query on <STDIN>...
435 Execute a query with variables:
437 graphql http://myserver/graphql <<END --var episode=JEDI
438 > query HeroNameAndFriends($episode: Episode) {
439 > hero(episode: $episode) {
448 graphql http://myserver/graphql --vars '{"episode":"JEDI"}'
450 Configure the transport:
452 graphql http://myserver/graphql {hello} -t headers.authorization='Basic s3cr3t'
454 This example shows the effect of L</--unpack>:
456 graphql http://myserver/graphql {hello}
461 "hello" : "Hello world!"
465 graphql http://myserver/graphql {hello} --unpack
469 "hello" : "Hello world!"
474 Some environment variables affect the way C<graphql> behaves:
480 C<GRAPHQL_CLIENT_DEBUG> - Set to 1 to print diagnostic messages to STDERR.
484 C<GRAPHQL_CLIENT_HTTP_USER_AGENT> - Set the HTTP user agent string.
488 C<PERL_TEXT_TABLE> - Set table format backend; see L</FORMAT>.
494 Here is a consolidated summary of what exit statuses mean:
504 C<1> - Client or server errors
508 C<2> - Option usage is wrong
512 C<3> - Could not format the response as requested
518 Please report any bugs or feature requests on the bugtracker website
519 L<https://github.com/chazmcgarvey/graphql-client/issues>
521 When submitting a bug or request, please include a test-file or a
522 patch to an existing test-file that illustrates the bug or desired
527 Charles McGarvey <chazmcgarvey@brokenzipper.com>
529 =head1 COPYRIGHT AND LICENSE
531 This software is copyright (c) 2020 by Charles McGarvey.
533 This is free software; you can redistribute it and/or modify it under
534 the same terms as the Perl 5 programming language system itself.