///|
/// Encode a GraphQL-over-HTTP POST body as `{ query, variables }` JSON.
fn request_body(query : String, variables : Json) -> Bytes {
let obj : Json = { "query": Json(query), "variables": variables }
@utf8.encode(obj.stringify())
}
///|
/// The `content-type` a GraphQL POST must carry, plus the media type this client
/// wants back.
fn headers(accept : String) -> Array[(String, String)] {
[("content-type", "application/json"), ("accept", accept)]
}
///|
/// The response's `content-type`, for printing.
fn ctype(resp : @moonasgi.TestResponse) -> String {
match resp.header("content-type") {
Some(h) => h
None => "?"
}
}
///|
/// The GraphQL-over-HTTP transport. `graphql_handler` turns a schema into a
/// moonasgi request handler; moonasgi's `TestClient` drives it over the synchronous
/// seam (no socket).
///
/// A POST carries an `application/json` body — one request, or an array of them
/// for a batch. `Accept` picks the response media type: ask for
/// `application/graphql-response+json` and the status separates a request error
/// (400, the document never ran) from a field error (200); ask for
/// `application/json` and everything is a 200. A GET carries the same fields in
/// its query string, and without a `query` serves the GraphiQL IDE, which
/// `graphiql_html` also renders standalone for any endpoint path.
///
/// moon run src/examples/22-http-server
#coverage.skip
fn main {
let s = @moongql.Schema::new()
let q = s.object("Query")
q.field_args("user", [("id", NonNull(Scalar("ID")))], Named("User"))
let u = s.object("User")
u.field("id", NonNull(Scalar("ID")))
u.field("name", NonNull(Scalar("String")))
let r = @moongql.Resolvers::new()
r.field("Query", "user", info => {
match info.arg("id") {
String("1") => { "id": "1", "name": "lfanke" }
_ => Json::null()
}
})
let client = @moonasgi.TestClient::new(@moongql.graphql_handler(s, r))
let body = request_body("{ user(id: \"1\") { id name } }", Json::null())
let resp = client.post("/graphql", body~, headers=headers("application/json"))
println("POST status: \{resp.status}")
println("content-type: " + ctype(resp))
println("body: " + resp.text())
let vars : Json = { "uid": "1" }
let body2 = request_body(
"query ($uid: ID!) { user(id: $uid) { name } }", vars,
)
println(
"vars body: " +
client
.post("/graphql", body=body2, headers=headers("application/json"))
.text(),
)
// Without a content type the server will not guess what the bytes are.
println("no content-type: \{client.post("/graphql", body~).status}")
let spec = client.post(
"/graphql",
body~,
headers=headers("application/graphql-response+json"),
)
println("spec media type: " + ctype(spec) + " status: \{spec.status}")
let bad = client.post(
"/graphql",
body=request_body("{ user(id: \"1\") { nope } }", Json::null()),
headers=headers("application/graphql-response+json"),
)
println("request error status: \{bad.status}")
let batch : Json = [
{ "query": "{ user(id: \"1\") { name } }" },
{ "query": "{ hero: user(id: \"1\") { id } }" },
]
println(
"batch: " +
client
.post(
"/graphql",
body=@utf8.encode(batch.stringify()),
headers=headers("application/json"),
)
.text(),
)
let got = client.get(
"/graphql?query=%7B%20user(id%3A%20%221%22)%20%7B%20name%20%7D%20%7D",
)
println("GET query: " + got.text())
let page = client.get("/graphql")
println("GET status: \{page.status}")
println("serves GraphiQL: \{page.text().contains("GraphiQL")}")
let standalone = @moongql.graphiql_html(endpoint="/api/graphql")
println(
"fetcher target present: \{standalone.contains("url: '/api/graphql'")}",
)
}