3 # ABSTRACT: Command-line GraphQL client
5 # FATPACK - Do not remove this line.
14 our $VERSION = '999.999'; # VERSION
21 my $format = 'json:pretty';
29 'query|mutation=s' => \$query,
30 'variables|vars|V=s' => \$variables,
31 'variable|var|d=s%' => \$variables,
32 'operation-name=s' => \$operation_name,
33 'transport|t=s%' => \$transport,
34 'format|f=s' => \$format,
35 'unpack!' => \$unpack,
36 'output|o=s' => \$outfile,
37 'version' => \$version,
39 'manual|man' => \$manual,
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 '-';
56 $transport = expand_vars($transport);
59 $variables = expand_vars($variables);
62 $variables = JSON::MaybeXS->new->decode($variables);
65 my $client = GraphQL::Client->new(url => $url);
66 $client->transport; # just make sure we can load the transport
68 if (!$query || $query eq '-') {
69 print STDERR "Interactive mode engaged! Waiting for a query on <STDIN>...\n"
70 if -t STDIN; ## no critic (InputOutput::ProhibitInteractiveTest)
71 $query = do { local $/; <> };
74 my $resp = $client->request($query, $variables, $operation_name, $transport);
75 my $err = $resp->{errors};
77 my $data = $unpack ? $resp->{data} : $resp;
80 open(my $out, '>', $outfile) or die "Open $outfile failed: $!";
84 print_data($data, $format);
86 exit($unpack && $err ? 1 : 0);
89 my ($data, $format) = @_;
90 $format = lc($format || 'json:pretty');
91 if ($format eq 'json' || $format eq 'json:pretty') {
92 my %opts = (canonical => 1, utf8 => 1);
93 $opts{pretty} = 1 if $format eq 'json:pretty';
94 print JSON::MaybeXS->new(%opts)->encode($data);
96 elsif ($format eq 'yaml') {
97 eval { require YAML } or die "Missing dependency: YAML\n";
98 print YAML::Dump($data);
100 elsif ($format eq 'csv' || $format eq 'tsv' || $format eq 'table') {
101 my $sep = $format eq 'tsv' ? "\t" : ',';
103 my $unpacked = $data;
104 $unpacked = $data->{data} if !$unpack && !$err;
106 # check the response to see if it can be formatted
109 if (keys %$unpacked == 1) {
110 my ($val) = values %$unpacked;
111 if (ref $val eq 'ARRAY') {
112 my $first = $val->[0];
113 if ($first && ref $first eq 'HASH') {
114 @columns = sort keys %$first;
116 map { [@{$_}{@columns}] } @$val
120 @columns = keys %$unpacked;
121 $rows = [map { [$_] } @$val];
127 if ($format eq 'table') {
128 eval { require Text::Table::Any } or die "Missing dependency: Text::Table::Any\n";
129 my $table = Text::Table::Any::table(
131 rows => [[@columns], @$rows],
132 backend => $ENV{PERL_TEXT_TABLE},
137 eval { require Text::CSV } or die "Missing dependency: Text::CSV\n";
138 my $csv = Text::CSV->new({binary => 1, sep => $sep, eol => $/});
139 $csv->print(*STDOUT, [@columns]);
140 for my $row (@$rows) {
141 $csv->print(*STDOUT, $row);
147 print STDERR sprintf("Error: Response could not be formatted as %s.\n", uc($format));
151 elsif ($format eq 'perl') {
152 eval { require Data::Dumper } or die "Missing dependency: Data::Dumper\n";
153 print Data::Dumper::Dumper($data);
156 print STDERR "Error: Format not supported: $format\n";
166 while (my ($key, $value) = each %$vars) {
168 my @segments = split(/\./, $key);
169 for my $segment (reverse @segments) {
171 if ($segment =~ /^(\d+)$/) {
173 $var->[$segment] = $saved;
177 $var->{$segment} = $saved;
180 %out = (%out, %$var);
187 eval { require Pod::Usage };
189 my $ref = $VERSION eq '999.999' ? 'master' : "v$VERSION";
190 my $exit = (@_ == 1 && $_[0] =~ /^\d+$/ && $_[0]) //
191 (@_ % 2 == 0 && {@_}->{'-exitval'}) // 2;
193 Online documentation is available at:
195 https://github.com/chazmcgarvey/graphql-client/blob/$ref/README.md
197 Tip: To enable inline documentation, install the Pod::Usage module.
203 goto &Pod::Usage::pod2usage;
209 graphql <URL> <QUERY> [--var key=value]... [--transport key=value]...
210 [--[no-]unpack] [--format json|json:pretty|yaml|csv|tsv]
214 C<graphql> is a command-line program for executing queries and mutations on
215 a L<GraphQL|https://graphql.org/> server.
219 There are several ways to install F<graphql> to your system.
223 You can install F<graphql> using L<cpanm>:
225 cpanm GraphQL::Client
229 You can also choose to download F<graphql> as a self-contained executable:
231 curl -OL https://raw.githubusercontent.com/chazmcgarvey/graphql-client/solo/graphql
234 To hack on the code, clone the repo instead:
236 git clone https://github.com/chazmcgarvey/graphql-client.git
238 make bootstrap # installs dependencies; requires cpanm
244 The URL of the GraphQL server endpoint.
246 If no C<--url> option is given, the first argument is assumed to be the URL.
252 The query or mutation to execute.
254 If no C<--query> option is given, the first argument (after URL) is assumed to be the query.
256 If the value is C<-> (which is the default), the query will be read from C<STDIN>.
258 See: L<https://graphql.org/learn/queries/>
262 =head2 --variables JSON
264 Provide the variables as a JSON object.
266 Aliases: C<--vars>, C<-V>
268 =head2 --variable KEY=VALUE
270 An alternative way to provide variables individually. Repeat this option to provide multiple
273 If used in combination with L</"--variables JSON">, this option is silently ignored.
275 See: L<https://graphql.org/learn/queries/#variables>
277 Aliases: C<--var>, C<-d>
279 =head2 --transport KEY=VALUE
281 Key-value pairs for configuring the transport (usually HTTP).
287 Specify the output format to use. See L</FORMAT>.
293 Enables C<unpack> mode.
295 By default, the response structure is printed as-is from the server, and the program exits 0.
297 When C<unpack> mode is enabled, if the response completes with no errors, only the data section of
298 the response is printed and the program exits 0. If the response has errors, the whole response
299 structure is printed as-is and the program exits 1.
305 The C<--format> argument can be one of:
308 * C<csv> - Comma-separated values (requires L<Text::CSV>)
309 * C<json:pretty> - Pretty JSON (default)
311 * C<perl> - Perl code (requires L<Data::Dumper>)
312 * C<table> - Table (requires L<Text::Table::Any>)
313 * C<tsv> - Tab-separated values (requires L<Text::CSV>)
314 * C<yaml> - YAML (requires L<YAML>)
316 The C<csv>, C<tsv>, and C<table> formats will only work if the response has a particular shape:
341 If the response cannot be formatted, the default format will be used instead, an error message will
342 be printed to STDERR, and the program will exit 3.
344 Table formatting can be done by one of several different modules, each with its own features and
345 bugs. The default module is L<Text::Table::Tiny>, but this can be overridden using the
346 C<PERL_TEXT_TABLE> environment variable if desired, like this:
348 PERL_TEXT_TABLE=Text::Table::HTML graphql ... -f table
350 The list of supported modules is at L<Text::Table::Any/@BACKENDS>.
354 Different ways to provide the query/mutation to execute:
356 graphql http://myserver/graphql {hello}
358 echo {hello} | graphql http://myserver/graphql
360 graphql http://myserver/graphql <<END
364 graphql http://myserver/graphql
365 Interactive mode engaged! Waiting for a query on <STDIN>...
369 Execute a query with variables:
371 graphql http://myserver/graphql <<END --var episode=JEDI
372 > query HeroNameAndFriends($episode: Episode) {
373 > hero(episode: $episode) {
382 Configure the transport:
384 graphql http://myserver/graphql {hello} -t headers.authorization='Basic s3cr3t'
386 This example shows the effect of L<--unpack>:
388 graphql http://myserver/graphql {hello}
393 "hello" : "Hello world!"
397 graphql http://myserver/graphql {hello} --unpack
401 "hello" : "Hello world!"
406 Some environment variables affect the way C<graphql> behaves:
409 * C<GRAPHQL_CLIENT_DEBUG> - Set to 1 to print diagnostic messages to STDERR.
410 * C<PERL_TEXT_TABLE> - Set table format backend; see L</FORMAT>.