3 # ABSTRACT: Command-line GraphQL client
5 # FATPACK - Do not remove this line.
14 our $VERSION = '999.999'; # 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;
221 graphql <URL> <QUERY> [ [--variables JSON] | [--variable KEY=VALUE]... ]
222 [--operation-name NAME] [--transport KEY=VALUE]...
223 [--[no-]unpack] [--format json|json:pretty|yaml|perl|csv|tsv|table]
226 graphql --version|--help|--manual
230 C<graphql> is a command-line program for executing queries and mutations on
231 a L<GraphQL|https://graphql.org/> server.
235 There are several ways to install F<graphql> to your system.
239 You can install F<graphql> using L<cpanm>:
241 cpanm GraphQL::Client
245 You can also choose to download F<graphql> as a self-contained executable:
247 curl -OL https://raw.githubusercontent.com/chazmcgarvey/graphql-client/solo/graphql
250 To hack on the code, clone the repo instead:
252 git clone https://github.com/chazmcgarvey/graphql-client.git
254 make bootstrap # installs dependencies; requires cpanm
260 The URL of the GraphQL server endpoint.
262 If no C<--url> option is given, the first argument is assumed to be the URL.
264 This option is required.
268 =head2 C<--query STR>
270 The query or mutation to execute.
272 If no C<--query> option is given, the next argument (after URL) is assumed to be the query.
274 If the value is "-" (which is the default), the query will be read from C<STDIN>.
276 See: L<https://graphql.org/learn/queries/>
280 =head2 C<--variables JSON>
282 Provide the variables as a JSON object.
284 Aliases: C<--vars>, C<-V>
286 =head2 C<--variable KEY=VALUE>
288 An alternative way to provide variables. Repeat this option to provide multiple variables.
290 If used in combination with L</"--variables JSON">, this option is silently ignored.
292 See: L<https://graphql.org/learn/queries/#variables>
294 Aliases: C<--var>, C<-d>
296 =head2 C<--operation-name NAME>
298 Inform the server which query/mutation to execute.
302 =head2 C<--output FILE>
304 Write the response to a file instead of STDOUT.
308 =head2 C<--transport KEY=VALUE>
310 Key-value pairs for configuring the transport (usually HTTP).
314 =head2 C<--format STR>
316 Specify the output format to use. See L</FORMAT>.
324 By default, the response structure is printed as-is from the server, and the program exits 0.
326 When unpack mode is enabled, if the response completes with no errors, only the data section of
327 the response is printed and the program exits 0. If the response has errors, the whole response
328 structure is printed as-is and the program exits 1.
334 The C<--format> argument can be one of:
337 * C<csv> - Comma-separated values (requires L<Text::CSV>)
338 * C<json:pretty> - Human-readable JSON (default)
340 * C<perl> - Perl code (requires L<Data::Dumper>)
341 * C<table> - Table (requires L<Text::Table::Any>)
342 * C<tsv> - Tab-separated values (requires L<Text::CSV>)
343 * C<yaml> - YAML (requires L<YAML>)
345 The C<csv>, C<tsv>, and C<table> formats will only work if the response has a particular shape:
370 If the response cannot be formatted, the default format will be used instead, an error message will
371 be printed to STDERR, and the program will exit 3.
373 Table formatting can be done by one of several different modules, each with its own features and
374 bugs. The default module is L<Text::Table::Tiny>, but this can be overridden using the
375 C<PERL_TEXT_TABLE> environment variable if desired, like this:
377 PERL_TEXT_TABLE=Text::Table::HTML graphql ... -f table
379 The list of supported modules is at L<Text::Table::Any/@BACKENDS>.
383 Different ways to provide the query/mutation to execute:
385 graphql http://myserver/graphql {hello}
387 echo {hello} | graphql http://myserver/graphql
389 graphql http://myserver/graphql <<END
393 graphql http://myserver/graphql
394 Interactive mode engaged! Waiting for a query on <STDIN>...
398 Execute a query with variables:
400 graphql http://myserver/graphql <<END --var episode=JEDI
401 > query HeroNameAndFriends($episode: Episode) {
402 > hero(episode: $episode) {
411 graphql http://myserver/graphql --vars '{"episode":"JEDI"}'
413 Configure the transport:
415 graphql http://myserver/graphql {hello} -t headers.authorization='Basic s3cr3t'
417 This example shows the effect of L</--unpack>:
419 graphql http://myserver/graphql {hello}
424 "hello" : "Hello world!"
428 graphql http://myserver/graphql {hello} --unpack
432 "hello" : "Hello world!"
437 Some environment variables affect the way C<graphql> behaves:
440 * C<GRAPHQL_CLIENT_DEBUG> - Set to 1 to print diagnostic messages to STDERR.
441 * C<GRAPHQL_CLIENT_HTTP_USER_AGENT> - Set the HTTP user agent string.
442 * C<PERL_TEXT_TABLE> - Set table format backend; see L</FORMAT>.
446 Here is a consolidated summary of what exit statuses mean:
450 * C<1> - Client or server errors
451 * C<2> - Option usage is wrong
452 * C<3> - Could not format the response as requested