///|
/// The HTTP surface: a GraphiQL IDE page plus the `POST /graphql` endpoint,
/// wired as a `moonasgi` handler so moongql plugs straight into `mooncat` (or any
/// server that binds the ASGI seam). A `GET` on the endpoint serves the IDE; a
/// `POST` reads a `{ query, variables, operationName }` body, runs it through the
/// executor, and replies with the `{ data, errors }` JSON. Everything rides the
/// synchronous `run_http_app` core moonasgi exposes, so the whole thing is
/// testable on every backend through `TestClient` without an async runtime.

///|
/// The GraphiQL IDE page, wired to fetch against `endpoint`. This is the same
/// standalone GraphiQL build strawberry serves: React and GraphiQL load from a
/// CDN via an import map, and a fetcher points at the GraphQL endpoint. Splitting
/// the template around the endpoint keeps it a plain string with no interpolation
/// machinery.
pub fn graphiql_html(endpoint? : String = "/graphql") -> String {
  graphiql_head() + endpoint + graphiql_tail()
}

///|
/// Everything in the GraphiQL page up to the fetcher URL literal.
fn graphiql_head() -> String {
  let head =
    #|
    #|
    #|
    #|  
    #|  GraphiQL
    #|  
    #|  
    #|  
    #|  
    #|
    #|
    #|  
Loading GraphiQL…
#| #| #| tail } ///| /// An `application/json` error reply carrying a single `errors` entry, for the /// transport-level failures (wrong path, wrong method, unreadable body) that stop /// a request before the executor runs. fn error_response(status : Int, message : String) -> @moonasgi.Response { let errs : Array[Json] = [jobj([("message", message.to_json())])] @moonasgi.Response::json(status~, jobj([("errors", errs.to_json())])) } ///| /// Pull `query`, `variables` and `operationName` out of a parsed request body. /// A missing or non-string `query` yields `None`; `variables` defaults to empty /// and `operationName` to `None`, matching the GraphQL-over-HTTP request shape. fn parse_request(body : Json) -> (String?, Map[String, Json], String?) { match body { Object(m) => { let query = match m.get("query") { Some(String(q)) => Some(q) _ => None } let variables = match m.get("variables") { Some(Object(v)) => v _ => Map([]) } let operation_name = match m.get("operationName") { Some(String(n)) => Some(n) _ => None } (query, variables, operation_name) } _ => (None, Map([]), None) } } ///| /// Build the moonasgi request handler for a schema and its resolvers. A `GET` on /// `path` serves the GraphiQL IDE (when `graphiql` is true); a `POST` on `path` /// executes a GraphQL request and returns `{ data, errors }`. Any other path is a /// `404` and any other method a `405`, both as JSON errors. Lift it onto an /// `AsgiApp` with `graphql_app`, or drive it directly with moonasgi's /// `TestClient`. pub fn graphql_handler( schema : Schema, resolvers : Resolvers, path? : String = "/graphql", graphiql? : Bool = true, ) -> @moonasgi.Handler { fn(req : @moonasgi.Request) -> @moonasgi.Response { if req.path != path { return error_response(404, "Not Found") } match req.http_method { "GET" | "HEAD" => if graphiql { @moonasgi.Response::new( 200, [("content-type", "text/html; charset=utf-8")], @utf8.encode(graphiql_html(endpoint=path)), ) } else { error_response(405, "Method Not Allowed") } "POST" => { let body = @json.parse(@utf8.decode_lossy(req.body[:])) catch { _ => return error_response(400, "Request body is not valid JSON") } let (query, variables, operation_name) = parse_request(body) match query { None => error_response(400, "No GraphQL query found in request body") Some(q) => @moonasgi.Response::json( execute(schema, resolvers, q, variables~, operation_name~), ) } } _ => error_response(405, "Method Not Allowed") } } } ///| /// Lift the GraphQL handler onto the load-bearing `AsgiApp` a server binds to. /// This is what `mooncat` (or any moonasgi server) mounts to serve the schema /// over real HTTP; the request→response logic is shared with `graphql_handler`, /// which `TestClient` exercises without a socket. pub fn graphql_app( schema : Schema, resolvers : Resolvers, path? : String = "/graphql", graphiql? : Bool = true, ) -> @moonasgi.AsgiApp { @moonasgi.to_asgi(graphql_handler(schema, resolvers, path~, graphiql~)) }