mirror of
https://github.com/luxfi/config.git
synced 2026-09-24 00:24:16 +00:00
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:
@@ -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
|
||||
|
||||
@@ -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=
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user