Documentation
¶
Overview ¶
Package dalgo2http exposes read-only HTTP/JSON endpoints as DALgo collections.
The adapter is declarative and fails closed: a query runs only when every condition can be pushed to the endpoint. See README.md for the constraints.
Index ¶
- Variables
- func ContextWithProvenanceObserver(ctx context.Context, obs Observer) context.Context
- func NewDB(cfg Config) (dal.DB, error)
- func Record(ctx context.Context, client *http.Client, coll Collection, ...) (path string, err error)
- func SnapshotKey(collection string, params map[string]string) string
- type Capabilities
- type CapabilitiesProvider
- type Collection
- type Config
- type Method
- type Mode
- type Observer
- type Param
- type ParamLocation
- type Provenance
- type Recorder
- type Source
Constants ¶
This section is empty.
Variables ¶
var ( // ErrInvalidConfig indicates a Collection or Config value failed // validation (LoadConfigYAML, LoadConfigJSON or NewDB). ErrInvalidConfig = errors.New("dalgo2http: invalid config") // ErrUnknownCollection indicates a record.Key or query named a collection // that is not declared in the Config. ErrUnknownCollection = errors.New("dalgo2http: unknown collection") // ErrMissingParam indicates a request could not be built because a // required URL template parameter had no value. ErrMissingParam = errors.New("dalgo2http: missing required parameter") // ErrUpstream indicates a live request failed for a reason snapshot // fallback treats as transient: a network error, a timeout, or a 5xx / // 429 response. ErrUpstream = errors.New("dalgo2http: upstream request failed") // ErrUpstreamClient indicates a live request received a 4xx response. // This is treated as a caller/config error, not a transient failure, so // it never triggers snapshot fallback. ErrUpstreamClient = errors.New("dalgo2http: upstream rejected the request") // ErrSnapshotMiss indicates no recorded snapshot exists for a request. ErrSnapshotMiss = errors.New("dalgo2http: no snapshot available") // ErrRedirectNotAllowed indicates a live request received a redirect // response. Redirects are disabled by default (Phase 1 HTTP bounds: // "Redirects are disabled for the demo") — a redirecting endpoint is a // caller/config problem (the descriptor should target the final host // directly), never a transient failure, so doLiveFetch reports it // wrapping ErrUpstreamClient, never ErrUpstream: it is never // fallback-eligible. ErrRedirectNotAllowed = errors.New("dalgo2http: redirects are not allowed") // ErrResponseTooLarge indicates a live response body exceeded // maxBodyBytes. Reported explicitly rather than silently truncated // (which would otherwise fail JSON decoding downstream with a // misleading "unexpected end of JSON input"). ErrResponseTooLarge = errors.New("dalgo2http: response body exceeds the size limit") // ErrAddressBlocked indicates the guarded dialer refused to connect to a // resolved address because it is private, loopback, link-local or a // metadata-service address (Phase 1 HTTP bounds: "Deny private, // loopback, link-local and metadata-service addresses"). A caller/config // problem, not a transient failure. ErrAddressBlocked = errors.New("dalgo2http: target address is blocked (private/loopback/link-local/metadata)") )
Sentinel errors this adapter returns. All are typed so callers can use errors.Is; several also wrap dal.ErrNotSupported (see descriptor.go and query.go) so a caller that only checks for dal.ErrNotSupported still sees the fail-closed refusal.
Functions ¶
func ContextWithProvenanceObserver ¶
ContextWithProvenanceObserver returns a context that, when passed to a dalgo2http database method, calls obs with that call's Provenance before the method returns. See also Recorder for a ready-made Observer that keeps the last value for the caller to read back afterwards.
func NewDB ¶
NewDB validates cfg and returns a dal.DB backed by it. cfg.Client defaults to a guarded client (see security.go's newDefaultClient — a custom DialContext rejecting private/loopback/link-local/metadata addresses, and redirects disabled) when nil, per the Phase 1 HTTP bounds; cfg.Mode defaults to ModeLiveThenSnapshot when empty.
func Record ¶
func Record(ctx context.Context, client *http.Client, coll Collection, params map[string]string, dir string) (path string, err error)
Record performs one live GET for coll with params — ignoring Config.Mode entirely, it always calls the network — and writes the response as a snapshot fixture file under dir, named by SnapshotKey. It is a development/test tool for building fixtures (see examples/), not part of request-serving at runtime.
func SnapshotKey ¶
SnapshotKey returns the deterministic file name a recorded snapshot for collection with the given request params is stored under: the collection name followed by its params sorted by key, so the same logical request always resolves to the same file regardless of map iteration order.
Types ¶
type Capabilities ¶
type Capabilities struct {
Collection string
// Fields lists the declared parameter fields an equality condition may
// target and have pushed into the request.
Fields []string
// Operators lists the dal.Operator values usable against Fields.
// Equality is always supported (it is the only operator ever pushed into
// the URL); the ordering/inequality operators appear only when
// ClientSideFilter is set, since those are evaluated in memory after a
// broader fetch, never pushed down.
Operators []dal.Operator
// SupportsGet reports whether dal.DB.Get/Exists work for this
// collection: true only when KeyField is itself a declared Param.
SupportsGet bool
// ClientSideFilter mirrors Collection.ClientSideFilter.
ClientSideFilter bool
}
Capabilities describes what a policy layer may push down to one collection, so it can decide whether a query is answerable before executing it (see query.go for the fail-closed enforcement this describes).
type CapabilitiesProvider ¶
type CapabilitiesProvider interface {
Capabilities(collection string) (Capabilities, error)
}
CapabilitiesProvider is the optional capability a dalgo2http database implements. dal.NewDB decorates the Backend passed to it, so a plain type assertion on a dal.DB does not see Capabilities; use dal.As[CapabilitiesProvider](db) instead, per the convention dal.As documents (the same one dbschema.SchemaReader and ddl.SchemaModifier use).
type Collection ¶
type Collection struct {
// Name is the collection name a record.Key or dal.Query.From() names.
Name string `yaml:"name" json:"name"`
// URLTemplate is the request URL, with {name} placeholders for every
// declared Param that is substituted directly into the template (a path
// segment, or a value embedded in a literal query string such as
// "...?symbols={to}"). A Param declared with ParamLocation "query" whose
// name does NOT appear in the template is instead appended as an extra
// "?name=value" query parameter when a value is supplied.
URLTemplate string `yaml:"urlTemplate" json:"urlTemplate"`
// Method is the HTTP method. Empty means MethodGET; any other value
// fails validation.
Method Method `yaml:"method,omitempty" json:"method,omitempty"`
// Params declares every field a dal.Query equality condition, or a
// record.Key ID via KeyField, may be pushed down as.
Params map[string]Param `yaml:"params,omitempty" json:"params,omitempty"`
// RowsPath is the dot-separated JSON path (object-field traversal only)
// to the row array in the response body. Empty means the root of the
// response IS the rows value: a JSON array of row objects, or a single
// JSON object treated as one row.
RowsPath string `yaml:"rowsPath,omitempty" json:"rowsPath,omitempty"`
// KeyField is the row field whose value becomes a record.Key's ID.
// dal.DB.Get/Exists for this collection only work when KeyField is also
// a declared Param (see query.go); otherwise Get/Exists fail closed with
// an error wrapping dal.ErrNotSupported, and only ExecuteQueryToRecords*
// is usable.
KeyField string `yaml:"keyField" json:"keyField"`
// Headers maps an HTTP header name to the name of an environment
// variable holding its value. A variable that is unset or empty at
// request time means the header is simply not sent.
Headers map[string]string `yaml:"headers,omitempty" json:"headers,omitempty"`
// Timeout bounds one request to this collection's endpoint. Zero means
// no adapter-imposed timeout beyond the context's own deadline.
//
// This field is not (de)serialized directly: LoadConfigYAML/LoadConfigJSON
// read it from a "timeout" string (e.g. "10s", parsed with
// time.ParseDuration) since YAML/JSON have no native duration type.
Timeout time.Duration `yaml:"-" json:"-"`
// ClientSideFilter opts a collection into fetching a broader result than
// a dal.Query's Where() strictly asks for, and filtering the remainder
// in memory. It exists ONLY for public, non-confidential reference data:
// the point of failing closed by default (see query.go) is that an
// access-policy predicate that cannot be pushed down must refuse, not
// silently fetch a superset and hide the extra rows after the fact. Set
// this only on a collection where over-fetching cannot leak anything a
// caller was not already allowed to see.
ClientSideFilter bool `yaml:"clientSideFilter,omitempty" json:"clientSideFilter,omitempty"`
// InsecureAllowLoopback is a TEST-ONLY escape hatch, never intended for a
// real descriptor. Setting it relaxes two of the Phase 1 HTTP bounds for
// THIS collection alone, and only when URLTemplate's host is literally a
// loopback address (127.0.0.1, ::1, or "localhost" — validate() rejects
// it otherwise even with this set): (1) URLTemplate may use http://
// instead of https://; (2) the default guarded dialer (see
// security.go's guardedDialContext) permits dialing that loopback
// address. It exists only so this package's own test suite — and a
// consumer's — can exercise real HTTP against an httptest.Server, which
// always speaks plain HTTP on 127.0.0.1. It is deliberately NOT
// (de)serializable from LoadConfigYAML/LoadConfigJSON (see collectionFile
// in config.go): a production descriptor loaded from a file can never
// enable it, only Go code building a Collection literal directly can.
// Every other blocked address class (private, link-local, metadata,
// multicast, unspecified) stays blocked even with this set.
InsecureAllowLoopback bool `yaml:"-" json:"-"`
}
Collection declaratively describes one read-only HTTP/JSON source: the endpoint to call, which fields of a dal.Query can be pushed into it, where the rows live in the JSON response, and which field identifies a row's dal.DB record key.
A Collection carries no secrets: Headers maps a header name to the name of an environment variable dalgo2http reads at request time, never a literal value (see README.md "Design constraints").
type Config ¶
type Config struct {
Collections []Collection
// Client performs live HTTP requests. Nil means http.DefaultClient.
Client *http.Client
// Snapshots is an optional read-only store of recorded fixtures, keyed
// by SnapshotKey(collection, params). Nil means no snapshot fallback is
// possible regardless of Mode. Use Record to populate a directory that
// can then be wrapped with os.DirFS (or embed.FS for shipped fixtures).
Snapshots fs.FS
// Mode governs live-vs-snapshot behaviour. Empty means
// ModeLiveThenSnapshot.
Mode Mode
}
Config configures a dalgo2http database: the collections it serves, the HTTP client and optional recorded-snapshot store it reads through, and the Mode governing live-vs-snapshot behaviour.
func LoadConfigJSON ¶
LoadConfigJSON parses a Config from JSON. See LoadConfigYAML.
func LoadConfigYAML ¶
LoadConfigYAML parses a Config from YAML in the shape documented on configFile/collectionFile (see also the example descriptors under examples/), and validates it exactly as NewDB does.
type Method ¶
type Method string
Method is the HTTP method a Collection is fetched with. v0.x supports GET only — see README.md "Design constraints".
const MethodGET Method = "GET"
MethodGET is the only Method a Collection may declare.
type Mode ¶
type Mode string
Mode selects where a collection's rows come from.
const ( // ModeLive always fetches live and never falls back to a snapshot: a // live failure is returned to the caller as-is. ModeLive Mode = "live" // ModeSnapshot never calls the network: every read is served from // Config.Snapshots, failing with ErrSnapshotMiss when none exists. ModeSnapshot Mode = "snapshot" // ModeLiveThenSnapshot (the default — see NewDB) fetches live and, only // when the live request fails for a transient reason (network error, // timeout, or a 5xx/429 response — never a 4xx, which is treated as a // caller/config error) AND Config.Snapshots has a matching snapshot, // falls back to it. This is the behaviour item 4 of the stream brief // describes; naming it as its own Mode (rather than making it the only // behaviour) lets a caller pin ModeLive for "never serve stale data" or // ModeSnapshot for fully offline runs. ModeLiveThenSnapshot Mode = "live-then-snapshot" )
type Observer ¶
type Observer func(Provenance)
Observer is called once per collection read (Get, Exists, or a query) with that read's Provenance.
type Param ¶
type Param struct {
Location ParamLocation `yaml:"location" json:"location"`
}
Param declares one substitutable field of a Collection's URL template.
type ParamLocation ¶
type ParamLocation string
ParamLocation says where a declared parameter's value is placed in the request: substituted into a {name} placeholder in the path portion of the URL template, or attached as a query-string value (either by substituting a {name} placeholder embedded in the template's query string, or, if the name never appears in the template, appended as an extra query parameter).
const ( ParamPath ParamLocation = "path" ParamQuery ParamLocation = "query" )
type Provenance ¶
Provenance describes where one collection read's rows came from.
type Recorder ¶
type Recorder struct {
// contains filtered or unexported fields
}
Recorder captures the Provenance of the most recent call made with its context, so a caller that just wants "was that live or snapshot?" back after a call does not have to write its own Observer.
func NewRecorder ¶
func NewRecorder() *Recorder
NewRecorder creates a Recorder with no observed Provenance yet.
func (*Recorder) Last ¶
func (r *Recorder) Last() (Provenance, bool)
Last returns the most recently observed Provenance, and whether any call has been observed yet.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
examples
|
|
|
countries
Package countries is a dalgo2http descriptor for CountriesNow's keyless currency-by-country lookup, one of the two example sources DataTug's demo knowledge project uses to satisfy REQ:http-reference-source (public, keyless, read-only reference data — "some public sources like countries or currency rates", founder ruling 2026-09-09).
|
Package countries is a dalgo2http descriptor for CountriesNow's keyless currency-by-country lookup, one of the two example sources DataTug's demo knowledge project uses to satisfy REQ:http-reference-source (public, keyless, read-only reference data — "some public sources like countries or currency rates", founder ruling 2026-09-09). |
|
frankfurter
Package frankfurter is a dalgo2http descriptor for the Frankfurter exchange-rate API, the second of the two example sources DataTug's demo knowledge project uses to satisfy REQ:http-reference-source ("currency rates", founder ruling 2026-09-09).
|
Package frankfurter is a dalgo2http descriptor for the Frankfurter exchange-rate API, the second of the two example sources DataTug's demo knowledge project uses to satisfy REQ:http-reference-source ("currency rates", founder ruling 2026-09-09). |