graphql-persisted_queries 0.3.0 → 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +22 -0
- data/README.md +28 -101
- data/docs/alternative_stores.md +80 -0
- data/docs/error_handling.md +26 -0
- data/docs/hash.md +17 -0
- data/docs/http_cache.md +49 -0
- data/docs/tracing.md +51 -0
- data/graphql-persisted_queries.gemspec +1 -0
- data/lib/graphql/persisted_queries.rb +2 -1
- data/lib/graphql/persisted_queries/analyzers/http_method_analyzer.rb +22 -0
- data/lib/graphql/persisted_queries/analyzers/http_method_ast_analyzer.rb +19 -0
- data/lib/graphql/persisted_queries/analyzers/http_method_validator.rb +20 -0
- data/lib/graphql/persisted_queries/multiplex_resolver.rb +3 -2
- data/lib/graphql/persisted_queries/schema_patch.rb +37 -6
- data/lib/graphql/persisted_queries/store_adapters.rb +1 -0
- data/lib/graphql/persisted_queries/store_adapters/base_store_adapter.rb +30 -3
- data/lib/graphql/persisted_queries/store_adapters/memcached_client_builder.rb +38 -0
- data/lib/graphql/persisted_queries/store_adapters/memcached_store_adapter.rb +50 -0
- data/lib/graphql/persisted_queries/store_adapters/memory_store_adapter.rb +5 -2
- data/lib/graphql/persisted_queries/store_adapters/redis_store_adapter.rb +5 -2
- data/lib/graphql/persisted_queries/store_adapters/redis_with_local_cache_store_adapter.rb +13 -2
- data/lib/graphql/persisted_queries/version.rb +1 -1
- metadata +26 -3
- data/lib/graphql/persisted_queries/http_method_analyzer.rb +0 -29
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9a59eccea67a31d0bf06dd13fcefddefb32cb339eefb2cd1967a7f3820571a18
|
4
|
+
data.tar.gz: 7d4d2edca6b0fde6f31787a425164e8fcc6cfa535ce62d0ba373ff45948192a0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ba9bba62ffdc7c3636a35114ca036329119f713ea091c21462c4713e1255c63df0bbd3070b696c5929e8e26fa3f251b79a92db2ec728c1837bb8a75628a4909f
|
7
|
+
data.tar.gz: cdcfef083e461d87148ac90762cefcf4bb5e1661b5735d552accfc66b981b011daf720cfefcf3032d3f9f94fe0f7a06b0362c0a720e4013be34f59d2e33a8193
|
data/CHANGELOG.md
CHANGED
@@ -2,6 +2,26 @@
|
|
2
2
|
|
3
3
|
## master
|
4
4
|
|
5
|
+
## 🥳 1.0.1 (2020-06-25)
|
6
|
+
|
7
|
+
- [PR#34](https://github.com/DmitryTsepelev/graphql-ruby-persisted_queries/pull/34) Return GraphQL::Query::Result when raise error ([@ogidow ][])
|
8
|
+
|
9
|
+
## 🥳 1.0.0 (2020-03-31)
|
10
|
+
|
11
|
+
- [PR#30](https://github.com/DmitryTsepelev/graphql-ruby-persisted_queries/pull/30) **BREAKING CHANGE** Move extenstions to the query context ([@DmitryTsepelev][])
|
12
|
+
|
13
|
+
## 0.5.1 (2020-03-18)
|
14
|
+
|
15
|
+
- [PR#33](https://github.com/DmitryTsepelev/graphql-ruby-persisted_queries/pull/33) Support AST analyzers ([@DmitryTsepelev][])
|
16
|
+
|
17
|
+
## 0.5.0 (2020-03-12)
|
18
|
+
|
19
|
+
- [PR#29](https://github.com/DmitryTsepelev/graphql-ruby-persisted_queries/pull/29) Instrumentation via graphql-ruby's tracer feature ([@bmorton][])
|
20
|
+
|
21
|
+
## 0.4.0 (2020-02-26)
|
22
|
+
|
23
|
+
- [PR#26](https://github.com/DmitryTsepelev/graphql-ruby-persisted_queries/pull/26) Add Memcached store ([@JanStevens][])
|
24
|
+
|
5
25
|
## 0.3.0 (2020-02-21)
|
6
26
|
|
7
27
|
- [PR#24](https://github.com/DmitryTsepelev/graphql-ruby-persisted_queries/pull/24) Add multiplex support ([@DmitryTsepelev][])
|
@@ -30,3 +50,5 @@
|
|
30
50
|
|
31
51
|
[@DmitryTsepelev]: https://github.com/DmitryTsepelev
|
32
52
|
[@bmorton]: https://github.com/bmorton
|
53
|
+
[@JanStevens]: https://github.com/JanStevens
|
54
|
+
[@ogidow]: https://github.com/ogidow
|
data/README.md
CHANGED
@@ -14,11 +14,9 @@
|
|
14
14
|
</a>
|
15
15
|
</p>
|
16
16
|
|
17
|
-
##
|
17
|
+
## Getting started
|
18
18
|
|
19
|
-
|
20
|
-
|
21
|
-
2. Install and configure [apollo-link-persisted-queries](https://github.com/apollographql/apollo-link-persisted-queries):
|
19
|
+
First of all, install and configure [apollo-link-persisted-queries](https://github.com/apollographql/apollo-link-persisted-queries) on the front–end side:
|
22
20
|
|
23
21
|
```js
|
24
22
|
import { createPersistedQueryLink } from "apollo-link-persisted-queries";
|
@@ -35,7 +33,7 @@ const client = new ApolloClient({
|
|
35
33
|
});
|
36
34
|
```
|
37
35
|
|
38
|
-
|
36
|
+
Add the gem to your Gemfile `gem 'graphql-persisted_queries'` and add the plugin to your schema class:
|
39
37
|
|
40
38
|
```ruby
|
41
39
|
class GraphqlSchema < GraphQL::Schema
|
@@ -43,126 +41,55 @@ class GraphqlSchema < GraphQL::Schema
|
|
43
41
|
end
|
44
42
|
```
|
45
43
|
|
46
|
-
|
47
|
-
|
48
|
-
```ruby
|
49
|
-
GraphqlSchema.execute(
|
50
|
-
params[:query],
|
51
|
-
variables: ensure_hash(params[:variables]),
|
52
|
-
context: {},
|
53
|
-
operation_name: params[:operationName],
|
54
|
-
extensions: ensure_hash(params[:extensions])
|
55
|
-
)
|
56
|
-
```
|
57
|
-
|
58
|
-
5. Run the app! 🔥
|
59
|
-
|
60
|
-
## Usage with BatchLink
|
61
|
-
|
62
|
-
It's possible to group queries using [batch-link](https://www.apollographql.com/docs/link/links/batch-http/) and send them as a single HTTP request. In this case you need to use `GraphqlSchema.multiplex(queries)` instead of `#execute`. The gem supports it too, no action required!
|
63
|
-
|
64
|
-
## Alternative stores
|
65
|
-
|
66
|
-
All the queries are stored in memory by default, but you can easily switch to _redis_:
|
67
|
-
|
68
|
-
```ruby
|
69
|
-
class GraphqlSchema < GraphQL::Schema
|
70
|
-
use GraphQL::PersistedQueries, store: :redis, redis_client: { redis_url: ENV["MY_REDIS_URL"] }
|
71
|
-
end
|
72
|
-
```
|
73
|
-
|
74
|
-
If you have `ENV["REDIS_URL"]` configured – you don't need to pass it explicitly. Also, you can pass `:redis_host`, `:redis_port` and `:redis_db_name` inside the `:redis_client` hash to build the URL from scratch or pass the configured `Redis` or `ConnectionPool` object:
|
44
|
+
**Heads up!** If you've already switched to interpreter mode and and AST analyzers—make sure AST plugin is added _before_ `GraphQL::PersistedQueries`:
|
75
45
|
|
76
46
|
```ruby
|
77
47
|
class GraphqlSchema < GraphQL::Schema
|
78
|
-
use GraphQL::
|
79
|
-
|
80
|
-
|
81
|
-
# or
|
82
|
-
use GraphQL::PersistedQueries,
|
83
|
-
store: :redis,
|
84
|
-
redis_client: Redis.new(url: "redis://127.0.0.2:2214/7")
|
85
|
-
# or
|
86
|
-
use GraphQL::PersistedQueries,
|
87
|
-
store: :redis,
|
88
|
-
redis_client: ConnectionPool.new { Redis.new(url: "redis://127.0.0.2:2214/7") }
|
48
|
+
use GraphQL::Execution::Interpreter
|
49
|
+
use GraphQL::Analysis::AST
|
50
|
+
use GraphQL::PersistedQueries
|
89
51
|
end
|
90
52
|
```
|
91
53
|
|
92
|
-
|
54
|
+
Pass `:extensions` argument as part of a `context` to all calls of `GraphqlSchema#execute`, usually it happens in `GraphqlController`, `GraphqlChannel` and tests:
|
93
55
|
|
94
56
|
```ruby
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
57
|
+
GraphqlSchema.execute(
|
58
|
+
params[:query],
|
59
|
+
variables: ensure_hash(params[:variables]),
|
60
|
+
context: {
|
61
|
+
extensions: ensure_hash(params[:extensions])
|
62
|
+
},
|
63
|
+
operation_name: params[:operationName]
|
64
|
+
)
|
102
65
|
```
|
103
66
|
|
104
|
-
|
67
|
+
You're all set!
|
105
68
|
|
106
|
-
|
69
|
+
## Advanced usage
|
107
70
|
|
108
|
-
|
109
|
-
- `:redis`: This store will allow you to share a Redis cache across all instances of your GraphQL application so that each instance doesn't have to ask the client for the query again if it hasn't seen it yet.
|
110
|
-
- `:redis_with_local_cache`: This store combines both the `:memory` and `:redis` approaches so that we can reduce the number of network requests we make while mitigating the independent cache issue. This adapter is configured identically to the `:redis` store.
|
111
|
-
|
112
|
-
## Alternative hash functions
|
113
|
-
|
114
|
-
[apollo-link-persisted-queries](https://github.com/apollographql/apollo-link-persisted-queries) uses _SHA256_ by default so this gem uses it as a default too, but if you want to override it – you can use `:hash_generator` option:
|
71
|
+
All the queries are stored in memory by default, but you can easily switch to another storage (e.g., _redis_:
|
115
72
|
|
116
73
|
```ruby
|
117
74
|
class GraphqlSchema < GraphQL::Schema
|
118
|
-
use GraphQL::PersistedQueries,
|
119
|
-
end
|
120
|
-
```
|
121
|
-
|
122
|
-
If string or symbol is passed – the gem would try to find the class in the `Digest` namespace. Altenatively, you can pass a lambda, e.g.:
|
123
|
-
|
124
|
-
```ruby
|
125
|
-
class GraphqlSchema < GraphQL::Schema
|
126
|
-
use GraphQL::PersistedQueries, hash_generator: proc { |_value| "super_safe_hash!!!" }
|
75
|
+
use GraphQL::PersistedQueries, store: :redis, redis_client: { redis_url: ENV["MY_REDIS_URL"] }
|
127
76
|
end
|
128
77
|
```
|
129
78
|
|
130
|
-
|
131
|
-
|
132
|
-
You may optionally specify an object that will be called whenever an error occurs while attempting to resolve or save a query. This will give you the opportunity to both handle (e.g. graceful Redis failure) and/or log the error. By default, errors will be raised when a failure occurs within a `StoreAdapter`.
|
79
|
+
We currently support `memory`, `redis`, `redis_with_local_cache` and `memcached` out of the box. The detailed documentation can be found [here](docs/alternative_stores.md).
|
133
80
|
|
134
|
-
|
81
|
+
When the error occurs, the gem tries to not interrupt the regular flow of the app (e.g., when something is wrong with the storage, it will just answer that persisted query is not found). You can add a [custom](docs/error_handling.md) error handler and try to fix the problem or just log it.
|
135
82
|
|
136
|
-
|
137
|
-
class GracefulRedisErrorHandler < GraphQL::PersistedQueries::ErrorHandlers::BaseErrorHandler
|
138
|
-
def call(error)
|
139
|
-
case error
|
140
|
-
when Redis::BaseError
|
141
|
-
# Treat Redis errors as a cache miss, but you should log the error into
|
142
|
-
# your instrumentation framework here.
|
143
|
-
else
|
144
|
-
raise error
|
145
|
-
end
|
146
|
-
|
147
|
-
# Return nothing to ensure handled errors are treated as cache misses
|
148
|
-
return
|
149
|
-
end
|
150
|
-
end
|
83
|
+
Since our queries are slim now, we can switch back to HTTP GET, you can find a [guide](docs/http_cache.md) here.
|
151
84
|
|
152
|
-
|
153
|
-
use GraphQL::PersistedQueries, error_handler: GracefulRedisErrorHandler.new
|
154
|
-
end
|
155
|
-
```
|
85
|
+
[batch-link](https://www.apollographql.com/docs/link/links/batch-http/) allows to group queries on the client side into a single HTTP request before sending to the server. In this case you need to use `GraphqlSchema.multiplex(queries)` instead of `#execute`. The gem supports it too, no action required!
|
156
86
|
|
157
|
-
|
87
|
+
[apollo-link-persisted-queries](https://github.com/apollographql/apollo-link-persisted-queries) uses _SHA256_ for building hashes by default. Check out this [guide](docs/hash.md) if you want to override this behavior.
|
158
88
|
|
159
|
-
|
160
|
-
1. Change the way link is initialized on front-end side (`createPersistedQueryLink({ useGETForHashedQueries: true })`);
|
161
|
-
2. Register a new route `get "/graphql", to: "graphql#execute"`;
|
162
|
-
3. Put the request object to the GraphQL context in the controller `GraphqlSchema.execute(query, variables: variables, context: { request: request })`;
|
163
|
-
4. Turn the `verify_http_method` option on (`use GraphQL::PersistedQueries, verify_http_method: true`) to enforce using `POST` requests for performing mutations (otherwise the error `Mutations cannot be performed via HTTP GET` will be returned).
|
89
|
+
An experimental tracing feature can be enabled by setting `tracing: true` when configuring the plugin. Read more about this feature in the [Tracing guide](docs/tracing.md).
|
164
90
|
|
165
|
-
|
91
|
+
> 📖 Read more about the gem internals: [Persisted queries in GraphQL:
|
92
|
+
Slim down Apollo requests to your Ruby application](https://evilmartians.com/chronicles/persisted-queries-in-graphql-slim-down-apollo-requests-to-your-ruby-application)
|
166
93
|
|
167
94
|
## Contributing
|
168
95
|
|
@@ -0,0 +1,80 @@
|
|
1
|
+
# Alternative stores
|
2
|
+
|
3
|
+
We currently support a few different stores that can be configured out of the box:
|
4
|
+
|
5
|
+
- `:memory`: This is the default in-memory store and is great for getting started, but will require each instance to cache results independently which can result in lots of ["new query path"](https://blog.apollographql.com/improve-graphql-performance-with-automatic-persisted-queries-c31d27b8e6ea) requests.
|
6
|
+
- `:redis`: This store will allow you to share a Redis cache across all instances of your GraphQL application so that each instance doesn't have to ask the client for the query again if it hasn't seen it yet.
|
7
|
+
- `:redis_with_local_cache`: This store combines both the `:memory` and `:redis` approaches so that we can reduce the number of network requests we make while mitigating the independent cache issue. This adapter is configured identically to the `:redis` store.
|
8
|
+
- `:memcached`: This store will allow you to share a Memcached cache across all instances of your GraphQL application. The client is implemented with the Dalli gem.
|
9
|
+
|
10
|
+
## Redis
|
11
|
+
|
12
|
+
If you have `ENV["REDIS_URL"]` configured – you don't need to pass it explicitly. Also, you can pass `:redis_host`, `:redis_port` and `:redis_db_name`
|
13
|
+
inside the `:redis_client` hash to build the URL from scratch or pass the configured `Redis` or `ConnectionPool` object:
|
14
|
+
|
15
|
+
```ruby
|
16
|
+
class GraphqlSchema < GraphQL::Schema
|
17
|
+
use GraphQL::PersistedQueries,
|
18
|
+
store: :redis,
|
19
|
+
redis_client: { redis_host: "127.0.0.2", redis_port: "2214", redis_db_name: "7" }
|
20
|
+
# or
|
21
|
+
use GraphQL::PersistedQueries,
|
22
|
+
store: :redis,
|
23
|
+
redis_client: Redis.new(url: "redis://127.0.0.2:2214/7")
|
24
|
+
# or
|
25
|
+
use GraphQL::PersistedQueries,
|
26
|
+
store: :redis,
|
27
|
+
redis_client: ConnectionPool.new { Redis.new(url: "redis://127.0.0.2:2214/7") }
|
28
|
+
end
|
29
|
+
```
|
30
|
+
|
31
|
+
You can also pass options for expiration and namespace to override the defaults:
|
32
|
+
|
33
|
+
```ruby
|
34
|
+
class GraphqlSchema < GraphQL::Schema
|
35
|
+
use GraphQL::PersistedQueries,
|
36
|
+
store: :redis,
|
37
|
+
redis_client: { redis_url: ENV["MY_REDIS_URL"] },
|
38
|
+
expiration: 172800, # optional, default is 24 hours
|
39
|
+
namespace: "my-custom-namespace" # optional, default is "graphql-persisted-query"
|
40
|
+
end
|
41
|
+
```
|
42
|
+
|
43
|
+
## Memcached
|
44
|
+
|
45
|
+
If you have `ENV["MEMCACHE_SERVERS"]` configured - you don't need to pass it explicitly. Also, you can pass `:memcached_host` and `:memcached_port`
|
46
|
+
inside the `:dalli_client` hash to build the server name from scratch or pass the configured `Dalli::Client` object:
|
47
|
+
|
48
|
+
```ruby
|
49
|
+
class GraphqlSchema < GraphQL::Schema
|
50
|
+
use GraphQL::PersistedQueries,
|
51
|
+
store: :memcached,
|
52
|
+
dalli_client: { memcached_host: "127.0.0.2", memcached_port: "11211" }
|
53
|
+
# or
|
54
|
+
use GraphQL::PersistedQueries,
|
55
|
+
store: :memcached,
|
56
|
+
dalli_client: Dalli::Client.new("127.0.0.2:11211")
|
57
|
+
# or
|
58
|
+
use GraphQL::PersistedQueries,
|
59
|
+
store: :memcached,
|
60
|
+
dalli_client: { memcached_url: "127.0.0.2:11211" }
|
61
|
+
end
|
62
|
+
```
|
63
|
+
|
64
|
+
You can also pass options for `expiration` and `namespace` to override the defaults.
|
65
|
+
Any additional argument inside `dalli_client` will be forwarded to `Dalli::Client.new`.
|
66
|
+
Following example configures Dalli `pool_size` and `compress` options:
|
67
|
+
|
68
|
+
```ruby
|
69
|
+
class GraphqlSchema < GraphQL::Schema
|
70
|
+
use GraphQL::PersistedQueries,
|
71
|
+
store: :memcached,
|
72
|
+
dalli_client: {
|
73
|
+
memcached_url: "127.0.0.2:11211",
|
74
|
+
pool_size: 5,
|
75
|
+
compress: true
|
76
|
+
},
|
77
|
+
expiration: 172800, # optional, default is 24 hours
|
78
|
+
namespace: "my-custom-namespace" # optional, default is "graphql-persisted-query"
|
79
|
+
end
|
80
|
+
```
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# Error handling
|
2
|
+
|
3
|
+
You may optionally specify an object that will be called whenever an error occurs while attempting to resolve or save a query. This will give you the opportunity to both handle (e.g. graceful Redis failure) and/or log the error. By default, errors will be raised when a failure occurs within a `StoreAdapter`.
|
4
|
+
|
5
|
+
An error handler can be a proc or an implementation of `GraphQL::PersistedQueries::ErrorHandlers::BaseErrorHandler`. Here's an example for treating Redis failures as cache misses:
|
6
|
+
|
7
|
+
```ruby
|
8
|
+
class GracefulRedisErrorHandler < GraphQL::PersistedQueries::ErrorHandlers::BaseErrorHandler
|
9
|
+
def call(error)
|
10
|
+
case error
|
11
|
+
when Redis::BaseError
|
12
|
+
# Treat Redis errors as a cache miss, but you should log the error into
|
13
|
+
# your instrumentation framework here.
|
14
|
+
else
|
15
|
+
raise error
|
16
|
+
end
|
17
|
+
|
18
|
+
# Return nothing to ensure handled errors are treated as cache misses
|
19
|
+
return
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class GraphqlSchema < GraphQL::Schema
|
24
|
+
use GraphQL::PersistedQueries, error_handler: GracefulRedisErrorHandler.new
|
25
|
+
end
|
26
|
+
```
|
data/docs/hash.md
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
# Alternative hash functions
|
2
|
+
|
3
|
+
[apollo-link-persisted-queries](https://github.com/apollographql/apollo-link-persisted-queries) uses _SHA256_ by default so this gem uses it as a default too, but if you want to override it – you can use `:hash_generator` option:
|
4
|
+
|
5
|
+
```ruby
|
6
|
+
class GraphqlSchema < GraphQL::Schema
|
7
|
+
use GraphQL::PersistedQueries, hash_generator: :md5
|
8
|
+
end
|
9
|
+
```
|
10
|
+
|
11
|
+
If string or symbol is passed – the gem would try to find the class in the `Digest` namespace. Altenatively, you can pass a lambda, e.g.:
|
12
|
+
|
13
|
+
```ruby
|
14
|
+
class GraphqlSchema < GraphQL::Schema
|
15
|
+
use GraphQL::PersistedQueries, hash_generator: proc { |_value| "super_safe_hash!!!" }
|
16
|
+
end
|
17
|
+
```
|
data/docs/http_cache.md
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
# GET requests and HTTP cache
|
2
|
+
|
3
|
+
Using `GET` requests for persisted queries allows you to enable HTTP caching (e.g., turn on CDN).
|
4
|
+
|
5
|
+
Firstly, turn on the `useGETForHashedQueries` parameter on the front-end side:
|
6
|
+
|
7
|
+
```js
|
8
|
+
import { createPersistedQueryLink } from "apollo-link-persisted-queries";
|
9
|
+
import { createHttpLink } from "apollo-link-http";
|
10
|
+
import { InMemoryCache } from "apollo-cache-inmemory";
|
11
|
+
import ApolloClient from "apollo-client";
|
12
|
+
|
13
|
+
|
14
|
+
// use this with Apollo Client
|
15
|
+
const link = createPersistedQueryLink({ useGETForHashedQueries: true }).concat(createHttpLink({ uri: "/graphql" }));
|
16
|
+
const client = new ApolloClient({
|
17
|
+
cache: new InMemoryCache(),
|
18
|
+
link: link,
|
19
|
+
});
|
20
|
+
```
|
21
|
+
|
22
|
+
Register a new route in `routes.rb`:
|
23
|
+
|
24
|
+
```ruby
|
25
|
+
get "/graphql", to: "graphql#execute"
|
26
|
+
```
|
27
|
+
|
28
|
+
Put the request object to the GraphQL context everywhere you execute GraphQL queries:
|
29
|
+
|
30
|
+
```ruby
|
31
|
+
GraphqlSchema.execute(
|
32
|
+
query,
|
33
|
+
variables: ensure_hash(params[:variables]),
|
34
|
+
context: {
|
35
|
+
extensions: ensure_hash(params[:extensions]),
|
36
|
+
request: request
|
37
|
+
}
|
38
|
+
)
|
39
|
+
```
|
40
|
+
|
41
|
+
Turn the `verify_http_method` option when configuring the plugin to enforce using `POST` requests for performing mutations (otherwise the error `Mutations cannot be performed via HTTP GET` will be returned):
|
42
|
+
|
43
|
+
```ruby
|
44
|
+
class GraphqlSchema < GraphQL::Schema
|
45
|
+
use GraphQL::PersistedQueries, verify_http_method: true
|
46
|
+
end
|
47
|
+
```
|
48
|
+
|
49
|
+
HTTP method verification is important, because when mutations are allowed via `GET` requests, it's easy to perform an attack by sending the link containing mutation to a signed in user.
|
data/docs/tracing.md
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
# Tracing
|
2
|
+
|
3
|
+
Tracing is an experimental feature that, when enabled, uses the tracing system defined in `graphql-ruby` to surface these events:
|
4
|
+
|
5
|
+
* `persisted_queries.fetch_query.cache_hit` - Triggered when a store adapter successfully looks up a hash and finds a query.
|
6
|
+
* `persisted_queries.fetch_query.cache_miss` - Triggered when a store adapter attempts to look up a hash but cannot find it.
|
7
|
+
* `persisted_queries.save_query` - Triggered when a store adapter persists a query.
|
8
|
+
|
9
|
+
All events include a metadata hash as their `data` parameter. This hash currently only includes the name of the adapter that triggered the event.
|
10
|
+
|
11
|
+
## Usage
|
12
|
+
|
13
|
+
Tracing must be opted into via the plugin configuration for the events to trigger. Once they are enabled, any tracer that is defined on the schema will get the following events yielded to them. An example configuration will look similar to this:
|
14
|
+
|
15
|
+
```ruby
|
16
|
+
class GraphqlSchema < GraphQL::Schema
|
17
|
+
use GraphQL::PersistedQueries, tracing: true
|
18
|
+
tracer MyPersistedQueriesTracer
|
19
|
+
end
|
20
|
+
```
|
21
|
+
|
22
|
+
Tracers in this plugin integrate with the `GraphQL::Tracing` feature in [`graphql-ruby`](https://github.com/rmosolgo/graphql-ruby). The same tracers are used for tracing events directly from `graphql-ruby` and this plugin. The [guide on "Tracing"](https://graphql-ruby.org/queries/tracing.html) in `graphql-ruby` has implementation details, but an example tracer would look similar to this:
|
23
|
+
|
24
|
+
```ruby
|
25
|
+
class MyPersistedQueriesTracer
|
26
|
+
def self.trace(key, data)
|
27
|
+
yield.tap do |result|
|
28
|
+
# Note: this tracer will get called for these persisted queries events as
|
29
|
+
# well as all events traced by the graphql-ruby gem.
|
30
|
+
case key
|
31
|
+
when "persisted_queries.fetch_query.cache_hit"
|
32
|
+
# data = { adapter: :redis }
|
33
|
+
# result = nil
|
34
|
+
# increment a counter metric to track cache hits
|
35
|
+
when "persisted_queries.fetch_query.cache_miss"
|
36
|
+
# data = { adapter: :redis }
|
37
|
+
# result = nil
|
38
|
+
# increment a counter metric to track cache misses
|
39
|
+
when "persisted_queries.save_query"
|
40
|
+
# data = { adapter: :redis }
|
41
|
+
# result = return value from method call
|
42
|
+
# increment a counter metric to track saved queries
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
```
|
48
|
+
|
49
|
+
## Be aware of tracers-as-notifications
|
50
|
+
|
51
|
+
A word of caution about the `cache_hit` and `cache_miss` events: they yield an empty block. The `GraphQL::Tracing` feature typically wraps around the code performing the event. The `save_query` event works this way, too -- the block that is yielded is essentially the `StoreAdapter#save` method. This means you can add timing instrumentation for this call. However, the `cache_hit` and `cache_miss` events are simply event notifications and do not wrap any code. This means that they won't yield anything meaningful and they can't be timed.
|
@@ -28,5 +28,6 @@ Gem::Specification.new do |spec|
|
|
28
28
|
spec.add_development_dependency "rake", ">= 10.0"
|
29
29
|
spec.add_development_dependency "rubocop", "0.75"
|
30
30
|
spec.add_development_dependency "redis"
|
31
|
+
spec.add_development_dependency "dalli"
|
31
32
|
spec.add_development_dependency "connection_pool"
|
32
33
|
end
|
@@ -5,7 +5,6 @@ require "graphql/persisted_queries/schema_patch"
|
|
5
5
|
require "graphql/persisted_queries/store_adapters"
|
6
6
|
require "graphql/persisted_queries/version"
|
7
7
|
require "graphql/persisted_queries/builder_helpers"
|
8
|
-
require "graphql/persisted_queries/http_method_analyzer"
|
9
8
|
|
10
9
|
module GraphQL
|
11
10
|
# Plugin definition
|
@@ -21,6 +20,8 @@ module GraphQL
|
|
21
20
|
error_handler = options.delete(:error_handler) || :default
|
22
21
|
schema.configure_persisted_query_error_handler(error_handler)
|
23
22
|
|
23
|
+
schema.persisted_queries_tracing_enabled = options.delete(:tracing)
|
24
|
+
|
24
25
|
store = options.delete(:store) || :memory
|
25
26
|
schema.configure_persisted_query_store(store, options)
|
26
27
|
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GraphQL
|
4
|
+
module PersistedQueries
|
5
|
+
module Analyzers
|
6
|
+
# Verifies that mutations are not executed using GET requests
|
7
|
+
class HttpMethodAnalyzer
|
8
|
+
def initial_value(query)
|
9
|
+
{ query: query }
|
10
|
+
end
|
11
|
+
|
12
|
+
def call(memo, _visit_type, _irep_node)
|
13
|
+
memo
|
14
|
+
end
|
15
|
+
|
16
|
+
def final_value(memo)
|
17
|
+
HttpMethodValidator.new(memo[:query]).perform
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GraphQL
|
4
|
+
module PersistedQueries
|
5
|
+
module Analyzers
|
6
|
+
# Verifies that mutations are not executed using GET requests
|
7
|
+
class HttpMethodAstAnalyzer < GraphQL::Analysis::AST::Analyzer
|
8
|
+
def initialize(query)
|
9
|
+
super
|
10
|
+
@query = query
|
11
|
+
end
|
12
|
+
|
13
|
+
def result
|
14
|
+
HttpMethodValidator.new(@query).perform
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GraphQL
|
4
|
+
module PersistedQueries
|
5
|
+
module Analyzers
|
6
|
+
# Verifies that mutations are not executed using GET requests
|
7
|
+
class HttpMethodValidator
|
8
|
+
def initialize(query)
|
9
|
+
@query = query
|
10
|
+
end
|
11
|
+
|
12
|
+
def perform
|
13
|
+
return if !@query.context[:request]&.get? || !@query.mutation?
|
14
|
+
|
15
|
+
GraphQL::AnalysisError.new("Mutations cannot be performed via HTTP GET")
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -29,12 +29,13 @@ module GraphQL
|
|
29
29
|
end
|
30
30
|
|
31
31
|
def resolve_persisted_query(query_params, pos)
|
32
|
-
extensions = query_params.
|
32
|
+
extensions = query_params.dig(:context, :extensions)
|
33
33
|
return unless extensions
|
34
34
|
|
35
35
|
query_params[:query] = Resolver.new(extensions, @schema).resolve(query_params[:query])
|
36
36
|
rescue Resolver::NotFound, Resolver::WrongHash => e
|
37
|
-
|
37
|
+
values = { "errors" => [{ "message" => e.message }] }
|
38
|
+
results[pos] = GraphQL::Query::Result.new(query: query_params[:query], values: values)
|
38
39
|
end
|
39
40
|
|
40
41
|
def perform_multiplex
|
@@ -3,6 +3,7 @@
|
|
3
3
|
require "graphql/persisted_queries/hash_generator_builder"
|
4
4
|
require "graphql/persisted_queries/resolver"
|
5
5
|
require "graphql/persisted_queries/multiplex_resolver"
|
6
|
+
require "graphql/persisted_queries/analyzers/http_method_validator"
|
6
7
|
|
7
8
|
module GraphQL
|
8
9
|
module PersistedQueries
|
@@ -16,9 +17,12 @@ module GraphQL
|
|
16
17
|
end
|
17
18
|
|
18
19
|
attr_reader :persisted_query_store, :hash_generator_proc, :persisted_query_error_handler
|
20
|
+
attr_writer :persisted_queries_tracing_enabled
|
19
21
|
|
20
22
|
def configure_persisted_query_store(store, options)
|
21
|
-
@persisted_query_store = StoreAdapters.build(store, options)
|
23
|
+
@persisted_query_store = StoreAdapters.build(store, options).tap do |adapter|
|
24
|
+
adapter.tracers = tracers if persisted_queries_tracing_enabled?
|
25
|
+
end
|
22
26
|
end
|
23
27
|
|
24
28
|
def configure_persisted_query_error_handler(handler)
|
@@ -32,18 +36,45 @@ module GraphQL
|
|
32
36
|
def verify_http_method=(verify)
|
33
37
|
return unless verify
|
34
38
|
|
35
|
-
|
36
|
-
|
37
|
-
if Gem::Dependency.new("graphql", ">= 1.10.0").match?("graphql", GraphQL::VERSION)
|
38
|
-
query_analyzer(analyzer)
|
39
|
+
if graphql10?
|
40
|
+
query_analyzer(prepare_analyzer)
|
39
41
|
else
|
40
|
-
query_analyzers <<
|
42
|
+
query_analyzers << prepare_analyzer
|
41
43
|
end
|
42
44
|
end
|
43
45
|
|
46
|
+
def persisted_queries_tracing_enabled?
|
47
|
+
@persisted_queries_tracing_enabled
|
48
|
+
end
|
49
|
+
|
44
50
|
def multiplex(queries, **kwargs)
|
45
51
|
MultiplexResolver.new(self, queries, kwargs).resolve
|
46
52
|
end
|
53
|
+
|
54
|
+
def tracer(name)
|
55
|
+
super.tap do
|
56
|
+
# Since tracers can be set before *and* after our plugin hooks in,
|
57
|
+
# we need to set tracers both when this plugin gets initialized
|
58
|
+
# and any time a tracer is added after initialization
|
59
|
+
persisted_query_store.tracers = tracers if persisted_queries_tracing_enabled?
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
def graphql10?
|
66
|
+
Gem::Dependency.new("graphql", ">= 1.10.0").match?("graphql", GraphQL::VERSION)
|
67
|
+
end
|
68
|
+
|
69
|
+
def prepare_analyzer
|
70
|
+
if graphql10? && using_ast_analysis?
|
71
|
+
require "graphql/persisted_queries/analyzers/http_method_ast_analyzer"
|
72
|
+
Analyzers::HttpMethodAstAnalyzer
|
73
|
+
else
|
74
|
+
require "graphql/persisted_queries/analyzers/http_method_analyzer"
|
75
|
+
Analyzers::HttpMethodAnalyzer.new
|
76
|
+
end
|
77
|
+
end
|
47
78
|
end
|
48
79
|
end
|
49
80
|
end
|
@@ -4,6 +4,7 @@ require "graphql/persisted_queries/store_adapters/base_store_adapter"
|
|
4
4
|
require "graphql/persisted_queries/store_adapters/memory_store_adapter"
|
5
5
|
require "graphql/persisted_queries/store_adapters/redis_store_adapter"
|
6
6
|
require "graphql/persisted_queries/store_adapters/redis_with_local_cache_store_adapter"
|
7
|
+
require "graphql/persisted_queries/store_adapters/memcached_store_adapter"
|
7
8
|
|
8
9
|
module GraphQL
|
9
10
|
module PersistedQueries
|
@@ -5,15 +5,42 @@ module GraphQL
|
|
5
5
|
module StoreAdapters
|
6
6
|
# Base class for all store adapters
|
7
7
|
class BaseStoreAdapter
|
8
|
-
|
8
|
+
include GraphQL::Tracing::Traceable
|
9
|
+
attr_writer :tracers
|
9
10
|
|
10
|
-
def
|
11
|
+
def initialize(_options)
|
12
|
+
@name = :base
|
13
|
+
end
|
14
|
+
|
15
|
+
def fetch_query(hash)
|
16
|
+
fetch(hash).tap do |result|
|
17
|
+
event = result ? "cache_hit" : "cache_miss"
|
18
|
+
trace("fetch_query.#{event}", adapter: @name)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def save_query(hash, query)
|
23
|
+
trace("save_query", adapter: @name) { save(hash, query) }
|
24
|
+
end
|
25
|
+
|
26
|
+
protected
|
27
|
+
|
28
|
+
def fetch(_hash)
|
11
29
|
raise NotImplementedError
|
12
30
|
end
|
13
31
|
|
14
|
-
def
|
32
|
+
def save(_hash, _query)
|
15
33
|
raise NotImplementedError
|
16
34
|
end
|
35
|
+
|
36
|
+
def trace(key, metadata)
|
37
|
+
if @tracers
|
38
|
+
key = "persisted_queries.#{key}"
|
39
|
+
block_given? ? super : super {}
|
40
|
+
elsif block_given?
|
41
|
+
yield
|
42
|
+
end
|
43
|
+
end
|
17
44
|
end
|
18
45
|
end
|
19
46
|
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GraphQL
|
4
|
+
module PersistedQueries
|
5
|
+
module StoreAdapters
|
6
|
+
# Builds Redis object instance based on passed hash
|
7
|
+
class MemcachedClientBuilder
|
8
|
+
def initialize(memcached_url: nil, memcached_host: nil, memcached_port: nil, **dalli_args)
|
9
|
+
require "dalli"
|
10
|
+
|
11
|
+
@memcached_url = memcached_url
|
12
|
+
@memcached_host = memcached_host
|
13
|
+
@memcached_port = memcached_port
|
14
|
+
@dalli_args = dalli_args
|
15
|
+
rescue LoadError => e
|
16
|
+
msg = "Could not load the 'dalli' gem, please add it to your gemfile or " \
|
17
|
+
"configure a different adapter, e.g. use GraphQL::PersistedQueries, store: :memory"
|
18
|
+
raise e.class, msg, e.backtrace
|
19
|
+
end
|
20
|
+
|
21
|
+
def build
|
22
|
+
if @memcached_url && (@memcached_host || @memcached_port)
|
23
|
+
raise ArgumentError, "memcached_url cannot be passed along with memcached_host or " \
|
24
|
+
"memcached_port options"
|
25
|
+
end
|
26
|
+
|
27
|
+
::Dalli::Client.new(@memcached_url || build_memcached_url, **@dalli_args)
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def build_memcached_url
|
33
|
+
"#{@memcached_host}:#{@memcached_port}"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "graphql/persisted_queries/store_adapters/memcached_client_builder"
|
4
|
+
|
5
|
+
module GraphQL
|
6
|
+
module PersistedQueries
|
7
|
+
module StoreAdapters
|
8
|
+
# Redis adapter for storing persisted queries
|
9
|
+
class MemcachedStoreAdapter < BaseStoreAdapter
|
10
|
+
DEFAULT_EXPIRATION = 24 * 60 * 60
|
11
|
+
DEFAULT_NAMESPACE = "graphql-persisted-query"
|
12
|
+
|
13
|
+
def initialize(dalli_client:, expiration: nil, namespace: nil)
|
14
|
+
@dalli_proc = build_dalli_proc(dalli_client)
|
15
|
+
@expiration = expiration || DEFAULT_EXPIRATION
|
16
|
+
@namespace = namespace || DEFAULT_NAMESPACE
|
17
|
+
@name = :memcached
|
18
|
+
end
|
19
|
+
|
20
|
+
protected
|
21
|
+
|
22
|
+
def fetch(hash)
|
23
|
+
@dalli_proc.call { |dalli| dalli.get(key_for(hash)) }
|
24
|
+
end
|
25
|
+
|
26
|
+
def save(hash, query)
|
27
|
+
@dalli_proc.call { |dalli| dalli.set(key_for(hash), query, @expiration) }
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def key_for(hash)
|
33
|
+
"#{@namespace}:#{hash}"
|
34
|
+
end
|
35
|
+
|
36
|
+
def build_dalli_proc(dalli_client)
|
37
|
+
if dalli_client.is_a?(Hash)
|
38
|
+
build_dalli_proc(MemcachedClientBuilder.new(dalli_client).build)
|
39
|
+
elsif dalli_client.is_a?(Proc)
|
40
|
+
dalli_client
|
41
|
+
elsif defined?(::Dalli::Client) && dalli_client.is_a?(::Dalli::Client)
|
42
|
+
proc { |&b| b.call(dalli_client) }
|
43
|
+
else
|
44
|
+
raise ArgumentError, ":dalli_client accepts Dalli::Client, Hash or Proc only"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -7,13 +7,16 @@ module GraphQL
|
|
7
7
|
class MemoryStoreAdapter < BaseStoreAdapter
|
8
8
|
def initialize(_options)
|
9
9
|
@storage = {}
|
10
|
+
@name = :memory
|
10
11
|
end
|
11
12
|
|
12
|
-
|
13
|
+
protected
|
14
|
+
|
15
|
+
def fetch(hash)
|
13
16
|
@storage[hash]
|
14
17
|
end
|
15
18
|
|
16
|
-
def
|
19
|
+
def save(hash, query)
|
17
20
|
@storage[hash] = query
|
18
21
|
end
|
19
22
|
end
|
@@ -14,13 +14,16 @@ module GraphQL
|
|
14
14
|
@redis_proc = build_redis_proc(redis_client)
|
15
15
|
@expiration = expiration || DEFAULT_EXPIRATION
|
16
16
|
@namespace = namespace || DEFAULT_NAMESPACE
|
17
|
+
@name = :redis
|
17
18
|
end
|
18
19
|
|
19
|
-
|
20
|
+
protected
|
21
|
+
|
22
|
+
def fetch(hash)
|
20
23
|
@redis_proc.call { |redis| redis.get(key_for(hash)) }
|
21
24
|
end
|
22
25
|
|
23
|
-
def
|
26
|
+
def save(hash, query)
|
24
27
|
@redis_proc.call { |redis| redis.set(key_for(hash), query, ex: @expiration) }
|
25
28
|
end
|
26
29
|
|
@@ -19,9 +19,20 @@ module GraphQL
|
|
19
19
|
namespace: namespace
|
20
20
|
)
|
21
21
|
@memory_adapter = memory_adapter_class.new(nil)
|
22
|
+
@name = :redis_with_local_cache
|
22
23
|
end
|
23
24
|
|
24
|
-
|
25
|
+
# We don't need to implement our own traces for this adapter since the
|
26
|
+
# underlying adapters will emit the proper events for us. However,
|
27
|
+
# since tracers can be defined at any time, we need to pass them through.
|
28
|
+
def tracers=(tracers)
|
29
|
+
@memory_adapter.tracers = tracers
|
30
|
+
@redis_adapter.tracers = tracers
|
31
|
+
end
|
32
|
+
|
33
|
+
protected
|
34
|
+
|
35
|
+
def fetch(hash)
|
25
36
|
result = @memory_adapter.fetch_query(hash)
|
26
37
|
result ||= begin
|
27
38
|
inner_result = @redis_adapter.fetch_query(hash)
|
@@ -31,7 +42,7 @@ module GraphQL
|
|
31
42
|
result
|
32
43
|
end
|
33
44
|
|
34
|
-
def
|
45
|
+
def save(hash, query)
|
35
46
|
@redis_adapter.save_query(hash, query)
|
36
47
|
@memory_adapter.save_query(hash, query)
|
37
48
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: graphql-persisted_queries
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- DmitryTsepelev
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-06-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: graphql
|
@@ -80,6 +80,20 @@ dependencies:
|
|
80
80
|
- - ">="
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: dalli
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
83
97
|
- !ruby/object:Gem::Dependency
|
84
98
|
name: connection_pool
|
85
99
|
requirement: !ruby/object:Gem::Requirement
|
@@ -111,23 +125,32 @@ files:
|
|
111
125
|
- Rakefile
|
112
126
|
- bin/console
|
113
127
|
- bin/setup
|
128
|
+
- docs/alternative_stores.md
|
129
|
+
- docs/error_handling.md
|
130
|
+
- docs/hash.md
|
131
|
+
- docs/http_cache.md
|
132
|
+
- docs/tracing.md
|
114
133
|
- gemfiles/graphql_1_10.gemfile
|
115
134
|
- gemfiles/graphql_1_8.gemfile
|
116
135
|
- gemfiles/graphql_1_9.gemfile
|
117
136
|
- gemfiles/graphql_master.gemfile
|
118
137
|
- graphql-persisted_queries.gemspec
|
119
138
|
- lib/graphql/persisted_queries.rb
|
139
|
+
- lib/graphql/persisted_queries/analyzers/http_method_analyzer.rb
|
140
|
+
- lib/graphql/persisted_queries/analyzers/http_method_ast_analyzer.rb
|
141
|
+
- lib/graphql/persisted_queries/analyzers/http_method_validator.rb
|
120
142
|
- lib/graphql/persisted_queries/builder_helpers.rb
|
121
143
|
- lib/graphql/persisted_queries/error_handlers.rb
|
122
144
|
- lib/graphql/persisted_queries/error_handlers/base_error_handler.rb
|
123
145
|
- lib/graphql/persisted_queries/error_handlers/default_error_handler.rb
|
124
146
|
- lib/graphql/persisted_queries/hash_generator_builder.rb
|
125
|
-
- lib/graphql/persisted_queries/http_method_analyzer.rb
|
126
147
|
- lib/graphql/persisted_queries/multiplex_resolver.rb
|
127
148
|
- lib/graphql/persisted_queries/resolver.rb
|
128
149
|
- lib/graphql/persisted_queries/schema_patch.rb
|
129
150
|
- lib/graphql/persisted_queries/store_adapters.rb
|
130
151
|
- lib/graphql/persisted_queries/store_adapters/base_store_adapter.rb
|
152
|
+
- lib/graphql/persisted_queries/store_adapters/memcached_client_builder.rb
|
153
|
+
- lib/graphql/persisted_queries/store_adapters/memcached_store_adapter.rb
|
131
154
|
- lib/graphql/persisted_queries/store_adapters/memory_store_adapter.rb
|
132
155
|
- lib/graphql/persisted_queries/store_adapters/redis_client_builder.rb
|
133
156
|
- lib/graphql/persisted_queries/store_adapters/redis_store_adapter.rb
|
@@ -1,29 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module GraphQL
|
4
|
-
module PersistedQueries
|
5
|
-
# Verifies that mutations are not executed using GET requests
|
6
|
-
class HttpMethodAnalyzer
|
7
|
-
def analyze?(query)
|
8
|
-
query.context[:request]
|
9
|
-
end
|
10
|
-
|
11
|
-
def initial_value(query)
|
12
|
-
{
|
13
|
-
get_request: query.context[:request]&.get?,
|
14
|
-
mutation: query.mutation?
|
15
|
-
}
|
16
|
-
end
|
17
|
-
|
18
|
-
def call(memo, _visit_type, _irep_node)
|
19
|
-
memo
|
20
|
-
end
|
21
|
-
|
22
|
-
def final_value(memo)
|
23
|
-
return if !memo[:get_request] || !memo[:mutation]
|
24
|
-
|
25
|
-
GraphQL::AnalysisError.new("Mutations cannot be performed via HTTP GET")
|
26
|
-
end
|
27
|
-
end
|
28
|
-
end
|
29
|
-
end
|