feat: add spec subpackage for luxd configuration specification

- Move configspec from sdk to config/spec (single source of truth)
- Contains 217 flags with types, categories, constraints
- Provides validation helpers: KnownKey, GetFlag, FlagsByCategory
- Embedded spec.json with full flag definitions
This commit is contained in:
2026-01-05 06:54:01 -08:00
parent 4d7a878c7a
commit 182bc3f6be
5 changed files with 1921 additions and 4 deletions
+2 -1
View File
@@ -14,10 +14,11 @@ require (
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
github.com/google/go-cmp v0.7.0 // indirect
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/rogpeppe/go-internal v1.14.1 // indirect
github.com/sagikazarmark/locafero v0.11.0 // indirect
github.com/sagikazarmark/locafero v0.12.0 // indirect
github.com/spf13/afero v1.15.0 // indirect
github.com/spf13/cast v1.10.0 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
+2 -3
View File
@@ -18,6 +18,7 @@ github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ=
@@ -32,9 +33,7 @@ github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc=
github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik=
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw=
github.com/sagikazarmark/locafero v0.12.0 h1:/NQhBAkUb4+fH1jivKHWusDYFjMOOKU88eegjfxfHb4=
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=
github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
+199
View File
@@ -0,0 +1,199 @@
// Copyright (C) 2022-2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
// Package configspec provides the embedded luxd configuration specification.
// This is a snapshot of the node's configuration spec for use by SDK consumers
// without requiring a dependency on the node package.
//
// The spec.json file is generated from the node using:
//
// go run github.com/luxfi/node/cmd/config dump-spec --format=json > spec.json
//
// To regenerate, run:
//
// go generate ./...
package spec
import (
_ "embed"
"encoding/json"
"sync"
)
//go:generate sh -c "cd ../../node && go run ./cmd/config dump-spec --format=json > ../sdk/configspec/spec.json"
//go:embed spec.json
var specJSON []byte
// FlagType represents the type of a configuration flag.
type FlagType string
const (
TypeBool FlagType = "bool"
TypeInt FlagType = "int"
TypeUint FlagType = "uint"
TypeUint64 FlagType = "uint64"
TypeFloat64 FlagType = "float64"
TypeDuration FlagType = "duration"
TypeString FlagType = "string"
TypeStringSlice FlagType = "string-slice"
TypeIntSlice FlagType = "int-slice"
TypeStringToString FlagType = "string-to-string"
)
// Category groups related configuration flags.
type Category string
const (
CategoryProcess Category = "process"
CategoryNode Category = "node"
CategoryDatabase Category = "database"
CategoryNetwork Category = "network"
CategoryConsensus Category = "consensus"
CategoryStaking Category = "staking"
CategoryHTTP Category = "http"
CategoryAPI Category = "api"
CategoryHealth Category = "health"
CategoryLogging Category = "logging"
CategoryThrottler Category = "throttler"
CategorySystem Category = "system"
CategoryBootstrap Category = "bootstrap"
CategoryChain Category = "chain"
CategoryProfile Category = "profile"
CategoryMetrics Category = "metrics"
CategoryGenesis Category = "genesis"
CategoryFees Category = "fees"
CategoryIndex Category = "index"
CategoryTracing Category = "tracing"
CategoryPOA Category = "poa"
CategoryDev Category = "dev"
)
// Constraints defines validation rules for a flag.
type Constraints struct {
Min interface{} `json:"min,omitempty"`
Max interface{} `json:"max,omitempty"`
Enum []string `json:"enum,omitempty"`
Pattern string `json:"pattern,omitempty"`
RequiredWith []string `json:"required_with,omitempty"`
ConflictsWith []string `json:"conflicts_with,omitempty"`
}
// FlagSpec describes a single configuration flag.
type FlagSpec struct {
Key string `json:"key"`
Type FlagType `json:"type"`
Default interface{} `json:"default,omitempty"`
Description string `json:"description"`
Category Category `json:"category"`
Deprecated bool `json:"deprecated,omitempty"`
DeprecatedMessage string `json:"deprecated_message,omitempty"`
ReplacedBy string `json:"replaced_by,omitempty"`
Required bool `json:"required,omitempty"`
Sensitive bool `json:"sensitive,omitempty"`
Constraints *Constraints `json:"constraints,omitempty"`
Since string `json:"since,omitempty"`
}
// ConfigSpec is the complete specification of all luxd configuration flags.
type ConfigSpec struct {
Version string `json:"version"`
NodeVersion string `json:"node_version"`
GeneratedAt string `json:"generated_at"`
Flags []FlagSpec `json:"flags"`
Categories map[Category]string `json:"categories"`
}
var (
cachedSpec *ConfigSpec
specOnce sync.Once
specErr error
)
// Spec returns the embedded configuration specification.
// The spec is parsed once and cached for subsequent calls.
func Spec() (*ConfigSpec, error) {
specOnce.Do(func() {
cachedSpec = &ConfigSpec{}
specErr = json.Unmarshal(specJSON, cachedSpec)
})
if specErr != nil {
return nil, specErr
}
return cachedSpec, nil
}
// MustSpec returns the embedded configuration specification or panics on error.
func MustSpec() *ConfigSpec {
s, err := Spec()
if err != nil {
panic(err)
}
return s
}
// GetFlag returns the spec for a specific flag, or nil if not found.
func (s *ConfigSpec) GetFlag(key string) *FlagSpec {
for i := range s.Flags {
if s.Flags[i].Key == key {
return &s.Flags[i]
}
}
return nil
}
// FlagsByCategory returns all flags in a specific category.
func (s *ConfigSpec) FlagsByCategory(cat Category) []FlagSpec {
var result []FlagSpec
for _, f := range s.Flags {
if f.Category == cat {
result = append(result, f)
}
}
return result
}
// DeprecatedFlags returns all deprecated flags.
func (s *ConfigSpec) DeprecatedFlags() []FlagSpec {
var result []FlagSpec
for _, f := range s.Flags {
if f.Deprecated {
result = append(result, f)
}
}
return result
}
// AllKeys returns all flag keys.
func (s *ConfigSpec) AllKeys() []string {
keys := make([]string, len(s.Flags))
for i, f := range s.Flags {
keys[i] = f.Key
}
return keys
}
// KnownKey returns true if the key is a known configuration flag.
func (s *ConfigSpec) KnownKey(key string) bool {
return s.GetFlag(key) != nil
}
// Version returns the spec version.
func Version() string {
return MustSpec().Version
}
// NodeVersion returns the node version this spec was generated from.
func NodeVersion() string {
return MustSpec().NodeVersion
}
// AllKeys returns all known configuration keys.
func AllKeys() []string {
return MustSpec().AllKeys()
}
// KnownKey checks if a key is a valid configuration flag.
func KnownKey(key string) bool {
return MustSpec().KnownKey(key)
}
+99
View File
@@ -0,0 +1,99 @@
// Copyright (C) 2022-2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package spec
import (
"testing"
)
func TestSpec(t *testing.T) {
s, err := Spec()
if err != nil {
t.Fatalf("Spec() failed: %v", err)
}
if s == nil {
t.Fatal("Spec() returned nil")
}
if s.Version == "" {
t.Error("spec version is empty")
}
if len(s.Flags) == 0 {
t.Error("spec has no flags")
}
t.Logf("Loaded spec version %s with %d flags", s.Version, len(s.Flags))
}
func TestMustSpec(t *testing.T) {
defer func() {
if r := recover(); r != nil {
t.Errorf("MustSpec() panicked: %v", r)
}
}()
s := MustSpec()
if s == nil {
t.Fatal("MustSpec() returned nil")
}
}
func TestGetFlag(t *testing.T) {
s := MustSpec()
f := s.GetFlag("network-id")
if f == nil {
t.Fatal("GetFlag returned nil for network-id")
}
if f.Key != "network-id" {
t.Errorf("got key %q, want network-id", f.Key)
}
f = s.GetFlag("nonexistent-flag-xyz")
if f != nil {
t.Error("GetFlag should return nil for non-existent flag")
}
}
func TestFlagsByCategory(t *testing.T) {
s := MustSpec()
flags := s.FlagsByCategory(CategoryConsensus)
if len(flags) == 0 {
t.Error("CategoryConsensus has no flags")
}
for _, f := range flags {
if f.Category != CategoryConsensus {
t.Errorf("flag %q has category %q, expected consensus", f.Key, f.Category)
}
}
}
func TestAllKeys(t *testing.T) {
keys := AllKeys()
if len(keys) < 100 {
t.Errorf("expected at least 100 keys, got %d", len(keys))
}
}
func TestKnownKey(t *testing.T) {
if !KnownKey("network-id") {
t.Error("network-id should be known")
}
if KnownKey("unknown-flag-xyz") {
t.Error("unknown-flag-xyz should not be known")
}
}
func TestVersion(t *testing.T) {
v := Version()
if v == "" {
t.Error("Version() returned empty string")
}
}
func TestNodeVersion(t *testing.T) {
nv := NodeVersion()
if nv == "" {
t.Error("NodeVersion() returned empty string")
}
}
+1619
View File
File diff suppressed because it is too large Load Diff