queryapi

package
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 10, 2026 License: MIT Imports: 15 Imported by: 0

Documentation

Overview

Package queryapi implements the GLQL query gateway.

The gateway accepts GLQL — GitLab Query Language, the language behind `glql` Markdown blocks — transpiles it in-process, and routes the compiled query to the backend the transpiler selected. Results are transformed back into the GLQL result shape before being returned.

This package is a standalone library owned by group::global search. GitLab Workhorse is its first consumer: it mounts the Handler on the gateway route so that `group::source code` does not own search-team logic. Nothing here depends on Workhorse, and the library is usable from any Go HTTP server.

Why a proxy-tier gateway

Transpilation is pure, CPU-bound string work with no database access. Today it runs either in the browser (a ~2 MB wasm payload per page that wants a `glql` block) or in Puma via the gitlab_query_language gem, where it occupies a Ruby worker for its duration. Workhorse already sits in front of every request, is not GVL-bound, and is the only GitLab process that can already reach both Rails GraphQL and the graph-query backend. Resolving GLQL there means one compile, one place, and backend choice invisible to callers.

Backend routing

The transpiler reports a compile mode: `standard` yields a GraphQL document, `orbit` yields a graph_query traversal DSL body. The gateway keys its resolver registry on that mode, so which backend answered is an implementation detail of a Resolver rather than a branch in the handler and never appears in a response.

Permissions

Authorization is deliberately NOT evaluated here. The gateway rewrites the request and forwards it with the caller's original credentials untouched, so every query is authorized per request by the backend that owns the data. The gateway holds no identity and caches nothing per user, which is what keeps a cached result from outliving a revoked permission.

Index

Constants

View Source
const (
	// ModeStandard compiles GLQL to a GraphQL document.
	ModeStandard = "standard"

	// MaxRequestBytes matches the 10_000 of
	// Analytics::Glql::ParserService::MAX_INPUT_SIZE so the two endpoints
	// reject at the same boundary. The comparison is not like-for-like: Rails
	// measures the GLQL blob, the gateway measures its whole JSON envelope, so
	// the gateway is the stricter of the two. decodeRequest enforces it on the
	// inbound body; the route's withBodyLimit is a separate, outbound cap.
	MaxRequestBytes = 10000
)

Variables

This section is empty.

Functions

This section is empty.

Types

type Handler

type Handler struct {
	// contains filtered or unexported fields
}

Handler serves the query gateway endpoint.

func NewHandler

func NewHandler(transpiler Transpiler, timeout time.Duration, resolvers ...Resolver) *Handler

NewHandler builds a gateway handler over the given resolvers. A query whose compile mode has no registered resolver is refused with a contract-defined error rather than being silently routed somewhere else.

func (*Handler) ServeHTTP

func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request)

type QueryErrors

type QueryErrors struct {
	Messages []string
}

QueryErrors is returned when the backend rejected the query itself. The messages reach the caller; a resolver must not include any text that identifies which backend produced them.

func (*QueryErrors) Error

func (q *QueryErrors) Error() string

type Resolution

type Resolution struct {
	Data          json.RawMessage
	PartialErrors []string
}

Resolution is what a resolver returns on success.

PartialErrors carries GraphQL's per-field errors for a response that also returned data -- the normal shape when part of a query is unauthorized. It is separate from a returned error, which means the query produced no usable data at all. Callers must screen PartialErrors before writing them out; they are backend text.

type Resolver

type Resolver interface {
	// Mode is the compile mode this resolver serves.
	Mode() string

	// Resolve executes compiledQuery. It returns the payload to hand to
	// Transform on success. A *UpstreamResponse error carries a verbatim
	// upstream reply that must be relayed to the caller unaltered.
	Resolve(ctx context.Context, orig *http.Request, compiledQuery string, vars map[string]any) (Resolution, error)
}

Resolver executes a compiled GLQL query against one backend and returns the raw result body for the transform step.

The transpiler decides which resolver applies by way of the compile mode it reports: `standard` compiles to a GraphQL document, `orbit` compiles to a graph_query traversal DSL body. A resolver is therefore a (mode -> transport) binding, and adding a backend is a resolver registration rather than a change to the handler.

func NewGraphQLResolver

func NewGraphQLResolver(backend *url.URL, roundTripper http.RoundTripper) Resolver

NewGraphQLResolver returns the resolver for `standard`-mode queries.

type Transpiler

type Transpiler interface {
	Compile(ctx context.Context, query, compileContext string) (string, error)
	Transform(ctx context.Context, data, transformContext string) (string, error)
}

Transpiler compiles GLQL and transforms backend results. It must be safe for concurrent use.

type UpstreamResponse

type UpstreamResponse struct {
	StatusCode int
	Header     http.Header
	Body       []byte
}

UpstreamResponse is returned by a resolver when the upstream made a decision the gateway must not reinterpret — authentication, authorization, rate limiting, maintenance mode. It is relayed to the caller as-is.

func (*UpstreamResponse) Error

func (u *UpstreamResponse) Error() string

Directories

Path Synopsis
Package glqlwasm runs the GLQL transpiler WebAssembly module in-process.
Package glqlwasm runs the GLQL transpiler WebAssembly module in-process.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL