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 }

More tools from this site

  • claudestream Drive Claude Code from Python: run it as a subprocess and read its output as typed events, with async and sync sessions, sandbox policies, and tools you define in Python
  • claudewheel A TUI Claude Code Launcher that lets you have more than one profile, manage sessions lifecycle, pick the exact CC version, model to use (even older unlisted ones), pick which GitHub account to use, etc.
  • dirstat Fast, single-binary directory statistics CLI: every file under a tree grouped by format, with counts, sizes, and lines of code, as a colored terminal table or as JSON
  • fastware A batteries-included ASGI framework: msgspec JSON, a managed Granian server, dependency injection, SSE, WebSockets, auth, and a test client
  • go-toml-edit Zero-dep TOML editing library for Go with comment preservation
  • howmuchleft The fastest Claude Code statusline: context window, 5-hour, and weekly limit usage as three customizable gradient bars, rendering in about 6 ms
  • orxtra
  • pgdesign
  • predraw Declarative rendering pipeline: describe a scene in JSON and get SVG, PNG and WebP out, with light and dark style tokens, reusable components and text converted to path outlines
  • reposummary Turn a git repository's history into a Markdown journal: pick a time window or revision range and get a readable digest of what changed, optionally narrated by an LLM
  • rlsbl Release orchestration and project scaffolding CLI that bumps versions, validates a structured JSONL changelog, tags only the commit CI verified, and publishes to npm, PyPI, Go and more
  • safegit git wrapper CLI that gives each commit its own temporary index and retries ref updates on conflict, so concurrent agents share one repository
  • saferm Command-line replacement for rm that archives every deletion with a mandatory reason and the context it ran in, so deleted files can be listed, inspected and restored
  • strictcli
  • stricttest An always-on test-isolation floor: a pytest plugin and a Go env-hygiene module that make a test suite structurally unable to reach real credentials, the real HOME, the network, or the development repository.
  • wesktop A Python framework that turns an ASGI web app into a desktop application, serving it from a local Granian server and displaying it in a native OS window via pywebview
Search