Every client module generated by use Grephql includes an overridable prepare_req/1 callback. It receives the fully built %Req.Request{} just before it is sent, giving you a hook to attach Req response steps, add headers, or apply any other request-level customization.
How It Works
- Override
prepare_req/1in your client module - In a Req response step, use
Grephql.Result.put_resp_assign/3to stash values from the response - After decoding, those values appear in
result.assigns
defmodule MyApp.API do
use Grephql,
otp_app: :my_app,
source: "priv/schemas/api.json",
endpoint: "https://api.example.com/graphql"
def prepare_req(req) do
Req.Request.append_response_steps(req,
capture_request_id: fn {req, resp} ->
request_id = Req.Response.get_header(resp, "x-request-id")
{req, Grephql.Result.put_resp_assign(resp, :request_id, request_id)}
end
)
end
defgql :get_user, ~GQL"""
query GetUser($id: ID!) {
user(id: $id) { name }
}
"""
end
{:ok, result} = MyApp.API.get_user(%{id: "1"})
result.assigns[:request_id] #=> ["req-abc-123"]Storing Multiple Values
Call put_resp_assign/3 multiple times to store different pieces of metadata:
def prepare_req(req) do
Req.Request.append_response_steps(req,
capture_metadata: fn {req, resp} ->
resp =
resp
|> Grephql.Result.put_resp_assign(:extensions, resp.body["extensions"])
|> Grephql.Result.put_resp_assign(:request_id, Req.Response.get_header(resp, "x-request-id"))
{req, resp}
end
)
endTesting
Use Req.Test as usual. Include the metadata you want to capture in the stubbed response:
test "captures request metadata" do
Req.Test.stub(MyApp.API, fn conn ->
conn
|> Plug.Conn.put_resp_header("x-request-id", "test-123")
|> Req.Test.json(%{
"data" => %{"user" => %{"name" => "Alice"}}
})
end)
assert {:ok, result} = MyApp.API.get_user(%{id: "1"})
assert result.assigns[:request_id] == ["test-123"]
endExample: Shopify Rate Limiting via Extensions
Shopify's GraphQL API returns rate-limit and cost information in the extensions field:
{
"data": { "products": { ... } },
"extensions": {
"cost": {
"requestedQueryCost": 12,
"actualQueryCost": 10,
"throttleStatus": {
"currentlyAvailable": 980,
"maximumAvailable": 1000.0,
"restoreRate": 50.0
}
}
}
}Capture it with prepare_req/1:
defmodule MyApp.Shopify do
use Grephql,
otp_app: :my_app,
source: "priv/schemas/shopify.json",
endpoint: "https://myshop.myshopify.com/admin/api/graphql.json"
def prepare_req(req) do
Req.Request.append_response_steps(req,
capture_extensions: fn {req, resp} ->
{req, Grephql.Result.put_resp_assign(resp, :extensions, resp.body["extensions"])}
end
)
end
defgql :get_products, ~GQL"""
query GetProducts($first: Int!) {
products(first: $first) {
edges {
node {
title
}
}
}
}
"""
endThen use the captured cost data to throttle API calls:
{:ok, result} = MyApp.Shopify.get_products(%{first: 10})
throttle_status = result.assigns[:extensions]["cost"]["throttleStatus"]
throttle_status["currentlyAvailable"] #=> 980
throttle_status["restoreRate"] #=> 50.0defmodule MyApp.Shopify.RateLimiter do
def maybe_throttle(%Grephql.Result{assigns: assigns}) do
case assigns[:extensions] do
%{"cost" => %{"throttleStatus" => %{"currentlyAvailable" => available}}}
when available < 100 ->
Process.sleep(1_000)
_other ->
:ok
end
end
endAPI Reference
prepare_req/1— overridable callback on client modules, receives the%Req.Request{}before it is sentGrephql.Result.put_resp_assign/3— stores a key-value pair on the Req response for later transfer toResult.assignsGrephql.Result— the:assignsfield (default%{}) holds all values stored viaput_resp_assign/3