File Coverage

blib/lib/Plack/App/GraphQL/UITemplate.pm
Criterion Covered Total %
statement 6 25 24.0
branch 0 2 0.0
condition n/a
subroutine 2 8 25.0
pod 0 4 0.0
total 8 39 20.5


line stmt bran cond sub pod time code
1             package Plack::App::GraphQL::UITemplate;
2              
3 1     1   3928 use Moo;
  1         3  
  1         8  
4 1     1   386 use Plack::Util;
  1         2  
  1         542  
5              
6             has tt => (
7             is => 'ro',
8             required => 1,
9             builder => '_build_tt',
10             handles => {
11             tt_process => 'process',
12             }
13             );
14              
15             sub _build_tt {
16 0     0     return Plack::Util::load_class('Template::Tiny')->new();
17             }
18              
19             has template => (
20             is => 'ro',
21             required => 1,
22             builder => '_build_template',
23             );
24              
25             sub _build_template {
26 0     0     $/ = undef;
27 0           return my $data = <DATA>;
28             }
29              
30             has json_encoder => (
31             is => 'ro',
32             required => 1,
33             handles => {
34             json_encode => 'encode',
35             },
36             );
37              
38             has graphiql_version => (
39             is => 'ro',
40             required => 1,
41             default => 'latest',
42             );
43              
44             sub safe_serialize {
45 0     0 0   my ($self, $data) = @_;
46 0 0         if($data) {
47 0           my $json = $self->json_encode($data);
48 0           $json =~ s#/#\\/#g;
49 0           return $json;
50             } else {
51 0           return 'undefined';
52             }
53             }
54              
55             sub process {
56 0     0 0   my ($self, $req) = @_;
57 0           my $query = $req->query_parameters;
58 0           my %args = $self->args_from_query($query);
59 0           return my $body = $self->process_args(%args);
60             }
61              
62             sub args_from_query {
63 0     0 0   my ($self, $query) = @_;
64             return my %args = (
65             graphiql_version => $self->graphiql_version,
66             queryString => $self->safe_serialize( $query->{'query'} ),
67             operationName => $self->safe_serialize( $query->{'operationName'} ),
68             resultString => $self->safe_serialize( $query->{'result'} ),
69 0           variablesString => $self->safe_serialize( $query->{'variables'} ),
70             );
71             }
72              
73             sub process_args {
74 0     0 0   my ($self, %args) = @_;
75 0           my $input = $self->template;
76 0           $self->tt_process(\$input, \%args, \my $output);
77 0           return $output;
78             }
79              
80             1;
81              
82             =head1 NAME
83            
84             Plack::App::GraphQL::UITemplate - Template and processing for the GraphQL UI
85              
86             =head1 SYNOPSIS
87            
88             There's nothing really for end users here. Its just refactored into its own
89             package for code organization purposes.
90              
91             =head1 DESCRIPTION
92              
93             This is a package used to prepare and return an HTML response when you have the
94             'ui' flag enabled (probably for development) and the client requests an HTML
95             response. This is based on L<https://github.com/graphql/graphiql>
96              
97             Feel free to make your own improved development / query interface and put it on
98             CPAN!
99              
100             =head1 AUTHOR
101            
102             John Napiorkowski
103              
104             =head1 SEE ALSO
105            
106             L<Plack::App::GraphQL>
107            
108             =cut
109              
110             __DATA__
111              
112             <!DOCTYPE html>
113             <html>
114             <head>
115             <meta charset="utf-8" />
116             <title>GraphiQL</title>
117             <meta name="robots" content="noindex" />
118             <style>
119             html, body {
120             height: 100%;
121             margin: 0;
122             overflow: hidden;
123             width: 100%;
124             }
125             </style>
126             <link href="//cdn.jsdelivr.net/npm/graphiql@[% graphiql_version %]/graphiql.css" rel="stylesheet" />
127             <script src="//cdn.jsdelivr.net/fetch/0.9.0/fetch.min.js"></script>
128             <script src="//cdn.jsdelivr.net/react/15.4.2/react.min.js"></script>
129             <script src="//cdn.jsdelivr.net/react/15.4.2/react-dom.min.js"></script>
130             <script src="//cdn.jsdelivr.net/npm/graphiql@[% graphiql_version %]/graphiql.min.js"></script>
131             </head>
132             <body>
133             <script>
134             // Collect the URL parameters
135             var parameters = {};
136             window.location.search.substr(1).split('&').forEach(function (entry) {
137             var eq = entry.indexOf('=');
138             if (eq >= 0) {
139             parameters[decodeURIComponent(entry.slice(0, eq))] =
140             decodeURIComponent(entry.slice(eq + 1));
141             }
142             });
143             // Produce a Location query string from a parameter object.
144             function locationQuery(params) {
145             return '?' + Object.keys(params).filter(function (key) {
146             return Boolean(params[key]);
147             }).map(function (key) {
148             return encodeURIComponent(key) + '=' +
149             encodeURIComponent(params[key]);
150             }).join('&');
151             }
152             // Derive a fetch URL from the current URL, sans the GraphQL parameters.
153             var graphqlParamNames = {
154             query: true,
155             variables: true,
156             operationName: true
157             };
158             var otherParams = {};
159             for (var k in parameters) {
160             if (parameters.hasOwnProperty(k) && graphqlParamNames[k] !== true) {
161             otherParams[k] = parameters[k];
162             }
163             }
164             var fetchURL = locationQuery(otherParams);
165             // Defines a GraphQL fetcher using the fetch API.
166             function graphQLFetcher(graphQLParams) {
167             return fetch(fetchURL, {
168             method: 'post',
169             headers: {
170             'Accept': 'application/json',
171             'Content-Type': 'application/json'
172             },
173             body: JSON.stringify(graphQLParams),
174             credentials: 'include',
175             }).then(function (response) {
176             return response.text();
177             }).then(function (responseBody) {
178             try {
179             return JSON.parse(responseBody);
180             } catch (error) {
181             return responseBody;
182             }
183             });
184             }
185             // When the query and variables string is edited, update the URL bar so
186             // that it can be easily shared.
187             function onEditQuery(newQuery) {
188             parameters.query = newQuery;
189             updateURL();
190             }
191             function onEditVariables(newVariables) {
192             parameters.variables = newVariables;
193             updateURL();
194             }
195             function onEditOperationName(newOperationName) {
196             parameters.operationName = newOperationName;
197             updateURL();
198             }
199             function updateURL() {
200             history.replaceState(null, null, locationQuery(parameters));
201             }
202             // Render <GraphiQL /> into the body.
203             ReactDOM.render(
204             React.createElement(GraphiQL, {
205             fetcher: graphQLFetcher,
206             onEditQuery: onEditQuery,
207             onEditVariables: onEditVariables,
208             onEditOperationName: onEditOperationName,
209             query: [% queryString %],
210             response: [% resultString %],
211             variables: [% variablesString %],
212             operationName: [% operationName %],
213             }),
214             document.body
215             );
216             </script>
217             </body>
218             </html>
219