api

package
v0.6.0 Latest Latest
Warning

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

Go to latest
Published: Oct 14, 2026 License: MIT Imports: 33 Imported by: 0

Documentation

Overview

Package api is `brevis-sql-api`: declared, read-only SQL served over HTTP.

THE FILE IS THE CONTRACT, as it is for the gateway: every endpoint, its parameters and its SQL, refused at load when any of it cannot be honoured. A server that starts on a file it half understood answers a readiness probe and then a caller's first request with something nobody declared.

Index

Constants

View Source
const (

	// EnvLocal is the value that leaves the API open. The engine's word.
	EnvLocal = "local"

	// LocalTenant is the tenant every request is, locally and without auth.
	LocalTenant = "local"
)
View Source
const DefaultMaxRows = 10000

DefaultMaxRows is the row ceiling when the file names none.

View Source
const ManifestVersion = 1

ManifestVersion is the shape `describe` prints. An engine that reads another refuses it by number.

Variables

This section is empty.

Functions

func Describe

func Describe(path string, cfg *Config, reg *connections.Registry) ([]byte, error)

Describe prints the manifest of a loaded file. It OPENS NOTHING: the targets come from the registry's declarations, which is why it runs where the API's credentials do not -- in a publish Job, beside the gateway's.

func KeyLine

func KeyLine(key, tenant string) (string, error)

KeyLine is the line a key list holds for one key: what `brevis-sql-api key hash` prints, so nobody hand-rolls the hash and gets the encoding wrong.

func Refuse added in v0.6.0

func Refuse(msg string) error

Refuse is how a before hook answers 400. The message goes to the caller as written, so it is the hook author's to keep free of anything private.

Types

type After added in v0.6.0

type After func(ctx context.Context, r *Result) (any, error)

After returns what becomes the answer's `data`, marshalled as JSON.

IT MUST BE A FUNCTION OF ITS RESULT. Its output is what the cache keeps, so a hook that reads the clock or the network answers every later caller with what was true for the first one.

type Auth

type Auth struct {
	// Type is `bearer`.
	Type string `yaml:"type"`

	// KeysFrom names the ENVIRONMENT VARIABLE holding the key list -- one
	// `<sha256 of the key, hex>=<tenant>` per line. The file is in git, and
	// the variable holds no key either: a hash is what lets an environment
	// dump, a CI log or a `kubectl describe` show this variable without
	// handing anybody a way in.
	KeysFrom string `yaml:"keys_from"`
}

Auth is who may read, and as which tenant.

THE GATEWAY'S RULE, AND ONE MORE. BREVIS_ENV=local leaves the API open and outside it the process refuses to start without a key list -- spelled the way the gateway and the engine spell it, so an operator learns one word. The addition is the TENANT: every key belongs to one, and that tenant is bound into every query as `{{ tenant }}`. A caller never says which tenant it is, so it can never say a different one.

type Before added in v0.6.0

type Before func(ctx context.Context, r *Request) error

Before runs before the parameters are read. Nil goes on; Refuse(msg) answers 400 with msg; any other error answers 500, its text kept for the audit line and never sent.

type ByteSize

type ByteSize int64

ByteSize is a number of bytes, written `1GiB`, `512MiB` or a bare number.

func (ByteSize) String

func (b ByteSize) String() string

func (*ByteSize) UnmarshalYAML

func (b *ByteSize) UnmarshalYAML(node *yaml.Node) error

UnmarshalYAML reads binary units only. `1GB` is refused rather than read as either 10^9 or 2^30: a ceiling on money should not be off by 7% depending on which the reader assumed.

type CacheConfig

type CacheConfig struct {
	Type     string        `yaml:"type"`
	AddrFrom string        `yaml:"addr_from"`
	TTL      time.Duration `yaml:"ttl"`
	MaxEntry ByteSize      `yaml:"max_entry"`
}

CacheConfig is where answers are kept, and for how long at most.

type Config

type Config struct {
	Name       string `yaml:"name"`
	Connection string `yaml:"connection"`

	// ConnectionID is where the connection reaches, from the registry: part
	// of every cache key, so two deployments' answers never meet.
	ConnectionID string `yaml:"-"`

	// Timezone is the IANA zone `period` resolves in. UTC when absent.
	Timezone  string      `yaml:"timezone"`
	Listen    Listen      `yaml:"listen"`
	Endpoints []*Endpoint `yaml:"endpoints"`

	// Params are definitions an endpoint's parameter can name with `ref`.
	Params map[string]Param `yaml:"params"`

	// Limits bound every request; see Limits.
	Limits Limits `yaml:"limits"`

	// Cache keeps answers until a model they read is rebuilt. Absent, every
	// request reads the warehouse.
	Cache *CacheConfig `yaml:"cache"`

	// CacheKV is where the cache is kept; nil opens Cache.AddrFrom.
	CacheKV cache.KV `yaml:"-"`

	// Dialect and Open are the connection's, resolved from the registry at
	// load: a file naming a connection nobody declared is refused there,
	// not at the first request.
	Dialect dialect.Dialect                             `yaml:"-"`
	Open    func(context.Context) (dialect.Conn, error) `yaml:"-"`

	// MaxRows is the file-wide row ceiling.
	MaxRows int `yaml:"-"`

	// Metrics is where the Prometheus exposition listens.
	Metrics struct {
		Addr string `yaml:"addr"`
	} `yaml:"metrics"`

	// Audit receives one JSON line per request. Nil writes none.
	Audit io.Writer `yaml:"-"`

	// Env is BREVIS_ENV, read by the binary and never from the file: a file
	// that could declare itself local would be a file that turns off
	// authentication.
	Env string `yaml:"-"`
}

Config is one endpoint file, loaded and checked.

func Load

func Load(path string, reg *connections.Registry, hooks *Hooks) (*Config, error)

Load reads an endpoint file and resolves its connection in the registry, and its hooks in hooks -- nil is a binary with none.

type Endpoint

type Endpoint struct {
	Name    string           `yaml:"name"`
	Path    string           `yaml:"path"`
	Params  map[string]Param `yaml:"params"`
	SQL     string           `yaml:"sql"`
	SQLFile string           `yaml:"sql_file"`

	// Limits are this endpoint's, each at most the file's.
	Limits *Limits `yaml:"limits"`

	// Tenant is `none` for an endpoint whose data is the same for every
	// tenant. Absent, the SQL must use `{{ tenant }}`.
	Tenant string `yaml:"tenant"`

	// Hook names this endpoint's before and after, registered in the binary.
	Hook *HookNames `yaml:"hook"`

	// MaxRows is this endpoint's ceiling; zero takes the file's.
	MaxRows int `yaml:"-"`

	Template *render.Template `yaml:"-"`
	// contains filtered or unexported fields
}

Endpoint is one declared GET path.

func (*Endpoint) Reads

func (e *Endpoint) Reads() []string

Reads are the models the endpoint's SQL can read, named as their generations are keyed.

type HookNames added in v0.6.0

type HookNames struct {
	Before *string `yaml:"before"`
	After  *string `yaml:"after"`
}

HookNames is what an endpoint's `hook:` says: a name in this binary's registry for each, both optional.

type Hooks added in v0.6.0

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

Hooks is what this binary knows how to run.

COMPILED IN AND NAMED BY THE FILE, as the gateway's are, and for its measurements: a Go function is 93 ns/op against Starlark's 959, nothing is added to the module, and `plugin.Open` returns "not implemented" under the CGO_ENABLED=0 every image here is built with. The cost, said where it is paid: a new hook is a rebuild and a deploy, not a config change.

TWO REGISTRIES, because a before and an after take different arguments: a name found in the wrong one is a hook that cannot be called.

func NewHooks added in v0.6.0

func NewHooks() *Hooks

func (*Hooks) MustRegisterAfter added in v0.6.0

func (h *Hooks) MustRegisterAfter(name string, fn After)

MustRegisterAfter is RegisterAfter for a main with nowhere to return an error.

func (*Hooks) MustRegisterBefore added in v0.6.0

func (h *Hooks) MustRegisterBefore(name string, fn Before)

MustRegisterBefore is RegisterBefore for a main with nowhere to return an error.

func (*Hooks) RegisterAfter added in v0.6.0

func (h *Hooks) RegisterAfter(name string, fn After) error

RegisterAfter adds one, on RegisterBefore's terms.

func (*Hooks) RegisterBefore added in v0.6.0

func (h *Hooks) RegisterBefore(name string, fn Before) error

RegisterBefore adds one. A duplicate name is REFUSED rather than overwritten: a silently replaced registration is the wrong hook running in production.

type Limits

type Limits struct {
	Timeout        time.Duration `yaml:"timeout"`
	MaxRows        int           `yaml:"max_rows"`
	MaxBytesBilled ByteSize      `yaml:"max_bytes_billed"`

	// MaxResponseBytes bounds one answer's JSON. max_rows bounds how many
	// rows; this bounds how wide they are, which nothing did.
	MaxResponseBytes ByteSize `yaml:"max_response_bytes"`

	// MaxInFlight is how many requests the whole API may have waiting on
	// the warehouse at once -- each holds a connection. File-wide only.
	MaxInFlight int `yaml:"max_in_flight"`
}

Limits bound one request: how long, how many rows, how many bytes billed.

FILE-WIDE, AND AN ENDPOINT MAY ONLY LOWER THEM. The file's numbers are what whoever deploys the API agreed to; an endpoint that could raise its own would make the file's a suggestion.

type Listen

type Listen struct {
	Addr string `yaml:"addr"`
	Auth *Auth  `yaml:"auth"`
}

Listen is where the API answers.

type Manifest

type Manifest struct {
	Version    int                `json:"version"`
	Kind       string             `json:"kind"`
	Name       string             `json:"name"`
	Connection string             `json:"connection"`
	Dialect    string             `json:"dialect"`
	Cache      *ManifestCache     `json:"cache,omitempty"`
	Endpoints  []ManifestEndpoint `json:"endpoints"`
	Notes      map[string]string  `json:"notes,omitempty"`
}

Manifest is an API file as it runs, for the console: what each endpoint takes and reads, never how it reads it.

NO SQL TEXT. It is in the customer's repository, and what an operator acts on is which models an endpoint reads -- a model's page shows the endpoint beside its other consumers. NO VALUE OF ANY VARIABLE: a `*_from` is published as the name it holds. AND ONLY THE COMMENTS THEIR AUTHOR MARKED with `#:`, the gateway's rule: a comment is free text, and a secret ends up in one in ways no pattern recognises.

type ManifestCache

type ManifestCache struct {
	Type     string `json:"type"`
	AddrFrom string `json:"addr_from"`
	TTL      string `json:"ttl"`
	MaxEntry int64  `json:"max_entry"`
}

ManifestCache is the cache's shape, by the variable's name.

type ManifestEndpoint

type ManifestEndpoint struct {
	Name   string            `json:"name"`
	Path   string            `json:"path"`
	Tenant string            `json:"tenant"`
	Params []ManifestParam   `json:"params"`
	Limits ManifestLimits    `json:"limits"`
	Reads  []string          `json:"reads"`
	Notes  map[string]string `json:"notes,omitempty"`
}

ManifestEndpoint is one endpoint, its parameters resolved.

type ManifestLimits

type ManifestLimits struct {
	Timeout        string `json:"timeout"`
	MaxRows        int    `json:"max_rows"`
	MaxBytesBilled int64  `json:"max_bytes_billed"`
}

ManifestLimits are an endpoint's effective limits.

type ManifestParam

type ManifestParam struct {
	Name     string   `json:"name"`
	Type     string   `json:"type"`
	Default  *string  `json:"default,omitempty"`
	Required bool     `json:"required,omitempty"`
	Values   []string `json:"values,omitempty"`
	Min      *int64   `json:"min,omitempty"`
	Max      *int64   `json:"max,omitempty"`
	MaxLen   int      `json:"max_len,omitempty"`
}

ManifestParam is one parameter as a caller sees it.

type Param

type Param struct {
	Type string `yaml:"type"`

	// MaxLen bounds a string, in characters. Required for one.
	MaxLen int `yaml:"max_len"`

	// Min and Max bound an int.
	Min *int64 `yaml:"min"`
	Max *int64 `yaml:"max"`

	// Values are an enum's, each an identifier: an enum is the one value
	// written into the SQL text, and an identifier is what makes that safe.
	Values []string `yaml:"values"`

	// Default stands in for an omitted value, and is parsed exactly as a
	// sent one is -- so a default its own type cannot hold is refused at
	// load.
	Default *string `yaml:"default"`

	// Required refuses a request that omits the parameter.
	Required bool `yaml:"required"`

	// Ref names a definition in the file's `params:`, and takes nothing
	// else.
	Ref string `yaml:"ref"`
}

Param is one query-string parameter an endpoint takes.

FIVE TYPES AND EACH ONE NARROW. A parameter is a filter somebody outside sends, and every type is the smallest set of strings that can mean what it says: an int is base-10 digits, a date is YYYY-MM-DD and nothing an instant-parser would also accept, a bool is `true` or `false`, an enum is one of the values the file lists. Anything looser is a second way to spell a value, and the cache key, the audit line and the author all read the first.

type Request added in v0.6.0

type Request struct {
	Endpoint string
	Tenant   string
	Query    url.Values
	// Header is a copy, without Authorization.
	Header http.Header
}

Request is what a before hook sees of one call. Query is the caller's query string, and the only field whose changes are kept: what the hook leaves there is validated exactly as if the caller had sent it, so a hook can translate a value but cannot slip past a type, a tenant or a declaration.

type Result added in v0.6.0

type Result struct {
	Endpoint string
	Tenant   string
	// Params are the values the SQL was rendered with, typed.
	Params    map[string]any
	Columns   []string
	Rows      [][]any
	Truncated bool
}

Result is what an after hook sees: the rows, already cut by max_rows.

type Server

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

Server answers the endpoints of one file.

func New

func New(cfg *Config) (*Server, error)

New builds the handler, or refuses a configuration it cannot serve.

func (*Server) Keys

func (s *Server) Keys() (keys, tenants int)

Keys is how many keys and tenants the API accepts; zero and zero means it is open, which only BREVIS_ENV=local allows.

func (*Server) Metrics

func (s *Server) Metrics() http.Handler

Metrics is the Prometheus exposition, for its own port: never the API's, which is public by design, and a /metrics on it would publish every endpoint's traffic.

func (*Server) ServeHTTP

func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request)

Directories

Path Synopsis
Package cache holds an endpoint's answers, compressed, until a model they read is rebuilt.
Package cache holds an endpoint's answers, compressed, until a model they read is rebuilt.
Package render turns an endpoint's SQL template into a statement and its arguments, and never writes a caller's value into the statement.
Package render turns an endpoint's SQL template into a statement and its arguments, and never writes a caller's value into the statement.

Jump to

Keyboard shortcuts

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