From c2383aed8b34dafaef89dadcd8cdd3307b38f809 Mon Sep 17 00:00:00 2001 From: Charles McGarvey Date: Tue, 17 Mar 2020 18:29:08 -0600 Subject: [PATCH] Release GraphQL-Client 0.602 * Add support for GRAPHQL_CLIENT_OPTIONS environment variable. * Fix a slew of CLI option problems. --- Changes | 5 +- README.md | 301 +++++++++++++++++++++++++++++++++--------------------- 2 files changed, 191 insertions(+), 115 deletions(-) diff --git a/Changes b/Changes index 935c49a..718fb8c 100644 --- a/Changes +++ b/Changes @@ -2,9 +2,12 @@ Revision history for GraphQL-Client. {{$NEXT}} +0.602 2020-03-17 18:27:46-06:00 MST7MDT + * Add support for GRAPHQL_CLIENT_OPTIONS environment variable. + * Fix a slew of CLI option problems. + 0.601 2020-03-15 20:38:38-06:00 MST7MDT * Rename "class" attribute to "transport_class". - * Minor pod fixups. 0.600 2020-03-15 18:08:57-06:00 MST7MDT * Initial public release. diff --git a/README.md b/README.md index 835afee..d188b99 100644 --- a/README.md +++ b/README.md @@ -1,175 +1,248 @@ # NAME -GraphQL::Client - A GraphQL client +graphql - Command-line GraphQL client # VERSION -version 0.601 +version 0.602 # SYNOPSIS - my $graphql = GraphQL::Client->new(url => 'http://localhost:4000/graphql'); + graphql [ [--variables JSON] | [--variable KEY=VALUE]... ] + [--operation-name NAME] [--transport KEY=VALUE]... + [--[no-]unpack] [--format json|json:pretty|yaml|perl|csv|tsv|table] + [--output FILE] - # Example: Hello world! + graphql --version|--help|--manual - my $response = $graphql->execute('{hello}'); +# DESCRIPTION - # Example: Kitchen sink +`graphql` is a command-line program for executing queries and mutations on +a [GraphQL](https://graphql.org/) server. - my $query = q[ - query GetHuman { - human(id: $human_id) { - name - height - } - } - ]; - my $variables = { - human_id => 1000, - }; - my $operation_name = 'GetHuman'; - my $transport_options = { - headers => { - authorization => 'Bearer s3cr3t', - }, - }; - my $response = $graphql->execute($query, $variables, $operation_name, $transport_options); - - # Example: Asynchronous with Mojo::UserAgent (promisify requires Future::Mojo) - - my $ua = Mojo::UserAgent->new; - my $graphql = GraphQL::Client->new(ua => $ua, url => 'http://localhost:4000/graphql'); - - my $future = $graphql->execute('{hello}'); - - $future->promisify->then(sub { - my $response = shift; - ... - }); +# INSTALL -# DESCRIPTION +There are several ways to install `graphql` to your system. -`GraphQL::Client` provides a simple way to execute [GraphQL](https://graphql.org/) queries and -mutations on a server. +## from CPAN -This module is the programmatic interface. There is also a ["CLI program"](https://metacpan.org/pod/graphql). +You can install `graphql` using [cpanm](https://metacpan.org/pod/cpanm): -GraphQL servers are usually served over HTTP. The provided transport, [GraphQL::Client::http](https://metacpan.org/pod/GraphQL%3A%3AClient%3A%3Ahttp), lets -you plug in your own user agent, so this client works naturally with [HTTP::Tiny](https://metacpan.org/pod/HTTP%3A%3ATiny), -[Mojo::UserAgent](https://metacpan.org/pod/Mojo%3A%3AUserAgent), and more. You can also use [HTTP::AnyUA](https://metacpan.org/pod/HTTP%3A%3AAnyUA) middleware. + cpanm GraphQL::Client -# ATTRIBUTES +## from GitHub -## url +You can also choose to download `graphql` as a self-contained executable: -The URL of a GraphQL endpoint, e.g. `"http://myapiserver/graphql"`. + curl -OL https://raw.githubusercontent.com/chazmcgarvey/graphql-client/solo/graphql + chmod +x graphql -## unpack +To hack on the code, clone the repo instead: -Whether or not to "unpack" the response, which enables a different style for error-handling. + git clone https://github.com/chazmcgarvey/graphql-client.git + cd graphql-client + make bootstrap # installs dependencies; requires cpanm -Default is 0. +# OPTIONS -See ["ERROR HANDLING"](#error-handling). +## `--url URL` -## transport\_class +The URL of the GraphQL server endpoint. -The package name of a transport. +If no `--url` option is given, the first argument is assumed to be the URL. -This is optional if the correct transport can be correctly determined from the ["url"](#url). +This option is required. -## transport +Alias: `-u` -The transport object. +## `--query STR` -By default this is automatically constructed based on ["transport\_class"](#transport_class) or ["url"](#url). +The query or mutation to execute. -# METHODS +If no `--query` option is given, the next argument (after URL) is assumed to be the query. -## new +If the value is "-" (which is the default), the query will be read from `STDIN`. - $graphql = GraphQL::Client->new(%attributes); +See: [https://graphql.org/learn/queries/](https://graphql.org/learn/queries/) -Construct a new client. +Alias: `--mutation` -## execute +## `--variables JSON` - $response = $graphql->execute($query); - $response = $graphql->execute($query, \%variables); - $response = $graphql->execute($query, \%variables, $operation_name); - $response = $graphql->execute($query, \%variables, $operation_name, \%transport_options); - $response = $graphql->execute($query, \%variables, \%transport_options); +Provide the variables as a JSON object. -Execute a request on a GraphQL server, and get a response. +Aliases: `--vars`, `-V` -By default, the response will either be a hashref with the following structure or a [Future](https://metacpan.org/pod/Future) that -resolves to such a hashref, depending on the transport and how it is configured. +## `--variable KEY=VALUE` - { - data => { - field1 => {...}, # or [...] - ... - }, - errors => [ - { message => 'some error message blah blah blah' }, - ... - ], - } +An alternative way to provide variables one at a time. This option can be repeated to provide +multiple variables. -Note: Setting the ["unpack"](#unpack) attribute affects the response shape. +If used in combination with ["--variables JSON"](#variables-json), this option is silently ignored. -# ERROR HANDLING +See: [https://graphql.org/learn/queries/#variables](https://graphql.org/learn/queries/#variables) -There are two different styles for handling errors. +Aliases: `--var`, `-d` -If ["unpack"](#unpack) is 0 (off, the default), every response -- whether success or failure -- is enveloped -like this: +## `--operation-name NAME` - { - data => {...}, - errors => [...], - } +Inform the server which query/mutation to execute. + +Alias: `-n` + +## `--output FILE` + +Write the response to a file instead of STDOUT. + +Alias: `-o` + +## `--transport KEY=VALUE` + +Key-value pairs for configuring the transport (usually HTTP). + +Alias: `-t` -where `data` might be missing or undef if errors occurred (though not necessarily) and `errors` -will be missing if the response completed without error. +## `--format STR` -It is up to you to check for errors in the response, so your code might look like this: +Specify the output format to use. See ["FORMAT"](#format). - my $response = $graphql->execute(...); - if (my $errors = $response->{errors}) { - # handle $errors +Alias: `-f` + +## `--unpack` + +Enables unpack mode. + +By default, the response structure is printed as-is from the server, and the program exits 0. + +When unpack mode is enabled, if the response completes with no errors, only the data section of +the response is printed and the program exits 0. If the response has errors, the whole response +structure is printed as-is and the program exits 1. + +See ["EXAMPLES"](#examples). + +# FORMAT + +The argument for ["--format STR"](#format-str) can be one of: + +- `csv` - Comma-separated values (requires [Text::CSV](https://metacpan.org/pod/Text%3A%3ACSV)) +- `json:pretty` - Human-readable JSON (default) +- `json` - JSON +- `perl` - Perl code (requires [Data::Dumper](https://metacpan.org/pod/Data%3A%3ADumper)) +- `table` - Table (requires [Text::Table::Any](https://metacpan.org/pod/Text%3A%3ATable%3A%3AAny)) +- `tsv` - Tab-separated values (requires [Text::CSV](https://metacpan.org/pod/Text%3A%3ACSV)) +- `yaml` - YAML (requires [YAML](https://metacpan.org/pod/YAML)) + +The `csv`, `tsv`, and `table` formats will only work if the response has a particular shape: + + { + "data" : { + "onefield" : [ + { + "key" : "value", + ... + }, + ... + ] + } } - else { - my $data = $response->{data}; - # do something with $data + +or + + { + "data" : { + "onefield" : [ + "value", + ... + ] + } } -If `unpack` is 1 (on), then ["execute"](#execute) will return just the data if there were no errors, -otherwise it will throw an exception. So your code would instead look like this: +If the response cannot be formatted, the default format will be used instead, an error message will +be printed to STDERR, and the program will exit 3. + +Table formatting can be done by one of several different modules, each with its own features and +bugs. The default module is [Text::Table::Tiny](https://metacpan.org/pod/Text%3A%3ATable%3A%3ATiny), but this can be overridden using the +`PERL_TEXT_TABLE` environment variable if desired, like this: + + PERL_TEXT_TABLE=Text::Table::HTML graphql ... -f table + +The list of supported modules is at ["@BACKENDS" in Text::Table::Any](https://metacpan.org/pod/Text%3A%3ATable%3A%3AAny#BACKENDS). + +# EXAMPLES + +Different ways to provide the query/mutation to execute: + + graphql http://myserver/graphql {hello} + + echo {hello} | graphql http://myserver/graphql + + graphql http://myserver/graphql < {hello} + > END + + graphql http://myserver/graphql + Interactive mode engaged! Waiting for a query on ... + {hello} + ^D + +Execute a query with variables: + + graphql http://myserver/graphql < query HeroNameAndFriends($episode: Episode) { + > hero(episode: $episode) { + > name + > friends { + > name + > } + > } + > } + > END - my $data = eval { $graphql->execute(...) }; - if (my $error = $@) { - my $resp = $error->{response}; - # handle errors + graphql http://myserver/graphql --vars '{"episode":"JEDI"}' + +Configure the transport: + + graphql http://myserver/graphql {hello} -t headers.authorization='Basic s3cr3t' + +This example shows the effect of ["--unpack"](#unpack): + + graphql http://myserver/graphql {hello} + + # Output: + { + "data" : { + "hello" : "Hello world!" + } } - else { - # do something with $data + + graphql http://myserver/graphql {hello} --unpack + + # Output: + { + "hello" : "Hello world!" } -Or if you want to handle errors in a different stack frame, your code is simply this: +# ENVIRONMENT + +Some environment variables affect the way `graphql` behaves: + +- `GRAPHQL_CLIENT_DEBUG` - Set to 1 to print diagnostic messages to STDERR. +- `GRAPHQL_CLIENT_HTTP_USER_AGENT` - Set the HTTP user agent string. +- `GRAPHQL_CLIENT_OPTIONS` - Set the default set of options. +- `PERL_TEXT_TABLE` - Set table format backend; see ["FORMAT"](#format). + +# EXIT STATUS - my $data = $graphql->execute(...); - # do something with $data +Here is a consolidated summary of what exit statuses mean: -Both styles map to [Future](https://metacpan.org/pod/Future) responses intuitively. If `unpack` is 0, the response always resolves -to the envelope structure. If `unpack` is 1, successful responses will resolve to just the data and -errors will fail/reject. +- `0` - Success +- `1` - Client or server errors +- `2` - Option usage is wrong +- `3` - Could not format the response as requested # SEE ALSO -- [graphql](https://metacpan.org/pod/graphql) - CLI program -- [GraphQL](https://metacpan.org/pod/GraphQL) - Perl implementation of a GraphQL server -- [https://graphql.org/](https://graphql.org/) - GraphQL project website +- [GraphQL::Client](https://metacpan.org/pod/GraphQL%3A%3AClient) - Programmatic interface # BUGS -- 2.43.0