Skip to content
internal/config
On this page

Loading and validating selfdoc.json against one declarative schema table, so every consumer reads a key that is always present with its declared default.

#internal/config

#internal/config

Package config loads and validates a project's selfdoc.json.

The whole schema is one declarative table, [Schema], and one recursive validator walks it. A loaded config is a plain map keyed by every top-level field name in [Schema] -- present unconditionally, carrying the field's default when the document omitted it -- so every consumer reads a key rather than asking whether it exists.

#Value shapes

A raw document is decoded the way Python's json module decodes it, because the validator's type rules were written against those shapes: a JSON object is a map[string]any, an array is a []any, an integer literal is an int64, a number carrying a fraction or an exponent is a float64, a string is a string, true/false is a bool, and null is a nil any. [ValidateConfig] accepts a document assembled in memory under the same rules, and additionally tolerates a plain int where an int64 would appear.

#Nested values are validated in place

The validator rewrites nested lists and objects as it descends -- a transform's result, a coerced float -- and because Go maps and slices are reference types the caller's own document sees those rewrites, exactly as the Python did. A caller that needs its raw document untouched copies it first.

#UnversionedVersion

Go go
const UnversionedVersion = "unversioned"

UnversionedVersion is what a project declaring "unversioned": true is dispatched and recorded under wherever a version string is required.

The assembly's dispatch payload, its derived membership record and the commit message a deploy writes all carry a version. A project with no public version has nothing to put there, and the empty string is not an answer: the membership record refuses an empty field, because a record that lost its version used to read the same as one that never had one. This literal is the answer -- it is never a number, so nothing can mistake it for a release, and every reader that renders a version treats it as "no version to show".

#FieldStr

Go go
const FieldStr FieldType = "str"

FieldStr accepts a JSON string.

#FieldBool

Go go
const FieldBool FieldType = "bool"

FieldBool accepts a JSON boolean.

#FieldInt

Go go
const FieldInt FieldType = "int"

FieldInt accepts a JSON integer literal, never a boolean.

#FieldFloat

Go go
const FieldFloat FieldType = "float"

FieldFloat accepts any JSON number, coercing an integer to a float.

#FieldDict

Go go
const FieldDict FieldType = "dict"

FieldDict accepts a JSON object.

#FieldList

Go go
const FieldList FieldType = "list"

FieldList accepts a JSON array.

#LintCodeValidator

Go go
var LintCodeValidator func(codes []string, source string) error

LintCodeValidator polices a config's "lint_ignore" list: it is called with the declared codes and the name of the source that declared them, and any error it returns becomes a [ConfigError].

It is a seam rather than a direct call because the lint-code registry is the lints package's to own, and this package must stay loadable without it. The command layer installs the registry-backed check at startup; while it is nil the list's codes are accepted as written, which is why installing it is part of wiring the binary and not optional.

#VALIDDeployProviders

Go go
var VALIDDeployProviders = []string{"cloudflare-pages", "github-pages"}

VALIDDeployProviders is the closed set of hosting providers a deploy block may name.

#VALIDSearchEngines

Go go
var VALIDSearchEngines = []string{"pagefind"}

VALIDSearchEngines is the closed set of engines that may answer a site's search UI. One member today, and the list is still the enumeration a config is checked against: the key is the extension point, so a second engine is a member added here rather than a new mechanism.

#Schema

Go go
var Schema = []FieldSpec{

Schema declares every top-level field of selfdoc.json, in the order a loaded config's keys are resolved. It is the single authority: the unknown-key refusal, the resolved config's key set, and the generated configuration tables all read it.

Treat it as immutable. It is a package-level slice only so the generated documentation can walk it.

#Config

Go go
type Config = map[string]any

Config is a loaded selfdoc.json: every top-level name in [Schema] is a key, carrying either the document's value or the field's default.

#ConfigError

Go go
type ConfigError struct

ConfigError is returned when selfdoc.json is present but invalid.

#FieldType

Go go
type FieldType string

FieldType is the value shape a [FieldSpec] accepts.

#FieldSpec

Go go
type FieldSpec struct

FieldSpec is the declaration of a single configuration field: its type, whether it is required, what it defaults to, and every constraint the validator applies to it.

One spec describes one field, and a spec's children (for an object) or ItemSpec (for an array, or for an object with an open key set) describe what sits inside it, so the whole schema is one value rather than a set of validation functions.

#Load

Go go
func Load(dir string) (Config, error)

Load reads and validates selfdoc.json from dir.

It returns a nil Config and a nil error when the file does not exist -- "this directory is not a selfdoc project" is an answer, not a failure. A malformed or invalid document is a [ConfigError].

#ValidateConfig

Go go
func ValidateConfig(raw any) (Config, error)

ValidateConfig validates a raw config document and returns the resolved config.

This is what [Load] runs on the parsed contents of selfdoc.json; it is separate so a config assembled in memory goes through the same rules as one read from disk. The accepted value shapes are the ones [DecodeDocument] produces.

#IsUnversioned

Go go
func IsUnversioned(config map[string]any) bool

IsUnversioned reports whether config declares the project has no public version.

It reads the declaration, not the rewritten "versions" array [postValidate] derives from it: the array is what the build addresses pages with, and the declaration is what says the project has no version at all.

#DecodeDocument

Go go
func DecodeDocument(data []byte) (any, error)

DecodeDocument decodes a selfdoc.json document into the value shapes the validator expects, matching Python's json module: an object becomes a map[string]any, an array a []any, an integer literal an int64, a number carrying a fraction or an exponent a float64, a string a string, true/false a bool, and null a nil any.

Trailing content after the first value is an error, as it is for json.load. An integer literal too large for an int64 becomes a float64 -- the one place Python's arbitrary-precision int cannot be reproduced -- so such a value fails an integer field's type check instead of passing it.

#ConfigError.Error

Go go
func (e *ConfigError) Error() string { return e.Message }
Search