graphqlschema

package
v3.0.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: 9 Imported by: 0

Documentation

Overview

Package graphqlschema holds the pinned GitLab GraphQL schema and validates documents against it.

Every GraphQL test in this repository answers the request from an httptest handler that returns whatever the test wrote, so the document itself used to be judged by nobody: our code and our fixture agreed with each other and both could be wrong about GitLab. Four registered tools shipped with green tests and documents no current instance accepts, and the same blindness let eight domains advertise a backward pagination no operation declared.

GitLab is the only party that refuses a document, and no unit test may reach it, so the schema comes here instead. cmd/gen_graphql_schema introspects a live instance and writes gitlab-schema.graphql beside source.json, which records the instance, the version it reported and the day it answered, so a reader can tell how old the pin is. The SDL is committed as text, close to a megabyte of it, because a re-pin is the one moment somebody needs to read what GitLab changed; git stores two revisions of the text in about half the space it needs for two gzip streams, which it can neither delta nor diff.

Cost

Parsing the schema takes around 200 ms for 4331 types, so it happens once per process behind a sync.Once and never per call. Validating one document against the loaded schema costs tens of microseconds, which is what makes running it inside the shared test transport affordable.

What it cannot see

The pin is a snapshot, and GitLab narrows fields in place: the securityReportFindings confidence argument and the [String!] typing of Project.vulnerabilities.severity were both valid once. A document this package accepts is one the pinned instance accepted on the day recorded in source.json, which is a far stronger statement than the mocks used to make and still not the same as one a live instance accepts today. cmd/audit_graphql_documents -schema is how that last gap is closed on demand, against a schema fetched rather than pinned.

Three limits are inherent to validating at this layer rather than at GitLab's, and are worth knowing before assuming more of a green run than it offers. A fractional number passes where Int is declared, because JSON has already made every number a float64 by the time the value is seen. A custom scalar accepts anything, so a malformed global id passes as a VulnerabilityID. And no depth or complexity limit is enforced, so a query GitLab would refuse as too expensive is accepted here. The pin also carries no deprecation marks, so it reports a document that is already broken and never one that is about to be.

Enum case is not among those limits, and used to be. gqlparser compares an enum value with strings.EqualFold, so it accepts "critical" for VulnerabilitySeverity, while GitLab answers that it expected one of INFO, UNKNOWN, LOW, MEDIUM, HIGH, CRITICAL and executes nothing. Validate walks the variables itself for that one reason.

Index

Constants

View Source
const SDLFileName = "gitlab-schema.graphql"

SDLFileName is the pinned schema's name on disk. The generator writes it and the package embeds it, so both sides name it once here.

The words are separated by a hyphen rather than an underscore because cmd/audit_doc_tool_names reads every gitlab_-prefixed identifier in the documentation as a tool name the server must register, and any page naming this file would otherwise fail that gate.

View Source
const SourceFileName = "source.json"

SourceFileName is the provenance record's name on disk, written by cmd/gen_graphql_schema beside the compressed schema.

Variables

This section is empty.

Functions

func Load

func Load(sdl []byte) (*ast.Schema, error)

Load parses an SDL document. It is exported so cmd/gen_graphql_schema can check a file it just wrote, or a committed one it did not, without going through the embedded copy.

func ParseAgainst

func ParseAgainst(schema *ast.Schema, document string) (*ast.QueryDocument, error)

ParseAgainst is ValidateDocumentAgainst returning what it parsed, so a caller can walk the fields, arguments and types a document actually depends on rather than the text it is written as.

The live re-probe compares two schemas that way. Whole-schema drift between two GitLab releases is thousands of lines and says nothing; the drift under our own selection sets is a handful of coordinates and says everything, and resolving those needs the validated document, where every field carries the definition it resolved to.

func Schema

func Schema() (*ast.Schema, error)

Schema returns the pinned GitLab schema, parsing the embedded SDL on first call and returning the same schema to every later caller.

The returned schema is read-only. gqlparser's validator never mutates it, and callers must not either, because every goroutine in the process shares this one value.

func Validate

func Validate(document string, variables map[string]any) error

Validate reports whether GitLab would accept document sent with variables.

It answers both halves of that question. The document half catches a field that does not exist, an argument the field does not accept and a variable used where its type does not fit. The variables half catches a variable sent that the operation never declared, which is the defect that let eight domains advertise backward pagination, and a value that does not fit the type it was declared as.

func ValidateDocument

func ValidateDocument(document string) error

ValidateDocument reports whether the pinned schema accepts document, ignoring variable values. This is what a static audit can ask, since a document read out of the source has no request behind it.

func ValidateDocumentAgainst

func ValidateDocumentAgainst(schema *ast.Schema, document string) error

ValidateDocumentAgainst is ValidateDocument against a schema the caller loaded rather than the pinned one.

It exists for the live re-probe. The pin can only say a document was already broken on the day it was taken, never that GitLab has narrowed something since, which is how every defect this gate was built for arose: securityReportFindings accepted confidence once, and Project.vulnerabilities.severity was [String!] once. Handing the same check a schema fetched today turns that into a failure on the day it happens.

Types

type Source

type Source struct {
	// Instance is the GraphQL endpoint that was introspected.
	Instance string `json:"instance"`
	// GitLabVersion is what that instance reported for itself, or "unknown"
	// when the introspection ran without a token: GitLab answers introspection
	// to anyone and refuses the metadata query to an anonymous caller.
	GitLabVersion string `json:"gitlab_version"`
	// GitLabRevision is the commit that instance was running, when known.
	GitLabRevision string `json:"gitlab_revision,omitempty"`
	// RetrievedAt is the UTC day the instance answered, as YYYY-MM-DD.
	RetrievedAt string `json:"retrieved_at"`
	// Types is how many types the schema beside this record loads with. It
	// is the one number that says at a glance whether a regeneration got the
	// whole schema or a truncated answer, and the loaded count rather than
	// the introspected one because the check recomputes it from the file on
	// disk and holds the record to it: the two files are one pin only while
	// they agree.
	Types int `json:"types"`
}

Source describes where the pinned schema came from, so a reader can tell how old the pin is without asking git. A schema that parses says nothing about whether the instance it was taken from still answers this way.

func ParseSource

func ParseSource(record []byte) (Source, error)

ParseSource decodes a provenance record. It is exported so the generator can check a file on disk rather than the embedded copy.

func SourceInfo

func SourceInfo() (Source, error)

SourceInfo returns the provenance of the embedded schema.

func (Source) String

func (s Source) String() string

String renders the provenance as one reportable line.

type ValidationError

type ValidationError struct {
	// Reasons is one message per validation failure, in the order gqlparser
	// reported them, followed by the variable failures this package adds.
	Reasons []string
}

ValidationError carries every reason the pinned schema refused a document.

The reasons are kept apart rather than pre-joined because the two callers present them differently: the test transport prints one per line under the operation it came from, and the document audit indents them under the constant that declares the document.

func (*ValidationError) Error

func (e *ValidationError) Error() string

Error joins the reasons into one line.

Jump to

Keyboard shortcuts

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