From 47d010760510e9abad9566f65e4b3bde99c23498 Mon Sep 17 00:00:00 2001 From: Ryan Tinianov Date: Fri, 7 Aug 2026 10:00:09 -0400 Subject: [PATCH 1/2] Add a config flags to standardize how we do things, TBD, should we have common structs for things like DB, OCR, etc? EVM and the like would then go in chainlink-evm. --- go.mod | 10 +- go.sum | 21 +- pkg/config/configdoc/configdoc.go | 78 +- pkg/config/configdoc/format.go | 53 ++ pkg/config/configdoc/toml.go | 105 +++ pkg/config/configdoc/toml_test.go | 143 ++++ pkg/config/flags/docs.go | 499 ++++++++++++ pkg/config/flags/flags.go | 832 +++++++++++++++++++ pkg/config/flags/flags_test.go | 1173 +++++++++++++++++++++++++++ pkg/config/flags/options.go | 129 +++ pkg/config/flags/tmp/docs/CONFIG.md | 122 +++ pkg/config/flags/tmp/main.go | 158 ++++ pkg/config/flags/tmp/tmp | Bin 0 -> 12250866 bytes pkg/config/flags/walk.go | 172 ++++ 14 files changed, 3466 insertions(+), 29 deletions(-) create mode 100644 pkg/config/configdoc/format.go create mode 100644 pkg/config/configdoc/toml.go create mode 100644 pkg/config/configdoc/toml_test.go create mode 100644 pkg/config/flags/docs.go create mode 100644 pkg/config/flags/flags.go create mode 100644 pkg/config/flags/flags_test.go create mode 100644 pkg/config/flags/options.go create mode 100644 pkg/config/flags/tmp/docs/CONFIG.md create mode 100644 pkg/config/flags/tmp/main.go create mode 100755 pkg/config/flags/tmp/tmp create mode 100644 pkg/config/flags/walk.go diff --git a/go.mod b/go.mod index 64b610c6d2..05b9ec8abd 100644 --- a/go.mod +++ b/go.mod @@ -56,6 +56,8 @@ require ( github.com/smartcontractkit/grpc-proxy v0.0.0-20240830132753-a7e17fec5ab7 github.com/smartcontractkit/libocr v0.0.0-20250912173940-f3ab0246e23d github.com/spf13/cobra v1.8.1 + github.com/spf13/pflag v1.0.10 + github.com/spf13/viper v1.21.0 github.com/stretchr/testify v1.11.1 go.opentelemetry.io/contrib/bridges/prometheus v0.68.0 go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 @@ -101,6 +103,7 @@ require ( github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cloudevents/sdk-go/v2 v2.16.1 // indirect github.com/fatih/color v1.18.0 // indirect + github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/gabriel-vasile/mimetype v1.4.8 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect @@ -143,10 +146,14 @@ require ( github.com/prometheus/procfs v0.20.1 // indirect github.com/rogpeppe/go-internal v1.14.1 // indirect github.com/ryanuber/go-glob v1.0.0 // indirect + github.com/sagikazarmark/locafero v0.11.0 // indirect github.com/sanity-io/litter v1.5.5 // indirect github.com/sethvargo/go-retry v0.3.0 // indirect - github.com/spf13/pflag v1.0.5 // indirect + github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect + github.com/spf13/afero v1.15.0 // indirect + github.com/spf13/cast v1.10.0 // indirect github.com/stretchr/objx v0.5.2 // indirect + github.com/subosito/gotenv v1.6.0 // indirect github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect github.com/x448/float16 v0.8.4 // indirect github.com/zeebo/xxh3 v1.0.2 // indirect @@ -155,6 +162,7 @@ require ( go.opentelemetry.io/proto/otlp v1.10.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.yaml.in/yaml/v2 v2.4.4 // indirect + go.yaml.in/yaml/v3 v3.0.4 // indirect golang.org/x/mod v0.36.0 // indirect golang.org/x/net v0.55.0 // indirect golang.org/x/sys v0.45.0 // indirect diff --git a/go.sum b/go.sum index 473214a72e..916230c125 100644 --- a/go.sum +++ b/go.sum @@ -59,6 +59,10 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7 github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= +github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM= @@ -264,6 +268,8 @@ github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7 github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= +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/sanity-io/litter v1.5.5 h1:iE+sBxPBzoK6uaEP5Lt3fHNgpKcHXc/A2HGETy0uJQo= github.com/sanity-io/litter v1.5.5/go.mod h1:9gzJgR2i4ZpjZHsKvUXIRQVk7P+yM3e+jAF7bU2UI5U= github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6NgVqpn3+iol9aGu4= @@ -298,10 +304,19 @@ github.com/smartcontractkit/grpc-proxy v0.0.0-20240830132753-a7e17fec5ab7 h1:12i github.com/smartcontractkit/grpc-proxy v0.0.0-20240830132753-a7e17fec5ab7/go.mod h1:FX7/bVdoep147QQhsOPkYsPEXhGZjeYx6lBSaSXtZOA= github.com/smartcontractkit/libocr v0.0.0-20250912173940-f3ab0246e23d h1:LokA9PoCNb8mm8mDT52c3RECPMRsGz1eCQORq+J3n74= github.com/smartcontractkit/libocr v0.0.0-20250912173940-f3ab0246e23d/go.mod h1:Acy3BTBxou83ooMESLO90s8PKSu7RvLCzwSTbxxfOK0= +github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw= +github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U= +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= +github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= +github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU= +github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= @@ -317,6 +332,8 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= +github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc= @@ -386,6 +403,8 @@ go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc= go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ= go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ= +go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= diff --git a/pkg/config/configdoc/configdoc.go b/pkg/config/configdoc/configdoc.go index c4e6ab6b49..a9a8009566 100644 --- a/pkg/config/configdoc/configdoc.go +++ b/pkg/config/configdoc/configdoc.go @@ -11,17 +11,28 @@ import ( const ( FieldDefault = "# Default" FieldExample = "# Example" + // FieldDocsOnly marks a field that is documented but left out of every example - both the + // document's example config and the code block of the table it belongs to. Use it for a + // setting that only applies in a mode the examples do not show, so that a reader can copy + // any example verbatim and get a configuration that works. + FieldDocsOnly = "# Docs only" TokenAdvanced = "**ADVANCED**" ) // Generate returns MarkDown documentation generated from the TOML string. -// - Each field but include a trailing comment of either FieldDefault or FieldExample. +// - Each field but include a trailing comment of FieldDefault, FieldExample or FieldDocsOnly. // - If a description begins with TokenAdvanced, then a warning will be included. // - The markdown wil begin with the header, followed by the example // - Extended descriptions can be applied to top level tables func Generate(toml, header, example string, extendedDescriptions map[string]string) (string, error) { - items, err := parseTOMLDocs(toml, extendedDescriptions) + return GenerateWith(TOML{}, toml, header, example, extendedDescriptions) +} + +// GenerateWith is Generate for a document written in the syntax described by format. Generate +// is GenerateWith(TOML{}, ...). +func GenerateWith(format Format, doc, header, example string, extendedDescriptions map[string]string) (string, error) { + items, err := parseDocs(format, doc, extendedDescriptions) var sb strings.Builder sb.WriteString(header) @@ -29,7 +40,9 @@ func Generate(toml, header, example string, extendedDescriptions map[string]stri ## Example `) - sb.WriteString("```toml\n") + sb.WriteString("```") + sb.WriteString(format.Name()) + sb.WriteString("\n") sb.WriteString(example) sb.WriteString("\n```\n\n") @@ -53,6 +66,7 @@ func (d lines) String() string { } type table struct { + lang string name string codes lines adv bool @@ -60,9 +74,10 @@ type table struct { extended string } -func newTable(line string, desc lines, extendedDescriptions map[string]string) *table { +func newTable(lang, line, name string, desc lines, extendedDescriptions map[string]string) *table { t := &table{ - name: strings.Trim(line, "[]"), + lang: lang, + name: name, codes: []string{line}, desc: desc, } @@ -78,9 +93,10 @@ func newTable(line string, desc lines, extendedDescriptions map[string]string) * return t } -func newArrayOfTables(line string, desc lines, extendedDescriptions map[string]string) *table { +func newArrayOfTables(lang, line, name string, desc lines, extendedDescriptions map[string]string) *table { t := &table{ - name: strings.Trim(strings.Trim(line, FieldExample), "[]"), + lang: lang, + name: name, codes: []string{line}, desc: desc, } @@ -105,7 +121,7 @@ func (t table) advanced() string { func (t table) code() string { if t.extended == "" { - return fmt.Sprint("```toml\n", t.codes, "\n```\n") + return fmt.Sprint("```", t.lang, "\n", t.codes, "\n```\n") } return "" } @@ -120,16 +136,18 @@ func (t *table) String() string { } type keyval struct { + lang string name string code string adv bool desc lines } -func newKeyval(line string, desc lines) keyval { +func newKeyval(lang, line, name string, desc lines) keyval { line = strings.TrimSpace(line) kv := keyval{ - name: line[:strings.Index(line, " ")], + lang: lang, + name: name, code: line, desc: desc, } @@ -155,38 +173,39 @@ func (k keyval) String() string { } return fmt.Sprint("### ", name, "\n", k.advanced(), - "```toml\n", + "```", k.lang, "\n", k.code, "\n```\n", k.desc) } -func parseTOMLDocs(s string, extendedDescriptions map[string]string) (items []fmt.Stringer, err error) { +func parseDocs(format Format, s string, extendedDescriptions map[string]string) (items []fmt.Stringer, err error) { defer func() { _, err = config.MultiErrorList(err) }() - globalTable := table{name: "Global"} + globalTable := table{lang: format.Name(), name: "Global"} currentTable := &globalTable items = append(items, currentTable) var desc lines + defaultMarker, exampleMarker, docsOnlyMarker := format.DefaultMarker(), format.ExampleMarker(), format.DocsOnlyMarker() for line := range strings.SplitSeq(s, "\n") { - if strings.HasPrefix(line, "#") { - // comment - desc = append(desc, strings.TrimSpace(line[1:])) - } else if strings.TrimSpace(line) == "" { - // empty + parsed := format.ParseLine(line) + switch parsed.Kind { + case LineComment: + desc = append(desc, parsed.Text) + case LineBlank: if len(desc) > 0 { items = append(items, desc) desc = nil } - } else if strings.HasPrefix(line, "[[") { - currentTable = newArrayOfTables(line, desc, extendedDescriptions) + case LineArrayOfTables: + currentTable = newArrayOfTables(format.Name(), line, parsed.Text, desc, extendedDescriptions) items = append(items, currentTable) desc = nil - } else if strings.HasPrefix(line, "[") { - currentTable = newTable(line, desc, extendedDescriptions) + case LineTable: + currentTable = newTable(format.Name(), line, parsed.Text, desc, extendedDescriptions) items = append(items, currentTable) desc = nil - } else { - kv := newKeyval(line, desc) + default: + kv := newKeyval(format.Name(), line, parsed.Text, desc) shortName := kv.name if currentTable != &globalTable { // update to full name @@ -197,12 +216,17 @@ func parseTOMLDocs(s string, extendedDescriptions map[string]string) (items []fm } else if !strings.HasPrefix(kv.desc[0], shortName) { err = errors.Join(err, fmt.Errorf("%s: description does not begin with %q", kv.name, shortName)) } - if !strings.HasSuffix(line, FieldDefault) && !strings.HasSuffix(line, FieldExample) { - err = errors.Join(err, fmt.Errorf(`%s: is not one of %v`, kv.name, []string{FieldDefault, FieldExample})) + docsOnly := strings.HasSuffix(line, docsOnlyMarker) + if !docsOnly && !strings.HasSuffix(line, defaultMarker) && !strings.HasSuffix(line, exampleMarker) { + err = errors.Join(err, fmt.Errorf(`%s: is not one of %v`, kv.name, []string{defaultMarker, exampleMarker, docsOnlyMarker})) } items = append(items, kv) - currentTable.codes = append(currentTable.codes, kv.code) + // A docs-only field still gets its own entry, but is kept out of the table's code + // block so every example in the document agrees on what a working config contains. + if !docsOnly { + currentTable.codes = append(currentTable.codes, kv.code) + } desc = nil } } diff --git a/pkg/config/configdoc/format.go b/pkg/config/configdoc/format.go new file mode 100644 index 0000000000..0caa5273c9 --- /dev/null +++ b/pkg/config/configdoc/format.go @@ -0,0 +1,53 @@ +package configdoc + +// LineKind classifies a line of a configuration document. +type LineKind int + +const ( + // LineBlank is an empty or whitespace-only line, which terminates a comment block. + LineBlank LineKind = iota + // LineComment is a description line. + LineComment + // LineTable opens a table (section) of fields. + LineTable + // LineArrayOfTables opens a repeated table. + LineArrayOfTables + // LineField is a key/value pair. + LineField +) + +// Line is a parsed line: its kind, plus the piece of it that carries meaning - the comment +// text with its marker stripped, the table's name, or the field's key. +type Line struct { + Kind LineKind + Text string +} + +// Format is a configuration file syntax. It both writes the pieces of a document (so callers +// can assemble one from Go structs) and recognizes them when reading one back (so a +// hand-written document can be turned into documentation). Implement it to document a format +// other than TOML; see TOML for the reference implementation. +type Format interface { + // Comment renders text as a description line. + Comment(text string) string + // Table renders the opening of a section, given its dotted path (e.g. "chain.nodes"). + Table(path string) string + // Field renders a key/value line. marker is DefaultMarker, ExampleMarker or + // DocsOnlyMarker, or empty for a plain value line with no documentation annotation. + Field(key, value, marker string) string + // Literal renders a Go value as a value literal of this format. + Literal(v any) string + + // DefaultMarker annotates a field whose value is its real default. + DefaultMarker() string + // ExampleMarker annotates a field with no usable default, whose value is a placeholder. + ExampleMarker() string + // DocsOnlyMarker annotates a field that is documented but shown in no example. + DocsOnlyMarker() string + + // ParseLine classifies one line of a document. + ParseLine(line string) Line + + // Name returns the markdown name for code blocks + Name() string +} diff --git a/pkg/config/configdoc/toml.go b/pkg/config/configdoc/toml.go new file mode 100644 index 0000000000..146c044c80 --- /dev/null +++ b/pkg/config/configdoc/toml.go @@ -0,0 +1,105 @@ +package configdoc + +import ( + "encoding" + "fmt" + "reflect" + "strconv" + "strings" + "time" +) + +// TOML documents TOML configuration: `# comment`, `[table]`, `key = value # Default`. +type TOML struct{} + +func (t TOML) Name() string { + return "toml" +} + +var _ Format = TOML{} + +func (TOML) Comment(text string) string { return "# " + text } + +func (TOML) Table(path string) string { return "[" + path + "]" } + +func (TOML) Field(key, value, marker string) string { + if marker == "" { + return fmt.Sprintf("%s = %s", key, value) + } + return fmt.Sprintf("%s = %s %s", key, value, marker) +} + +func (TOML) DefaultMarker() string { return FieldDefault } + +func (TOML) ExampleMarker() string { return FieldExample } + +func (TOML) DocsOnlyMarker() string { return FieldDocsOnly } + +func (TOML) ParseLine(line string) Line { + switch { + case strings.HasPrefix(line, "#"): + return Line{Kind: LineComment, Text: strings.TrimSpace(line[1:])} + case strings.TrimSpace(line) == "": + return Line{Kind: LineBlank} + case strings.HasPrefix(line, "[["): + return Line{Kind: LineArrayOfTables, Text: strings.Trim(strings.Trim(line, FieldExample), "[]")} + case strings.HasPrefix(line, "["): + return Line{Kind: LineTable, Text: strings.Trim(line, "[]")} + default: + name := strings.TrimSpace(line) + if i := strings.Index(name, " "); i > -1 { + name = name[:i] + } + return Line{Kind: LineField, Text: name} + } +} + +var textMarshalerType = reflect.TypeOf((*encoding.TextMarshaler)(nil)).Elem() + +// Literal renders v as a TOML value literal. Values that marshal themselves to text (such as +// config.Duration) are written as their text form in quotes, so they round-trip through the +// same UnmarshalText that reads them back. +func (t TOML) Literal(val any) string { + if val == nil { + return "''" + } + + v := reflect.ValueOf(val) + if v.Kind() == reflect.Pointer { + if v.IsNil() { + return "''" + } + v = v.Elem() + } + + if v.CanInterface() && v.Type().Implements(textMarshalerType) { + if b, err := v.Interface().(encoding.TextMarshaler).MarshalText(); err == nil { + return "'" + string(b) + "'" + } + } + + if v.Type() == reflect.TypeOf(time.Duration(0)) { + return "'" + time.Duration(v.Int()).String() + "'" + } + + switch v.Kind() { + case reflect.String: + return "'" + v.String() + "'" + case reflect.Bool: + return strconv.FormatBool(v.Bool()) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return strconv.FormatInt(v.Int(), 10) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + return strconv.FormatUint(v.Uint(), 10) + case reflect.Float32, reflect.Float64: + return strconv.FormatFloat(v.Float(), 'f', -1, 64) + case reflect.Slice, reflect.Array: + parts := make([]string, v.Len()) + for i := range parts { + parts[i] = t.Literal(v.Index(i).Interface()) + } + return "[" + strings.Join(parts, ", ") + "]" + default: + return fmt.Sprintf("'%v'", v.Interface()) + } +} diff --git a/pkg/config/configdoc/toml_test.go b/pkg/config/configdoc/toml_test.go new file mode 100644 index 0000000000..4618900da4 --- /dev/null +++ b/pkg/config/configdoc/toml_test.go @@ -0,0 +1,143 @@ +package configdoc + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/chainlink-common/pkg/config" +) + +func TestTOMLName(t *testing.T) { + // Used as the markdown code fence language. + assert.Equal(t, "toml", TOML{}.Name()) +} + +func TestTOMLComment(t *testing.T) { + assert.Equal(t, "# the host", TOML{}.Comment("the host")) +} + +func TestTOMLTable(t *testing.T) { + assert.Equal(t, "[chain]", TOML{}.Table("chain")) + assert.Equal(t, "[chain.nodes]", TOML{}.Table("chain.nodes")) +} + +func TestTOMLField(t *testing.T) { + f := TOML{} + assert.Equal(t, "host = 'x' # Default", f.Field("host", "'x'", f.DefaultMarker())) + assert.Equal(t, "host = 'x' # Example", f.Field("host", "'x'", f.ExampleMarker())) + // No marker: a plain value line, for the example config rather than the docs. + assert.Equal(t, "host = 'x'", f.Field("host", "'x'", "")) +} + +func TestTOMLMarkers(t *testing.T) { + assert.Equal(t, FieldDefault, TOML{}.DefaultMarker()) + assert.Equal(t, FieldExample, TOML{}.ExampleMarker()) +} + +func TestTOMLParseLine(t *testing.T) { + for _, tt := range []struct { + name string + line string + want Line + }{ + {"comment", "# the host", Line{Kind: LineComment, Text: "the host"}}, + {"comment without space", "#the host", Line{Kind: LineComment, Text: "the host"}}, + {"empty", "", Line{Kind: LineBlank}}, + {"whitespace only", " ", Line{Kind: LineBlank}}, + {"table", "[chain]", Line{Kind: LineTable, Text: "chain"}}, + {"nested table", "[chain.nodes]", Line{Kind: LineTable, Text: "chain.nodes"}}, + {"array of tables", "[[EVM]]", Line{Kind: LineArrayOfTables, Text: "EVM"}}, + {"field", "host = 'x' # Default", Line{Kind: LineField, Text: "host"}}, + {"field with no marker", "host = 'x'", Line{Kind: LineField, Text: "host"}}, + {"indented field", " host = 'x'", Line{Kind: LineField, Text: "host"}}, + // Degenerate, but must not panic - Text is simply the whole line. + {"field with no space", "host", Line{Kind: LineField, Text: "host"}}, + } { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.want, TOML{}.ParseLine(tt.line)) + }) + } +} + +// ParseLine has to read back whatever the emitting side writes, or a document assembled with +// this format can't be turned into documentation by it. +func TestTOMLEmitAndParseAgree(t *testing.T) { + f := TOML{} + + assert.Equal(t, Line{Kind: LineComment, Text: "the host"}, f.ParseLine(f.Comment("the host"))) + assert.Equal(t, Line{Kind: LineTable, Text: "chain.nodes"}, f.ParseLine(f.Table("chain.nodes"))) + assert.Equal(t, Line{Kind: LineField, Text: "host"}, f.ParseLine(f.Field("host", "'x'", f.DefaultMarker()))) + assert.Equal(t, Line{Kind: LineField, Text: "host"}, f.ParseLine(f.Field("host", "'x'", ""))) +} + +func TestTOMLLiteral(t *testing.T) { + for _, tt := range []struct { + name string + val any + want string + }{ + {"string", "example.com", "'example.com'"}, + {"empty string", "", "''"}, + {"bool", true, "true"}, + {"int", 42, "42"}, + {"negative int", -7, "-7"}, + {"sized int", int32(42), "42"}, + {"uint", uint32(42), "42"}, + {"float", 1.5, "1.5"}, + {"nil", nil, "''"}, + {"string slice", []string{"a", "b"}, "['a', 'b']"}, + {"empty slice", []string{}, "[]"}, + {"int slice", []int{1, 2}, "[1, 2]"}, + // A duration is an int64 underneath; it must not render as a raw nanosecond count. + {"duration", 90 * time.Second, "'1m30s'"}, + } { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.want, TOML{}.Literal(tt.val)) + }) + } +} + +func TestTOMLLiteralUsesTextMarshaler(t *testing.T) { + // config.Duration marshals itself to text, so it round-trips through the same + // UnmarshalText that reads it back rather than being printed as a struct. + d := config.MustNewDuration(90 * time.Second) + + assert.Equal(t, "'1m30s'", TOML{}.Literal(*d)) + assert.Equal(t, "'1m30s'", TOML{}.Literal(d), "a pointer is dereferenced first") +} + +func TestTOMLLiteralNilPointer(t *testing.T) { + var d *config.Duration + assert.Equal(t, "''", TOML{}.Literal(d)) +} + +// The literals this format writes have to be valid input for the document it produces, so a +// generated document round-trips through Generate without tripping its own parser. +func TestTOMLLiteralsRoundTripThroughGenerate(t *testing.T) { + f := TOML{} + + doc := f.Comment("host the host") + "\n" + + f.Field("host", f.Literal("example.com"), f.DefaultMarker()) + "\n" + + f.Comment("retries how many retries") + "\n" + + f.Field("retries", f.Literal(3), f.DefaultMarker()) + "\n" + + f.Comment("timeout how long to wait") + "\n" + + f.Field("timeout", f.Literal(90*time.Second), f.ExampleMarker()) + "\n" + + out, err := GenerateWith(f, doc, "# Docs\n", "", nil) + require.NoError(t, err) + assert.Contains(t, out, "host = 'example.com' # Default") + assert.Contains(t, out, "retries = 3 # Default") + assert.Contains(t, out, "timeout = '1m30s' # Example") +} + +func TestGenerateWithUsesFormatName(t *testing.T) { + out, err := GenerateWith(TOML{}, "# host the host\nhost = 'x' # Default\n", "# Docs\n", "host = 'x'", nil) + require.NoError(t, err) + + // The code fences are labelled with the format, exactly once. + assert.Contains(t, out, "```toml\n") + assert.NotContains(t, out, "```tomltoml") +} diff --git a/pkg/config/flags/docs.go b/pkg/config/flags/docs.go new file mode 100644 index 0000000000..bef583a590 --- /dev/null +++ b/pkg/config/flags/docs.go @@ -0,0 +1,499 @@ +package flags + +import ( + "fmt" + "os" + "path/filepath" + "reflect" + "strings" + + "github.com/spf13/cobra" + + "github.com/smartcontractkit/chainlink-common/pkg/config/configdoc" +) + +// docsOutputPath is where the "docs" subcommand writes generated documentation, mirroring +// chainlink core's docs/CONFIG.md convention. +const docsOutputPath = "docs/CONFIG.md" + +// addDocsCommand registers a "docs" subcommand on root (if one isn't already present) that +// writes generated Markdown documentation - covering root's registered config struct and +// every descendant subcommand's registered config struct - to docsOutputPath. +// +// opts.GenerateDoc is recorded on the command so the whole document renders with the generator +// the caller asked for. The first registration wins, since one document can only be rendered +// one way however many targets contribute to it. +func addDocsCommand(root *cobra.Command, opts Options) { + meta := getOrCreateMeta(root) + if meta.docFormat == nil { + meta.docFormat = opts.format() + } + + for _, c := range root.Commands() { + if c.Name() == "docs" { + return + } + } + + root.AddCommand(&cobra.Command{ + Use: "docs", + Short: "Write generated configuration documentation to " + docsOutputPath, + // Documenting the config must not require a valid config. Defining any + // PersistentPreRunE here shadows the root's (cobra runs only the nearest one), + // which is what skips the decode + `validate:"required"` step for this command. + PersistentPreRunE: func(*cobra.Command, []string) error { return nil }, + RunE: func(cmd *cobra.Command, args []string) error { + doc, err := GenerateDocs(cmd.Root()) + if err != nil { + return err + } + if err := os.MkdirAll(filepath.Dir(docsOutputPath), 0o755); err != nil { + return fmt.Errorf("failed to create %s: %w", filepath.Dir(docsOutputPath), err) + } + if err := os.WriteFile(docsOutputPath, []byte(doc), 0o600); err != nil { + return fmt.Errorf("failed to write %s: %w", docsOutputPath, err) + } + fmt.Println("Wrote", docsOutputPath) + return nil + }, + }) +} + +// GenerateDocs generates Markdown documentation covering root's registered config struct and +// every descendant subcommand's registered config struct (each under its own namespaced TOML +// table), by deriving TOML doc lines directly from struct tags: +// - `toml`/`mapstructure` (or the lowercased field name) supplies the key, same convention +// used for CLI/env/viper binding. +// - `usage` supplies the field's (or, for a nested struct field, the table's) description. +// - a field tagged `validate:"required"` (the same tag checked at runtime, see +// decodeAndApplyProfiles) is documented as configdoc.FieldExample instead of +// configdoc.FieldDefault, since it has no real default; its `example` tag supplies the +// placeholder value shown. +// - a `flagdocs` tag adjusts how the field appears in the example config; see flagDocsTag. +// +// Arrays/slices of structs ([[array-of-tables]] in hand-written docs) are not supported. +// +// Each command's block is preceded by a divider heading - "Global Configuration" for root, +// "Command: " for every subcommand - naming exactly which `myapp ...` invocation the +// fields below it apply to, since root and subcommand fields would otherwise render as +// visually identical tables with no indication of which command binds them. +func GenerateDocs(root *cobra.Command) (string, error) { + var toml, example strings.Builder + + // The rendering step belongs to the whole document, so it uses the format recorded on the + // root command; per-entry options still drive how each entry's own fields are read. + format := Options{}.format() + if rootMeta := getMeta(root); rootMeta != nil && rootMeta.docFormat != nil { + format = rootMeta.docFormat + } + + var walkCmd func(cmd *cobra.Command) error + walkCmd = func(cmd *cobra.Command) error { + if meta := getMeta(cmd); meta != nil && len(meta.entries) > 0 { + label := "Global Configuration" + if cmd != root { + label = "Command: " + cmd.CommandPath() + } + + var cmdToml, cmdExample strings.Builder + for _, entry := range meta.entries { + if entry.target == nil { + continue + } + + section, err := structToDocs(entry.target, entry.namespace, entry.opts) + if err != nil { + return fmt.Errorf("%s: %w", cmd.CommandPath(), err) + } + cmdToml.WriteString(section) + + exampleSection, err := exampleDoc(entry.target, entry.namespace, entry.opts) + if err != nil { + return fmt.Errorf("%s: %w", cmd.CommandPath(), err) + } + cmdExample.WriteString(exampleSection) + } + + // A comment whose text itself starts with "#" renders as a standalone H1 divider + // (the format strips its own comment marker, leaving the markdown one), set apart + // from the H2 table headings structToDocs emits. The blank line after it keeps it + // from being read as the next item's description. + toml.WriteString(format.Comment("# "+label) + "\n\n" + cmdToml.String()) + example.WriteString(format.Comment("----- "+label+" -----") + "\n" + cmdExample.String() + "\n") + } + for _, sub := range cmd.Commands() { + if err := walkCmd(sub); err != nil { + return err + } + } + return nil + } + + if err := walkCmd(root); err != nil { + return "", err + } + + header := fmt.Sprintf("# %s Configuration\n", root.Name()) + return configdoc.GenerateWith(format, toml.String(), header, example.String(), nil) +} + +// exampleDoc renders target's fields as plain, directly-usable TOML - real default values, +// and for `validate:"required"` fields (which have none), the placeholder from their +// `example` tag - namespaced the same way structToDocs is, so it can double as a working +// example config file for a namespaced subcommand. +func exampleDoc(target any, namespace string, opts Options) (string, error) { + var sb strings.Builder + f := opts.format() + chosen := newExclusiveChoice() + currentSection := "" + + if namespace != "" { + sb.WriteString(f.Table(namespace) + "\n") + currentSection = namespace + } + + err := walkStruct(target, opts, structVisitor{ + branch: func(m fieldMeta) (bool, error) { + if omitFromExample(m.field) || chosen.excluded(m) { + return true, nil + } + chosen.keep(m) + + section := m.key() + if namespace != "" { + section = namespace + "." + section + } + if section == "" || section == currentSection { + return false, nil + } + currentSection = section + sb.WriteString("\n" + f.Table(section) + "\n") + return false, nil + }, + leaf: func(m fieldMeta) error { + if omitFromExample(m.field) || chosen.excluded(m) { + return nil + } + chosen.keep(m) + + key := m.keyPath[len(m.keyPath)-1] + value := f.Literal(m.elem.Interface()) + if hasNoRealDefault(m.field) { + if ex := m.field.Tag.Get("example"); ex != "" { + value = ex + } + } + if override, ok := exampleOverride(m.field); ok { + value = override + } + sb.WriteString(f.Field(key, value, "") + "\n") + return nil + }, + }) + if err != nil { + return "", err + } + + return sb.String(), nil +} + +// structToDocs renders target's fields as annotated config-document lines in opts' format. If namespace is +// non-empty, all of target's keys are nested under a "[namespace]" table (and further nested +// structs under "[namespace.sub]", etc) instead of the top-level "Global" bucket - this is how +// a subcommand's settings (bound under that namespace by RegisterSubcommandFlags) are kept +// from colliding with the root command's own tables. +func structToDocs(target any, namespace string, opts Options) (string, error) { + var sb strings.Builder + f := opts.format() + currentSection := "" + + targetType := reflect.TypeOf(target) + for targetType != nil && targetType.Kind() == reflect.Pointer { + targetType = targetType.Elem() + } + + if namespace != "" { + // No description of its own: a namespace is a grouping, and the command's Short + // describes the command rather than this slice of its configuration. Squashed structs + // underneath still contribute theirs. + writeComments(&sb, f, squashedUsages(targetType, opts)) + sb.WriteString(f.Table(namespace) + "\n") + currentSection = namespace + } else if usages := squashedUsages(targetType, opts); len(usages) > 0 { + // No table header to hang them on, so they become standalone prose. A comment block + // followed by a blank line is a free-standing item to configdoc; without the blank + // line it would instead be read as the next field's description. + writeComments(&sb, f, usages) + sb.WriteString("\n") + } + + err := walkStruct(target, opts, structVisitor{ + branch: func(m fieldMeta) (bool, error) { + section := m.key() + if namespace != "" { + section = namespace + "." + section + } + // A squashed struct contributes no section of its own, so its description would + // have nowhere to go here; it is folded into the enclosing table's header instead + // (see squashedUsages), alongside that table's own description. + if section == "" || section == currentSection { + return false, nil + } + currentSection = section + + writeComments(&sb, f, append(nonEmpty(m.field.Tag.Get("usage")), squashedUsages(m.elemType, opts)...)) + sb.WriteString(f.Table(section) + "\n") + return false, nil + }, + leaf: func(m fieldMeta) error { + key := m.keyPath[len(m.keyPath)-1] + + desc := m.field.Tag.Get("usage") + switch { + case desc == "": + desc = key + case !strings.HasPrefix(desc, key): + desc = key + " " + desc + } + sb.WriteString(f.Comment(desc+constraintNote(m, opts)) + "\n") + + marker := f.DefaultMarker() + value := f.Literal(m.elem.Interface()) + if hasNoRealDefault(m.field) { + marker = f.ExampleMarker() + if ex := m.field.Tag.Get("example"); ex != "" { + value = ex + } + } + if omitFromExample(m.field) { + // Documented, but absent from the example config and from its own table's + // code block, so every example in the document describes the same setup. + marker = f.DocsOnlyMarker() + } + + sb.WriteString(f.Field(key, value, marker) + "\n\n") + return nil + }, + }) + if err != nil { + return "", err + } + + return sb.String(), nil +} + +// isRequired reports whether field carries a `required` validate rule - either the plain +// `required` or one of validator's conditional forms (`required_without=Other`, +// `required_if=...`, etc). Conditionally-required fields have no usable default either, so +// they're documented as FieldExample the same way. +// squashedUsages returns the `usage` descriptions of t's squashed struct fields, recursively. +// Squashed structs are flattened into their parent and get no table of their own, so their +// descriptions are reported as part of the enclosing table's header - otherwise they'd be lost, +// and emitting them next to their fields would make configdoc read them as the following +// field's description. +func squashedUsages(t reflect.Type, opts Options) []string { + if t == nil || t.Kind() != reflect.Struct { + return nil + } + + var usages []string + for i := 0; i < t.NumField(); i++ { + field := t.Field(i) + if field.PkgPath != "" { + continue + } + + elemType := field.Type + if elemType.Kind() == reflect.Pointer { + elemType = elemType.Elem() + } + if elemType.Kind() != reflect.Struct || implementsTextUnmarshaler(elemType) { + continue + } + if _, squash := opts.tagKey(field); !squash { + continue + } + + usages = append(usages, nonEmpty(field.Tag.Get("usage"))...) + usages = append(usages, squashedUsages(elemType, opts)...) + } + return usages +} + +// exclusiveChoice tracks which of a set of mutually exclusive fields the example already +// shows. An example config has to be one that actually works, and "exactly one of these" +// cannot be illustrated by listing all of them - so the first one declared wins and the rest +// are left out. The docs still describe every option; only the example has to choose. +type exclusiveChoice map[reflect.Type]map[string]bool + +func newExclusiveChoice() exclusiveChoice { return exclusiveChoice{} } + +// keep records that the example shows this field. +func (c exclusiveChoice) keep(m fieldMeta) { + if m.parent == nil { + return + } + if c[m.parent] == nil { + c[m.parent] = map[string]bool{} + } + c[m.parent][m.field.Name] = true +} + +// excluded reports whether a field the example already shows rules this one out. +func (c exclusiveChoice) excluded(m fieldMeta) bool { + shown := c[m.parent] + if len(shown) == 0 { + return false + } + for _, rule := range strings.Split(m.field.Tag.Get("validate"), ",") { + name, args, hasArgs := strings.Cut(rule, "=") + if !hasArgs || !strings.HasPrefix(name, "excluded_with") { + continue + } + for _, sibling := range strings.Fields(args) { + if shown[sibling] { + return true + } + } + } + return false +} + +// writeComments emits each line as a comment in f's syntax. +func writeComments(sb *strings.Builder, f configdoc.Format, lines []string) { + for _, l := range lines { + sb.WriteString(f.Comment(l) + "\n") + } +} + +// nonEmpty returns s as a one-element slice, or nothing if s is empty. +func nonEmpty(s string) []string { + if s == "" { + return nil + } + return []string{s} +} + +// The `flagdocs` tag adjusts how a field appears in the generated example config, without +// changing the field itself or how it is documented. It holds one directive: +// +// flagdocs:"noexample" leave the field out of every example (see configdoc.FieldDocsOnly) +// flagdocs:"example=" use this literal in the example instead of the field's default +// +// Use noexample for a setting the example must not carry - one that only applies in a mode the +// example isn't showing - where including it would describe a setup nobody should copy. Use +// example= where the field's own default would make the example wrong or unhelpful. +// +// The directive is taken whole, so an example= literal may itself contain commas. +const flagDocsTag = "flagdocs" + +// omitFromExample reports whether a field is kept out of the example config. +func omitFromExample(field reflect.StructField) bool { + return field.Tag.Get(flagDocsTag) == "noexample" +} + +// exampleOverride returns the literal to show for a field in the example config, if the field +// asked for one. +func exampleOverride(field reflect.StructField) (string, bool) { + const prefix = "example=" + tag := field.Tag.Get(flagDocsTag) + if !strings.HasPrefix(tag, prefix) { + return "", false + } + return strings.TrimPrefix(tag, prefix), true +} + +// hasNoRealDefault reports whether a field's zero value is a placeholder rather than a usable +// default, so the docs should show it as configdoc.FieldExample. That's true when the field is +// required (its value must be supplied), and also whenever it carries an `example` tag - which +// is how a field says "there is nothing sensible to default to" even when the rule making it +// required lives outside this struct and so can't be a `validate` tag. +func hasNoRealDefault(field reflect.StructField) bool { + return isRequired(field) || field.Tag.Get("example") != "" +} + +func isRequired(field reflect.StructField) bool { + for _, rule := range strings.Split(field.Tag.Get("validate"), ",") { + if name, _, _ := strings.Cut(rule, "="); name == "required" || strings.HasPrefix(name, "required_") { + return true + } + } + return false +} + +// crossFieldRules are the validator rules whose outcome depends on a sibling field, and which +// therefore need a field to be distinguishably "absent" rather than merely zero. +var crossFieldRules = map[string]bool{ + "required_with": true, "required_with_all": true, + "required_without": true, "required_without_all": true, + "excluded_with": true, "excluded_with_all": true, + "excluded_without": true, "excluded_without_all": true, +} + +// crossFieldRuleNames returns the cross-field rule names present on field, in tag order. +func crossFieldRuleNames(field reflect.StructField) []string { + var names []string + for _, rule := range strings.Split(field.Tag.Get("validate"), ",") { + if name, _, hasArgs := strings.Cut(rule, "="); hasArgs && crossFieldRules[name] { + names = append(names, name) + } + } + return names +} + +// constraintNote renders a field's cross-field `validate` rules as a human-readable sentence +// (e.g. "must not be set unless database-url is set"), so the generated docs explain when a +// conditionally required or mutually exclusive setting actually applies. Sibling fields named +// by the rules are reported by their config key rather than their Go field name. +func constraintNote(m fieldMeta, opts Options) string { + var notes []string + for _, rule := range strings.Split(m.field.Tag.Get("validate"), ",") { + name, args, hasArgs := strings.Cut(rule, "=") + if !hasArgs { + continue + } + + var keys []string + for _, goName := range strings.Fields(args) { + keys = append(keys, siblingKey(m.parent, goName, opts)) + } + list := strings.Join(keys, ", ") + + switch name { + case "required_with": + notes = append(notes, "required when "+list+" is set") + case "required_with_all": + notes = append(notes, "required when all of "+list+" are set") + case "required_without": + notes = append(notes, "required unless "+list+" is set") + case "required_without_all": + notes = append(notes, "required unless all of "+list+" are set") + case "excluded_with": + notes = append(notes, "must not be set when "+list+" is set") + case "excluded_with_all": + notes = append(notes, "must not be set when all of "+list+" are set") + case "excluded_without": + notes = append(notes, "must not be set unless "+list+" is set") + case "excluded_without_all": + notes = append(notes, "must not be set unless all of "+list+" are set") + } + } + if len(notes) == 0 { + return "" + } + return " (" + strings.Join(notes, "; ") + ")" +} + +// siblingKey maps a Go field name within parent to its config key, falling back to the Go name +// if there's no such field. +func siblingKey(parent reflect.Type, goName string, opts Options) string { + if parent == nil { + return goName + } + if f, ok := parent.FieldByName(goName); ok { + key, _ := opts.tagKey(f) + return key + } + return goName +} + diff --git a/pkg/config/flags/flags.go b/pkg/config/flags/flags.go new file mode 100644 index 0000000000..8509c060ce --- /dev/null +++ b/pkg/config/flags/flags.go @@ -0,0 +1,832 @@ +package flags + +import ( + "errors" + "fmt" + "os" + "reflect" + "strings" + "sync" + "time" + + "github.com/go-playground/validator/v10" + "github.com/go-viper/mapstructure/v2" + "github.com/spf13/cobra" + "github.com/spf13/pflag" + "github.com/spf13/viper" + + "github.com/smartcontractkit/chainlink-common/pkg/config" + "github.com/smartcontractkit/chainlink-common/pkg/config/configdoc" +) + +// durationType lets bindLeafFlag recognize a time.Duration field (Kind() is Int64, same as +// any plain int64) and bind it as a pflag Duration ("5s" CLI syntax) instead of a raw integer. +var durationType = reflect.TypeOf(time.Duration(0)) + +// configDurationType is config.Duration, the non-negative duration used by config structs +// across chainlink. It's a TextMarshaler/TextUnmarshaler, so it would otherwise bind as an +// untyped string flag; special-casing it keeps the typed "duration" pflag (and pflag's own +// parse errors) while still decoding through its UnmarshalText. +var configDurationType = reflect.TypeOf(config.Duration{}) + +// validate runs `validate:"..."` struct tag checks (e.g. `required`) against a fully +// decoded target, after config file/flags/env/profile defaulting has all been applied. +var validate = sync.OnceValue(func() *validator.Validate { return validator.New() }) + +type profileApplier func(cmd *cobra.Command, target any) error + +// targetEntry is one struct registered against a command via RegisterCommandFlags or +// RegisterSubcommandFlags. A single command can have multiple entries - e.g. several +// independent plugins/dependencies each registering their own config struct on a shared root +// command - decoded and validated independently of one another. +type targetEntry struct { + // namespace roots this entry's config keys. + namespace string + // flagPrefix roots this entry's flag names. It matches namespace for a root-command + // registration, where several structs share one flag set and would otherwise collide, but + // is empty for a subcommand, whose own name already separates it from its siblings. + flagPrefix string + prefixes []string + target any + opts Options + profiles []profileApplier + + // keys is every leaf bound for this target, recorded at registration so decoding can + // resolve exactly this entry's keys (and no other entry's) through viper's normal + // precedence. + keys []leafKey +} + +// leafKey ties a target field's position within its own struct (relPath) to the viper key it +// was bound under (viperKey, which carries the entry's namespace prefix, if any) and the CLI +// flag registered for it (flagName, which does not - it's derived from the key relative to the +// target, so it can't be recomputed from viperKey for a namespaced entry). +type leafKey struct { + relPath []string + viperKey string + flagName string +} + +type commandMetaData struct { + entries []*targetEntry + + // docFormat is the syntax documentation for this command's whole tree is written in, + // taken from the Options of the registration that added the docs command (see + // addDocsCommand). + docFormat configdoc.Format + + // hookWired guards against chaining a redundant decode step onto PersistentPreRunE/PreRunE + // every time RegisterCommandFlags/RegisterSubcommandFlags is called again for this command; + // the single wired hook always decodes every entry (see decodeAndApplyProfiles). + hookWired bool +} + +var ( + registryMu sync.RWMutex + cmdRegistry = make(map[*cobra.Command]*commandMetaData) +) + +func getOrCreateMeta(cmd *cobra.Command) *commandMetaData { + registryMu.Lock() + defer registryMu.Unlock() + + meta, exists := cmdRegistry[cmd] + if !exists { + meta = &commandMetaData{} + cmdRegistry[cmd] = meta + } + return meta +} + +func getMeta(cmd *cobra.Command) *commandMetaData { + registryMu.RLock() + defer registryMu.RUnlock() + return cmdRegistry[cmd] +} + +// RegisterCommandFlags binds struct fields as CLI persistent flags, Viper defaults, and env vars. +// It also wires an automatic decode step (config file + flags + env + registered profiles) into +// target, running before cmd's own PersistentPreRunE (if any). +// +// It's safe to call this (and/or RegisterSubcommandFlags) more than once for the same cmd with +// different targets - e.g. several independent dependencies each registering their own config +// struct on a shared root command - each target is decoded, profile-defaulted, and validated +// independently. +// +// Fields are validated with go-playground/validator `validate` tags after decoding, so a rule +// sees the value whatever supplied it (flag, env, config file, or profile). Mutually exclusive +// or conditionally required *sections* must be pointer fields, so that "not configured" is +// representable: +// +// Local *LocalConfig `toml:"local" validate:"required_without=Proxy,excluded_with=Proxy"` +// Proxy *ProxyConfig `toml:"proxy" validate:"required_without=Local,excluded_with=Local"` +// +// Registering a non-pointer struct with such a rule is an error, since a value struct is never +// absent and the rule could not work. +// +// opts controls the tag conventions, decoding, env prefixes, and doc generation; see +// DefaultTOMLOptions. +func RegisterCommandFlags(cmd *cobra.Command, target any, opts Options) error { + meta := getOrCreateMeta(cmd) + entry := &targetEntry{ + namespace: opts.Namespace, + flagPrefix: opts.Namespace, + prefixes: opts.Prefixes, + target: target, + opts: opts, + } + meta.entries = append(meta.entries, entry) + + if err := registerStructFlagsInternal(cmd, entry, false); err != nil { + return err + } + + wireDecodeHook(cmd, meta, true) + addDocsCommand(cmd, opts) + return nil +} + +// RegisterSubcommandFlags registers local flags on a subcommand, inheriting the root command's +// env prefixes when opts sets none. It also wires an automatic decode step for target, running +// before cmd's own PreRunE (if any). See RegisterCommandFlags for the multiple-targets-per-command +// note. +func RegisterSubcommandFlags(cmd *cobra.Command, namespace string, target any, opts Options) error { + meta := getOrCreateMeta(cmd) + // With no prefixes of its own, the entry inherits the root command's at decode time, since + // cmd usually hasn't been attached to its parent yet (see effectivePrefixes). + entry := &targetEntry{namespace: namespace, prefixes: opts.Prefixes, target: target, opts: opts} + meta.entries = append(meta.entries, entry) + + if err := registerStructFlagsInternal(cmd, entry, true); err != nil { + return err + } + + wireDecodeHook(cmd, meta, false) + return nil +} + +// effectivePrefixes returns the entry's own env-var prefixes, or - for a subcommand entry that +// didn't specify any - the union of those registered on cmd's root command. +// +// This is resolved when the command runs rather than when it's registered: a subcommand is +// typically registered with its config before rootCmd.AddCommand(sub) is called, so at +// registration time sub.Root() is still sub itself and the root's prefixes aren't reachable yet. +func (e *targetEntry) effectivePrefixes(cmd *cobra.Command) []string { + if len(e.prefixes) > 0 { + return e.prefixes + } + + rootMeta := getMeta(cmd.Root()) + if rootMeta == nil { + return nil + } + + seen := make(map[string]bool) + var prefixes []string + for _, re := range rootMeta.entries { + for _, p := range re.prefixes { + if !seen[p] { + seen[p] = true + prefixes = append(prefixes, p) + } + } + } + return prefixes +} + +// bindEnv binds each of the entry's keys to its PREFIX_UPPER_SNAKE env vars. Called at decode +// time for the same reason effectivePrefixes is resolved there. +func (e *targetEntry) bindEnv(prefixes []string) { + if len(prefixes) == 0 { + return + } + for _, k := range e.keys { + envSuffix := strings.ToUpper(strings.ReplaceAll(strings.ReplaceAll(k.viperKey, ".", "_"), "-", "_")) + bindArgs := []string{k.viperKey} + for _, prefix := range prefixes { + bindArgs = append(bindArgs, strings.TrimSuffix(strings.ToUpper(prefix), "_")+"_"+envSuffix) + } + _ = viper.BindEnv(bindArgs...) + } +} + +// wireDecodeHook chains a decode step in front of whatever PreRunE/PersistentPreRunE the command +// already has, so the caller's own hook (if any) observes already-populated targets. Only wires +// once per command - later calls (from additional RegisterCommandFlags/RegisterSubcommandFlags +// calls on the same cmd) are no-ops here since decodeAndApplyProfiles always walks every +// registered entry for the command. +func wireDecodeHook(cmd *cobra.Command, meta *commandMetaData, persistent bool) { + if meta.hookWired { + return + } + meta.hookWired = true + + decode := func(c *cobra.Command, args []string) error { + return decodeAndApplyProfiles(c, meta) + } + + if persistent { + prev := cmd.PersistentPreRunE + cmd.PersistentPreRunE = func(c *cobra.Command, args []string) error { + if err := decode(c, args); err != nil { + return err + } + if prev != nil { + return prev(c, args) + } + return nil + } + return + } + + prev := cmd.PreRunE + cmd.PreRunE = func(c *cobra.Command, args []string) error { + if err := decode(c, args); err != nil { + return err + } + if prev != nil { + return prev(c, args) + } + return nil + } +} + +// decodeAndApplyProfiles loads the optional config file, then for every entry registered +// against cmd: unmarshals Viper state into entry.target, applies profiles registered for that +// entry, and validates `validate:"required"` tags. Entries are independent - one entry's +// decode/validation failure doesn't stop the others from being decoded and validated too, so +// e.g. two dependencies sharing a command each get to report their own missing-required-field +// errors in the same run instead of one hiding the other. +func decodeAndApplyProfiles(cmd *cobra.Command, meta *commandMetaData) error { + // Cobra's own commands describe the program rather than run it, so they must work on a + // machine that has no valid configuration - asking someone to satisfy every required + // setting before they can read the help that tells them what those settings are would be + // backwards. They are generated when the command runs, too late to opt out at + // registration the way the docs command does, so they're recognized here. + if IsBuiltinCommand(cmd) { + return nil + } + + // Load into the (global) viper before reading any key. cmd here is the command actually + // being executed, not the one this hook was registered on, so it can't be used to tell + // "am I the root" - hence loading once per process rather than only on the root's pass. + if err := loadConfigFileOnce(cmd); err != nil { + return err + } + + var errs []error + for _, entry := range meta.entries { + if entry.target == nil { + continue + } + + // Resolve prefixes and bind env vars now that the command tree is fully assembled. + entry.prefixes = entry.effectivePrefixes(cmd) + entry.bindEnv(entry.prefixes) + + if err := decodeEntry(cmd, entry); err != nil { + errs = append(errs, err) + continue + } + + var applierErr error + for _, applier := range entry.profiles { + if err := applier(cmd, entry.target); err != nil { + applierErr = err + break + } + } + if applierErr != nil { + errs = append(errs, applierErr) + continue + } + + // Check `validate:"required"` (and any other validator tags) only now that config + // file/flags/env/profile defaulting have all had a chance to fill fields in - a field + // can be required yet still end up populated by a profile rather than the user directly. + if err := validate().Struct(entry.target); err != nil { + errs = append(errs, fmt.Errorf("invalid configuration: %w", err)) + } + } + + return errors.Join(errs...) +} + +// decodeEntry resolves each of entry's registered leaf keys through viper (so CLI flag / env +// var / config file / default precedence applies per key), assembles them into a nested map +// shaped like entry.target, and decodes that into the target. +// +// This is deliberately not viper.Unmarshal/UnmarshalKey: Unmarshal decodes the whole tree, so +// a namespaced entry's "foo.timeout" would never line up with its plain "timeout" field, and +// UnmarshalKey is decode(viper.Get(namespace)), which returns whichever single source holds +// that subtree - it does not merge per-key flag/env overrides underneath it. Both silently +// leave fields at their compiled-in defaults rather than erroring. +// Only keys the user actually supplied (changed flag, config file entry, or env var) are +// included. Fields nobody set keep whatever the caller's struct was constructed with, which is +// what makes an optional nested *struct stay nil when nothing under it was provided - decoding +// its defaults would allocate it and defeat `required_without`/`excluded_with` on that field. +func decodeEntry(cmd *cobra.Command, entry *targetEntry) error { + settings := map[string]any{} + for _, k := range entry.keys { + if !isExplicitlySet(cmd, k.flagName, k.viperKey, entry.prefixes) { + continue + } + val := viper.Get(k.viperKey) + if val == nil { + continue + } + setPath(settings, k.relPath, val) + } + + decoder, err := mapstructure.NewDecoder(entry.opts.decoderConfigFor(entry.target)) + if err != nil { + return err + } + return decoder.Decode(settings) +} + +// setPath assigns val at the nested path within m, creating intermediate maps as needed. +func setPath(m map[string]any, path []string, val any) { + for _, p := range path[:len(path)-1] { + next, ok := m[p].(map[string]any) + if !ok { + next = map[string]any{} + m[p] = next + } + m = next + } + m[path[len(path)-1]] = val +} + +var ( + configFileOnce = new(sync.Once) + configFileErr error +) + +// loadConfigFileOnce reads the config file into viper the first time it's called, since viper +// is process-global and several commands' decode hooks can run in one execution. +func loadConfigFileOnce(cmd *cobra.Command) error { + configFileOnce.Do(func() { configFileErr = loadConfigFile(cmd) }) + return configFileErr +} + +// IsBuiltinCommand reports whether cmd is one of cobra's generated commands (help, completion, +// and the hidden completion callbacks), or lives under one. +// +// Decoding and validation are skipped for these automatically. Callers that chain their own +// PersistentPreRunE/PreRunE with extra checks - rules spanning several config structs, say - +// should return early on it too, or those checks will reject `help` on a machine that has no +// configuration yet. +func IsBuiltinCommand(cmd *cobra.Command) bool { + for c := cmd; c != nil; c = c.Parent() { + switch c.Name() { + case "help", "completion", cobra.ShellCompRequestCmd, cobra.ShellCompNoDescRequestCmd: + return true + } + } + return false +} + +func loadConfigFile(cmd *cobra.Command) error { + configFile, _ := cmd.Flags().GetString("config") + + if configFile != "" { + viper.SetConfigFile(configFile) + if err := viper.ReadInConfig(); err != nil { + return fmt.Errorf("failed to read specified config file %q: %w", configFile, err) + } + return nil + } + + viper.AddConfigPath(".") + viper.SetConfigName("config") + viper.SetConfigType("toml") + + if err := viper.ReadInConfig(); err != nil { + if _, ok := err.(viper.ConfigFileNotFoundError); !ok { + return fmt.Errorf("failed to parse config file: %w", err) + } + } + return nil +} + +// RegisterProfile attaches a profile map to a command using a selector field path (e.g. "Chain.ID" or "chain.id"). +// T must match the type of a struct already registered for this command via +// RegisterCommandFlags/RegisterSubcommandFlags - if more than one entry of type T is registered +// on cmd, or none are, this returns an error (call RegisterCommandFlags/RegisterSubcommandFlags +// first, and don't register two same-typed structs on one command if you also need a profile). +func RegisterProfile[T any, K comparable]( + cmd *cobra.Command, + selectorFieldName string, + profiles map[K]T, + opts Options, +) error { + meta := getOrCreateMeta(cmd) + + var zero T + targetType := reflect.TypeOf(zero) + if targetType.Kind() == reflect.Pointer { + targetType = targetType.Elem() + } + + entry, err := findEntryByTargetType(meta, targetType) + if err != nil { + return err + } + + selectorPath, err := verifySelectorType(targetType, selectorFieldName, reflect.TypeOf((*K)(nil)).Elem(), opts) + if err != nil { + return err + } + + applier := func(cmd *cobra.Command, target any) error { + vTarget := reflect.ValueOf(target) + if vTarget.Kind() == reflect.Pointer { + vTarget = vTarget.Elem() + } + + selectedKey := extractSelectorValue[K](vTarget, selectorPath) + profile, exists := profiles[selectedKey] + if !exists { + // No profile for this selector value: nothing to default, not an error. + return nil + } + + // Scope the copy to the substruct owning the selector (e.g. "System" for + // "System.Env") so this profile can't clobber unrelated branches (e.g. "Chain") + // that just happen to be zero-valued in this profile's map entry. + scopePath := selectorPath[:len(selectorPath)-1] + leafName := selectorPath[len(selectorPath)-1] + targetScope := navigateFields(vTarget, scopePath) + profileScope := navigateFields(reflect.ValueOf(profile), scopePath) + prefix := scopePrefix(targetType, scopePath, opts) + + // The selector's own field (e.g. Chain.ID) sits inside the scoped substruct too; + // preserve the value that was actually used to pick this profile, since the + // profile's map entry usually leaves it zero (the profile is keyed by that value, + // not describing it). + leafField := targetScope.FieldByName(leafName) + selectedValue := reflect.ValueOf(leafField.Interface()) + + applyProfileDefaults(cmd, entry, opts, prefix, targetScope, profileScope) + + leafField.Set(selectedValue) + return nil + } + + entry.profiles = append(entry.profiles, applier) + enableProfileHelp(cmd, selectorPath, profiles) + return nil +} + +// findEntryByTargetType returns the single entry registered on meta whose target's type +// (dereferenced) matches targetType. +func findEntryByTargetType(meta *commandMetaData, targetType reflect.Type) (*targetEntry, error) { + var match *targetEntry + for _, e := range meta.entries { + et := reflect.TypeOf(e.target) + if et.Kind() == reflect.Pointer { + et = et.Elem() + } + if et != targetType { + continue + } + if match != nil { + return nil, fmt.Errorf("multiple registered targets of type %s on this command; ambiguous profile registration", targetType) + } + match = e + } + if match == nil { + return nil, fmt.Errorf("no registered target of type %s on this command; call RegisterCommandFlags/RegisterSubcommandFlags first", targetType) + } + return match, nil +} + +// --- INTERNAL HELPERS --- + +func verifySelectorType(tType reflect.Type, selectorFieldName string, kType reflect.Type, opts Options) ([]string, error) { + if tType.Kind() != reflect.Struct { + return nil, fmt.Errorf("target type T must be a struct") + } + + path, fType, found := findFieldByTagOrName(tType, selectorFieldName, opts) + if !found { + return nil, fmt.Errorf("field or tag %q not found in struct %s", selectorFieldName, tType.Name()) + } + + if fType != kType { + return nil, fmt.Errorf("type mismatch for %q in %s: field is %s, profile key K is %s", selectorFieldName, tType.Name(), fType, kType) + } + + return path, nil +} + +func findFieldByTagOrName(t reflect.Type, name string, opts Options) ([]string, reflect.Type, bool) { + if strings.Contains(name, ".") { + parts := strings.Split(name, ".") + curr := t + var fullPath []string + + for _, part := range parts { + if curr.Kind() == reflect.Pointer { + curr = curr.Elem() + } + if curr.Kind() != reflect.Struct { + return nil, nil, false + } + + subPath, fType, ok := findSingleField(curr, part, opts) + if !ok { + return nil, nil, false + } + fullPath = append(fullPath, subPath...) + curr = fType + } + return fullPath, curr, true + } + + return findSingleField(t, name, opts) +} + +func findSingleField(t reflect.Type, name string, opts Options) ([]string, reflect.Type, bool) { + for i := 0; i < t.NumField(); i++ { + field := t.Field(i) + if field.PkgPath != "" { + continue + } + + keyName, _ := opts.tagKey(field) + + if strings.EqualFold(field.Name, name) || strings.EqualFold(keyName, name) { + return []string{field.Name}, field.Type, true + } + + elemType := field.Type + if elemType.Kind() == reflect.Pointer { + elemType = elemType.Elem() + } + if elemType.Kind() == reflect.Struct { + if subPath, fType, ok := findSingleField(elemType, name, opts); ok { + return append([]string{field.Name}, subPath...), fType, true + } + } + } + return nil, nil, false +} + +func extractSelectorValue[K comparable](v reflect.Value, path []string) K { + curr := v + for _, p := range path { + if curr.Kind() == reflect.Pointer { + curr = curr.Elem() + } + curr = curr.FieldByName(p) + } + return curr.Interface().(K) +} + +// navigateFields walks v down through the named fields in path (dereferencing pointers +// as needed), returning the resulting nested field value. +func navigateFields(v reflect.Value, path []string) reflect.Value { + curr := v + for _, p := range path { + if curr.Kind() == reflect.Pointer { + curr = curr.Elem() + } + curr = curr.FieldByName(p) + } + return curr +} + +// scopePrefix computes the dotted toml/mapstructure key prefix (e.g. "system") that +// corresponds to the given Go field-name path (e.g. ["System"]) starting from struct type t. +func scopePrefix(t reflect.Type, path []string, opts Options) string { + var parts []string + curr := t + for _, name := range path { + if curr.Kind() == reflect.Pointer { + curr = curr.Elem() + } + field, ok := curr.FieldByName(name) + if !ok { + break + } + key, _ := opts.tagKey(field) + parts = append(parts, key) + curr = field.Type + } + return strings.Join(parts, ".") +} + +func applyProfileDefaults(cmd *cobra.Command, entry *targetEntry, opts Options, prefix string, tVal, pVal reflect.Value) { + if pVal.Kind() == reflect.Pointer { + pVal = pVal.Elem() + } + copyDefaultsRecursive(cmd, entry, opts, prefix, tVal, pVal) +} + +func copyDefaultsRecursive(cmd *cobra.Command, entry *targetEntry, opts Options, prefix string, tVal, pVal reflect.Value) { + namespace, prefixes := entry.namespace, entry.prefixes + tType := tVal.Type() + for i := 0; i < tType.NumField(); i++ { + field := tType.Field(i) + if field.PkgPath != "" { + continue + } + + key, _ := opts.tagKey(field) + + relKey := key + if prefix != "" { + relKey = prefix + "." + key + } + + viperKey := relKey + if namespace != "" { + viperKey = namespace + "." + relKey + } + + targetField := tVal.Field(i) + profileField := pVal.Field(i) + + if targetField.Kind() == reflect.Pointer { + if targetField.IsNil() && !profileField.IsNil() { + targetField.Set(reflect.New(targetField.Type().Elem())) + } + if !targetField.IsNil() && !profileField.IsNil() { + targetField = targetField.Elem() + profileField = profileField.Elem() + } else { + continue + } + } + + if targetField.Kind() == reflect.Struct { + copyDefaultsRecursive(cmd, entry, opts, relKey, targetField, profileField) + continue + } + + if !isExplicitlySet(cmd, flagNameFromViperKey(relKey), viperKey, prefixes) { + targetField.Set(profileField) + } + } +} + +// isExplicitlySet reports whether viperKey's value came from something other than its +// registered code default: a changed CLI flag, a config file entry, or a bound env var. +// flagName is passed separately because a namespaced entry's flag ("timeout") does not match +// its viper key ("foo.timeout"). +func isExplicitlySet(cmd *cobra.Command, flagName, viperKey string, prefixes []string) bool { + if f := cmd.Flags().Lookup(flagName); f != nil && f.Changed { + return true + } + + if viper.InConfig(viperKey) { + return true + } + + envSuffix := strings.ToUpper(strings.ReplaceAll(strings.ReplaceAll(viperKey, ".", "_"), "-", "_")) + for _, prefix := range prefixes { + cleanPrefix := strings.TrimSuffix(strings.ToUpper(prefix), "_") + if os.Getenv(cleanPrefix+"_"+envSuffix) != "" { + return true + } + } + + return false +} + +func flagNameFromViperKey(viperKey string) string { + parts := strings.Split(viperKey, ".") + for i, p := range parts { + parts[i] = strings.ReplaceAll(p, "_", "-") + } + return strings.Join(parts, ".") +} + +func enableProfileHelp[T any, K comparable](cmd *cobra.Command, selectorPath []string, profiles map[K]T) { + selectorFlagName := strings.ReplaceAll(strings.ToLower(selectorPath[len(selectorPath)-1]), "_", "-") + existingHelp := cmd.HelpFunc() + + cmd.SetHelpFunc(func(c *cobra.Command, args []string) { + if existingHelp != nil { + existingHelp(c, args) + } else { + fmt.Printf("Usage of %s:\n\n", c.CommandPath()) + c.Flags().VisitAll(func(f *pflag.Flag) { + defaultMsg := "" + if f.DefValue != "" { + defaultMsg = fmt.Sprintf(" (default %q)", f.DefValue) + } + fmt.Printf(" --%-35s %s%s\n", f.Name, f.Usage, defaultMsg) + }) + } + + fmt.Printf("\nAVAILABLE PROFILES FOR --%s:\n", selectorFlagName) + for k := range profiles { + fmt.Printf(" - %v\n", k) + } + }) +} + +func registerStructFlagsInternal(cmd *cobra.Command, entry *targetEntry, isSubcommand bool) error { + if entry.target == nil { + return fmt.Errorf("target cannot be nil") + } + + return walkStruct(entry.target, entry.opts, structVisitor{ + branch: func(m fieldMeta) (bool, error) { + if err := checkExclusiveStructIsPointer(m); err != nil { + return false, err + } + return false, entry.opts.checkEmbeddedIsUnnamed(m) + }, + leaf: func(m fieldMeta) error { + bindLeafFlag(cmd, entry, isSubcommand, m) + return nil + }, + }) +} + +// checkExclusiveStructIsPointer rejects a non-pointer nested struct carrying a cross-field +// rule. Such a field can never be absent - a zero struct is still a struct - so the rule can't +// distinguish "not configured" from "configured to zero", and the validator descends into it +// regardless and reports its inner `required` fields for a section the user never asked for. +// A pointer makes absence representable (nil), which is what these rules need. +func checkExclusiveStructIsPointer(m fieldMeta) error { + if m.field.Type.Kind() == reflect.Pointer || m.isTextUnmarshaler { + return nil + } + rules := crossFieldRuleNames(m.field) + if len(rules) == 0 { + return nil + } + return fmt.Errorf("%s: %s on a nested struct requires a pointer field (*%s); a value struct is never absent, so the rule cannot fire and %s's own required fields are reported even when the section is unused", + m.key(), strings.Join(rules, "/"), m.elemType.Name(), m.elemType.Name()) +} + +func bindLeafFlag(cmd *cobra.Command, entry *targetEntry, isSubcommand bool, m fieldMeta) { + namespace := entry.namespace + + relKey := m.key() + viperKey := relKey + if namespace != "" { + viperKey = namespace + "." + relKey + } + + flagKey := relKey + if entry.flagPrefix != "" { + flagKey = entry.flagPrefix + "." + relKey + } + flagName := flagNameFromViperKey(flagKey) + entry.keys = append(entry.keys, leafKey{relPath: m.keyPath, viperKey: viperKey, flagName: flagName}) + defaultVal := m.elem.Interface() + usageMsg := m.field.Tag.Get("usage") + + flags := cmd.Flags() + if !isSubcommand { + flags = cmd.PersistentFlags() + } + + switch { + case m.elemType == configDurationType: + d := defaultVal.(config.Duration) + // Default is stored as its text form so that every source (flag, env, config file, + // default) reaches the decoder as a string and goes through UnmarshalText. Handing + // mapstructure the config.Duration struct instead would decode struct-to-struct and + // silently drop the value, since its only field is unexported. + viper.SetDefault(viperKey, d.String()) + flags.Duration(flagName, d.Duration(), usageMsg) + case m.isTextUnmarshaler: + text := fmt.Sprintf("%v", defaultVal) + viper.SetDefault(viperKey, text) + flags.String(flagName, text, usageMsg) + case m.elemType == durationType: + viper.SetDefault(viperKey, defaultVal) + flags.Duration(flagName, time.Duration(m.elem.Int()), usageMsg) + case m.elemType.Kind() == reflect.Slice && m.elemType.Elem().Kind() == reflect.String: + var def []string + if !m.elem.IsNil() { + def = m.elem.Interface().([]string) + } + viper.SetDefault(viperKey, def) + flags.StringSlice(flagName, def, usageMsg) + default: + viper.SetDefault(viperKey, defaultVal) + switch m.elemType.Kind() { + case reflect.String: + flags.String(flagName, m.elem.String(), usageMsg) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + flags.Int64(flagName, m.elem.Int(), usageMsg) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + flags.Uint64(flagName, m.elem.Uint(), usageMsg) + case reflect.Bool: + flags.Bool(flagName, m.elem.Bool(), usageMsg) + default: + flags.String(flagName, fmt.Sprintf("%v", defaultVal), usageMsg) + } + } + + _ = viper.BindPFlag(viperKey, flags.Lookup(flagName)) + // Env vars are bound later, by entry.bindEnv at decode time - see effectivePrefixes. +} diff --git a/pkg/config/flags/flags_test.go b/pkg/config/flags/flags_test.go new file mode 100644 index 0000000000..89d5c5c79b --- /dev/null +++ b/pkg/config/flags/flags_test.go @@ -0,0 +1,1173 @@ +package flags + +import ( + "io" + "os" + "path/filepath" + "strings" + "sync" + "testing" + "time" + + "github.com/spf13/cobra" + "github.com/spf13/viper" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/chainlink-common/pkg/config" + "github.com/smartcontractkit/chainlink-common/pkg/config/configdoc" +) + +// newRoot returns a root command wired for testing: its own viper, a --config flag, and +// silenced output. Global state (the viper singleton and the load-config-once guard) is +// restored on cleanup so tests can't leak keys or a consumed once into each other. +func newRoot(t *testing.T) *cobra.Command { + t.Helper() + + oldViper := viper.GetViper() + viper.Reset() + t.Cleanup(func() { *viper.GetViper() = *oldViper }) + + oldOnce, oldErr := configFileOnce, configFileErr + configFileOnce, configFileErr = new(sync.Once), nil + t.Cleanup(func() { configFileOnce, configFileErr = oldOnce, oldErr }) + + cmd := &cobra.Command{Use: "app", RunE: func(*cobra.Command, []string) error { return nil }} + cmd.PersistentFlags().String("config", "", "path to config file") + cmd.SetOut(io.Discard) + cmd.SetErr(io.Discard) + return cmd +} + +// run registers target on a fresh root command and executes it with args, returning the error +// from the decode/validate step (nil if the config was accepted). +func run(t *testing.T, target any, args ...string) error { + t.Helper() + + root := newRoot(t) + require.NoError(t, RegisterCommandFlags(root, target, DefaultTOMLOptions("TEST"))) + root.SetArgs(args) + return root.Execute() +} + +// writeConfig writes a TOML config file and returns its path, for passing via --config. +func writeConfig(t *testing.T, contents string) string { + t.Helper() + + path := filepath.Join(t.TempDir(), "config.toml") + require.NoError(t, os.WriteFile(path, []byte(contents), 0o600)) + return path +} + +func TestRequiredLeaf(t *testing.T) { + type cfg struct { + Name string `toml:"name" validate:"required"` + } + + t.Run("missing", func(t *testing.T) { + var c cfg + require.ErrorContains(t, run(t, &c), "'required'") + }) + + t.Run("provided", func(t *testing.T) { + var c cfg + require.NoError(t, run(t, &c, "--name", "x")) + assert.Equal(t, "x", c.Name) + }) +} + +func TestNestedStructDecodes(t *testing.T) { + type inner struct { + Host string `toml:"host"` + } + type cfg struct { + Chain inner `toml:"chain"` + } + + var c cfg + require.NoError(t, run(t, &c, "--chain.host", "example.com")) + assert.Equal(t, "example.com", c.Chain.Host) +} + +func TestSquashedStructDecodes(t *testing.T) { + type inner struct { + Host string `toml:"host"` + } + type cfg struct { + // DefaultTOMLOptions names the squash option "inline", matching TOML's inline table. + Inner inner `toml:",inline"` + } + + // Squashed fields contribute no key segment, so the flag is --host, not --inner.host. + var c cfg + require.NoError(t, run(t, &c, "--host", "example.com")) + assert.Equal(t, "example.com", c.Inner.Host) +} + +func TestSquashOptionIsConfigurable(t *testing.T) { + type inner struct { + Host string `toml:"host"` + } + type cfg struct { + Inner inner `toml:",squash"` + } + + opts := DefaultTOMLOptions("TEST") + opts.DecoderConfig.SquashTagOption = "squash" + + root := newRoot(t) + var c cfg + require.NoError(t, RegisterCommandFlags(root, &c, opts)) + + root.SetArgs([]string{"--host", "example.com"}) + require.NoError(t, root.Execute()) + assert.Equal(t, "example.com", c.Inner.Host) +} + +// Exported so the embedded field itself is exported (an embedded unexported type is an +// unexported field, which the walker skips). +type EmbeddedInner struct { + Host string `toml:"host" mapstructure:"host"` +} + +func TestEmbeddedStructIsSquashedWhenDecoderSquashes(t *testing.T) { + type cfg struct { + EmbeddedInner // no tag; DefaultTOMLOptions sets DecoderConfig.Squash + } + + var c cfg + require.NoError(t, run(t, &c, "--host", "example.com")) + assert.Equal(t, "example.com", c.Host) +} + +func TestEmbeddedStructIsNestedWhenDecoderDoesNot(t *testing.T) { + type cfg struct { + EmbeddedInner + } + + // Squash off: mapstructure treats the embedded struct as a field named after its type, + // so the flag must be namespaced to match, not flattened to --host. + opts := DefaultTOMLOptions("TEST") + opts.DecoderConfig.Squash = false + + root := newRoot(t) + var c cfg + require.NoError(t, RegisterCommandFlags(root, &c, opts)) + + root.SetArgs([]string{"--embeddedinner.host", "example.com"}) + require.NoError(t, root.Execute()) + assert.Equal(t, "example.com", c.Host) +} + +func TestNamedEmbeddedStructIsRejectedWhenSquashing(t *testing.T) { + type cfg struct { + // The name is a lie under squashing: these fields flatten into the parent, but + // encoding/json would nest them under "inner". + EmbeddedInner `toml:"inner"` + } + + err := RegisterCommandFlags(newRoot(t), &cfg{}, DefaultTOMLOptions("TEST")) + require.Error(t, err) + assert.Contains(t, err.Error(), "must not be named") +} + +func TestNamedEmbeddedStructIsAllowedWhenNotSquashing(t *testing.T) { + type cfg struct { + EmbeddedInner `toml:"inner"` + } + + // Squash off, so the name is honoured rather than ignored - and agrees with json. + opts := DefaultTOMLOptions("TEST") + opts.DecoderConfig.Squash = false + + root := newRoot(t) + var c cfg + require.NoError(t, RegisterCommandFlags(root, &c, opts)) + + root.SetArgs([]string{"--inner.host", "example.com"}) + require.NoError(t, root.Execute()) + assert.Equal(t, "example.com", c.Host) +} + +func TestUnnamedEmbeddedStructMayCarryTagOptions(t *testing.T) { + type cfg struct { + // Options without a name are fine: nothing is being contradicted. + EmbeddedInner `toml:",inline"` + } + + var c cfg + require.NoError(t, run(t, &c, "--host", "example.com")) + assert.Equal(t, "example.com", c.Host) +} + +func TestTagNameIsConfigurable(t *testing.T) { + type cfg struct { + Host string `mapstructure:"host"` + } + + // The zero Options falls back to mapstructure's own tag name and squash option. + root := newRoot(t) + var c cfg + require.NoError(t, RegisterCommandFlags(root, &c, Options{Prefixes: []string{"TEST"}})) + + root.SetArgs([]string{"--host", "example.com"}) + require.NoError(t, root.Execute()) + assert.Equal(t, "example.com", c.Host) +} + +func TestOptionalNestedStructStaysNil(t *testing.T) { + type inner struct { + Host string `toml:"host" validate:"required"` + } + type cfg struct { + // Nothing under it was supplied, so it must stay nil rather than being allocated + // with defaults - otherwise its inner `required` would fire for an absent section. + Inner *inner `toml:"inner"` + } + + var c cfg + require.NoError(t, run(t, &c)) + assert.Nil(t, c.Inner) +} + +func TestOptionalNestedStructAllocatedWhenSet(t *testing.T) { + type inner struct { + Host string `toml:"host" validate:"required"` + } + type cfg struct { + Inner *inner `toml:"inner"` + } + + var c cfg + require.NoError(t, run(t, &c, "--inner.host", "example.com")) + require.NotNil(t, c.Inner) + assert.Equal(t, "example.com", c.Inner.Host) +} + +// The exactly-one-of shape: two mutually exclusive nested sections, each required when the +// other is absent. Pointers make "absent" representable, which is what stops the unselected +// section's own `required` fields from being reported. +type modeB struct { + X int32 `toml:"x" validate:"required"` + Z int32 `toml:"z" validate:"required"` + W int32 `toml:"w"` // deliberately not required +} + +type modeC struct { + Y int32 `toml:"y"` + Q int32 `toml:"q" validate:"required"` +} + +type modesCfg struct { + B *modeB `toml:"b" validate:"required_without=C,excluded_with=C"` + C *modeC `toml:"c" validate:"required_without=B,excluded_with=B"` +} + +func TestExclusiveModes_NeitherSet(t *testing.T) { + var c modesCfg + err := run(t, &c) + require.Error(t, err) + assert.Contains(t, err.Error(), "'required_without'") +} + +func TestExclusiveModes_OnlyBSet(t *testing.T) { + var c modesCfg + // C is absent, so none of C's own required fields should be reported. + require.NoError(t, run(t, &c, "--b.x", "1", "--b.z", "2")) + require.NotNil(t, c.B) + assert.Nil(t, c.C) + assert.Equal(t, int32(1), c.B.X) +} + +func TestExclusiveModes_OnlyCSet(t *testing.T) { + var c modesCfg + require.NoError(t, run(t, &c, "--c.q", "5")) + require.NotNil(t, c.C) + assert.Nil(t, c.B) +} + +func TestExclusiveModes_BothSetIsRejected(t *testing.T) { + var c modesCfg + err := run(t, &c, "--b.x", "1", "--b.z", "2", "--c.q", "5") + require.Error(t, err) + assert.Contains(t, err.Error(), "'excluded_with'") +} + +func TestExclusiveModes_PartialSectionReportsItsOwnRequired(t *testing.T) { + var c modesCfg + // B is present but incomplete: its missing required field is what should be reported. + err := run(t, &c, "--b.x", "1") + require.Error(t, err) + assert.Contains(t, err.Error(), "'Z'") +} + +func TestExclusiveModes_OnlyNonRequiredFieldSet(t *testing.T) { + var c modesCfg + // Setting only W allocates B, so B's unset required fields are reported. + err := run(t, &c, "--b.w", "9") + require.Error(t, err) + assert.Contains(t, err.Error(), "'X'") + assert.Contains(t, err.Error(), "'Z'") +} + +func TestValueStructWithCrossFieldRuleIsRejected(t *testing.T) { + type inner struct { + Host string `toml:"host" validate:"required"` + } + type other struct { + Addr string `toml:"addr"` + } + type cfg struct { + // Not a pointer, so it can never be absent - registration should refuse it rather + // than silently mis-validating at run time. + A inner `toml:"a" validate:"excluded_with=B"` + B other `toml:"b"` + } + + cmd := &cobra.Command{Use: "app"} + err := RegisterCommandFlags(cmd, &cfg{}, DefaultTOMLOptions()) + require.Error(t, err) + assert.Contains(t, err.Error(), "requires a pointer field") +} + +func TestExcludedWithoutLeaf(t *testing.T) { + type cfg struct { + URL string `toml:"url"` + RealDB bool `toml:"real-db" validate:"excluded_without=URL"` + } + + t.Run("set without its dependency", func(t *testing.T) { + var c cfg + require.ErrorContains(t, run(t, &c, "--real-db"), "'excluded_without'") + }) + + t.Run("set with its dependency", func(t *testing.T) { + var c cfg + require.NoError(t, run(t, &c, "--url", "postgres://x", "--real-db")) + }) +} + +func TestRequiredWithLeaf(t *testing.T) { + type cfg struct { + Enable bool `toml:"enable"` + Token string `toml:"token" validate:"required_with=Enable"` + } + + t.Run("dependency set, field missing", func(t *testing.T) { + var c cfg + require.ErrorContains(t, run(t, &c, "--enable"), "'required_with'") + }) + + t.Run("dependency unset", func(t *testing.T) { + var c cfg + require.NoError(t, run(t, &c)) + }) +} + +// --- precedence: flag > env > config file > compiled-in default --- + +type precedenceCfg struct { + Value string `toml:"value"` +} + +func newPrecedenceCfg() *precedenceCfg { return &precedenceCfg{Value: "from-default"} } + +func TestPrecedence_DefaultWhenNothingSet(t *testing.T) { + c := newPrecedenceCfg() + require.NoError(t, run(t, c)) + assert.Equal(t, "from-default", c.Value) +} + +func TestPrecedence_ConfigFileBeatsDefault(t *testing.T) { + path := writeConfig(t, "value = 'from-file'\n") + + c := newPrecedenceCfg() + require.NoError(t, run(t, c, "--config", path)) + assert.Equal(t, "from-file", c.Value) +} + +func TestPrecedence_EnvBeatsConfigFile(t *testing.T) { + path := writeConfig(t, "value = 'from-file'\n") + t.Setenv("TEST_VALUE", "from-env") + + c := newPrecedenceCfg() + require.NoError(t, run(t, c, "--config", path)) + assert.Equal(t, "from-env", c.Value) +} + +func TestPrecedence_FlagBeatsEnv(t *testing.T) { + path := writeConfig(t, "value = 'from-file'\n") + t.Setenv("TEST_VALUE", "from-env") + + c := newPrecedenceCfg() + require.NoError(t, run(t, c, "--config", path, "--value", "from-flag")) + assert.Equal(t, "from-flag", c.Value) +} + +func TestEnvPrefixesTriedInOrder(t *testing.T) { + type cfg struct { + Value string `toml:"value"` + } + + root := newRoot(t) + var c cfg + require.NoError(t, RegisterCommandFlags(root, &c, DefaultTOMLOptions("FIRST", "SECOND"))) + + // Both are bound; the earlier prefix wins. + t.Setenv("FIRST_VALUE", "first") + t.Setenv("SECOND_VALUE", "second") + + root.SetArgs(nil) + require.NoError(t, root.Execute()) + assert.Equal(t, "first", c.Value) +} + +// --- subcommands --- + +// newSubcommand registers rootTarget on a root and subTarget under namespace on a child +// command, returning both so a test can execute the child. +func newSubcommand(t *testing.T, rootTarget, subTarget any, namespace string) *cobra.Command { + t.Helper() + + root := newRoot(t) + require.NoError(t, RegisterCommandFlags(root, rootTarget, DefaultTOMLOptions("TEST"))) + + sub := &cobra.Command{Use: namespace, RunE: func(*cobra.Command, []string) error { return nil }} + require.NoError(t, RegisterSubcommandFlags(sub, namespace, subTarget, DefaultTOMLOptions())) + root.AddCommand(sub) + return root +} + +func TestSubcommand_FlagIsNotNamespaced(t *testing.T) { + type rootCfg struct { + Host string `toml:"host"` + } + type subCfg struct { + Retries int `toml:"retries"` + } + + var r rootCfg + var s subCfg + root := newSubcommand(t, &r, &s, "sub") + + // The viper key is "sub.retries", but the flag stays --retries. + root.SetArgs([]string{"sub", "--retries", "9"}) + require.NoError(t, root.Execute()) + assert.Equal(t, 9, s.Retries) +} + +func TestSubcommand_EnvIsNamespaced(t *testing.T) { + type rootCfg struct { + Host string `toml:"host"` + } + type subCfg struct { + Retries int `toml:"retries"` + } + + var r rootCfg + var s subCfg + root := newSubcommand(t, &r, &s, "sub") + + // Namespace appears in the env var (and the root's prefix is inherited). + t.Setenv("TEST_SUB_RETRIES", "11") + + root.SetArgs([]string{"sub"}) + require.NoError(t, root.Execute()) + assert.Equal(t, 11, s.Retries) +} + +func TestSubcommand_ConfigFileUsesNamespacedTable(t *testing.T) { + type rootCfg struct { + Host string `toml:"host"` + } + type subCfg struct { + Retries int `toml:"retries"` + } + + var r rootCfg + var s subCfg + root := newSubcommand(t, &r, &s, "sub") + path := writeConfig(t, "host = 'example.com'\n\n[sub]\nretries = 4\n") + + root.SetArgs([]string{"sub", "--config", path}) + require.NoError(t, root.Execute()) + assert.Equal(t, 4, s.Retries) + assert.Equal(t, "example.com", r.Host, "root config decodes too when a subcommand runs") +} + +func TestSubcommand_RootValidationStillApplies(t *testing.T) { + type rootCfg struct { + Host string `toml:"host" validate:"required"` + } + type subCfg struct { + Retries int `toml:"retries"` + } + + var r rootCfg + var s subCfg + root := newSubcommand(t, &r, &s, "sub") + + root.SetArgs([]string{"sub"}) + require.ErrorContains(t, root.Execute(), "'required'") +} + +// --- hook chaining --- + +func TestCallersPreRunESeesDecodedConfig(t *testing.T) { + type cfg struct { + Host string `toml:"host"` + } + + root := newRoot(t) + var c cfg + var seen string + root.PreRunE = func(*cobra.Command, []string) error { + seen = c.Host // must already be populated + return nil + } + require.NoError(t, RegisterCommandFlags(root, &c, DefaultTOMLOptions("TEST"))) + + root.SetArgs([]string{"--host", "example.com"}) + require.NoError(t, root.Execute()) + assert.Equal(t, "example.com", seen) +} + +func TestCallersPersistentPreRunEStillRuns(t *testing.T) { + type cfg struct { + Host string `toml:"host"` + } + + root := newRoot(t) + var c cfg + called := false + root.PersistentPreRunE = func(*cobra.Command, []string) error { + called = true + return nil + } + require.NoError(t, RegisterCommandFlags(root, &c, DefaultTOMLOptions("TEST"))) + + root.SetArgs(nil) + require.NoError(t, root.Execute()) + assert.True(t, called) +} + +// --- several independent targets on one command --- + +func TestMultipleTargetsDecodeIndependently(t *testing.T) { + type dbCfg struct { + URL string `toml:"db-url" validate:"required"` + } + type evmCfg struct { + ChainID string `toml:"evm-chain-id" validate:"required"` + } + + root := newRoot(t) + var db dbCfg + var evm evmCfg + require.NoError(t, RegisterCommandFlags(root, &db, DefaultTOMLOptions("TEST"))) + require.NoError(t, RegisterCommandFlags(root, &evm, DefaultTOMLOptions("TEST"))) + + root.SetArgs([]string{"--db-url", "postgres://x", "--evm-chain-id", "1"}) + require.NoError(t, root.Execute()) + assert.Equal(t, "postgres://x", db.URL) + assert.Equal(t, "1", evm.ChainID) +} + +func TestMultipleTargetsBothReportTheirOwnErrors(t *testing.T) { + type dbCfg struct { + URL string `toml:"db-url" validate:"required"` + } + type evmCfg struct { + ChainID string `toml:"evm-chain-id" validate:"required"` + } + + root := newRoot(t) + require.NoError(t, RegisterCommandFlags(root, &dbCfg{}, DefaultTOMLOptions("TEST"))) + require.NoError(t, RegisterCommandFlags(root, &evmCfg{}, DefaultTOMLOptions("TEST"))) + + root.SetArgs(nil) + err := root.Execute() + require.Error(t, err) + assert.Contains(t, err.Error(), "'URL'") + assert.Contains(t, err.Error(), "'ChainID'", "one target's failure must not hide the other's") +} + +// --- profiles --- + +type profileChain struct { + ID uint32 `toml:"id"` + RPC string `toml:"rpc"` +} + +type profileCfg struct { + Chain profileChain `toml:"chain"` +} + +// runWithProfile registers c plus a profile map keyed on Chain.ID, then executes with args. +func runWithProfile(t *testing.T, c *profileCfg, args ...string) error { + t.Helper() + + root := newRoot(t) + require.NoError(t, RegisterCommandFlags(root, c, DefaultTOMLOptions("TEST"))) + require.NoError(t, RegisterProfile(root, "Chain.ID", map[uint32]profileCfg{ + 1: {Chain: profileChain{RPC: "https://one"}}, + 137: {Chain: profileChain{RPC: "https://one-thirty-seven"}}, + }, DefaultTOMLOptions("TEST"))) + root.SetArgs(args) + return root.Execute() +} + +func TestProfileFillsDefaultsForSelectedKey(t *testing.T) { + c := &profileCfg{Chain: profileChain{ID: 1}} + require.NoError(t, runWithProfile(t, c)) + assert.Equal(t, "https://one", c.Chain.RPC) +} + +func TestProfileFollowsSelector(t *testing.T) { + c := &profileCfg{Chain: profileChain{ID: 1}} + require.NoError(t, runWithProfile(t, c, "--chain.id", "137")) + assert.Equal(t, uint32(137), c.Chain.ID) + assert.Equal(t, "https://one-thirty-seven", c.Chain.RPC) +} + +func TestProfileDoesNotOverrideExplicitValue(t *testing.T) { + c := &profileCfg{Chain: profileChain{ID: 1}} + require.NoError(t, runWithProfile(t, c, "--chain.rpc", "https://mine")) + assert.Equal(t, "https://mine", c.Chain.RPC) +} + +func TestProfileUnknownSelectorAppliesNothing(t *testing.T) { + c := &profileCfg{Chain: profileChain{ID: 1}} + require.NoError(t, runWithProfile(t, c, "--chain.id", "999")) + assert.Empty(t, c.Chain.RPC, "no matching profile means no defaults, not an error") +} + +func TestProfileRequiresARegisteredTargetOfItsType(t *testing.T) { + root := newRoot(t) + err := RegisterProfile(root, "Chain.ID", map[uint32]profileCfg{1: {}}, DefaultTOMLOptions()) + require.ErrorContains(t, err, "no registered target") +} + +// --- slices --- + +func TestStringSliceFromRepeatedFlag(t *testing.T) { + type cfg struct { + URLs []string `toml:"url"` + } + + var c cfg + require.NoError(t, run(t, &c, "--url", "a", "--url", "b")) + assert.Equal(t, []string{"a", "b"}, c.URLs) +} + +func TestStringSliceFromCommaSeparatedEnv(t *testing.T) { + type cfg struct { + URLs []string `toml:"url"` + } + + t.Setenv("TEST_URL", "a,b") + + var c cfg + require.NoError(t, run(t, &c)) + assert.Equal(t, []string{"a", "b"}, c.URLs) +} + +// --- config.Duration --- + +func TestConfigDurationDecodes(t *testing.T) { + type cfg struct { + Timeout config.Duration `toml:"timeout"` + } + + c := cfg{Timeout: *config.MustNewDuration(5 * time.Second)} + require.NoError(t, run(t, &c, "--timeout", "45s")) + assert.Equal(t, 45*time.Second, c.Timeout.Duration()) +} + +func TestConfigDurationRejectsNegative(t *testing.T) { + type cfg struct { + Timeout config.Duration `toml:"timeout"` + } + + // pflag accepts -5s as a duration; config.Duration is what refuses it. + t.Setenv("TEST_TIMEOUT", "-5s") + + var c cfg + require.ErrorContains(t, run(t, &c), "negative") +} + +// --- generated docs --- + +func TestDocsMarksDefaultsAndRequiredFields(t *testing.T) { + type cfg struct { + Host string `toml:"host" usage:"the host" validate:"required" example:"'example.com'"` + Retries int `toml:"retries" usage:"how many times to retry"` + } + + root := newRoot(t) + require.NoError(t, RegisterCommandFlags(root, &cfg{Retries: 3}, DefaultTOMLOptions("TEST"))) + + doc, err := GenerateDocs(root) + require.NoError(t, err) + // A required field has no real default, so it's documented by example. + assert.Contains(t, doc, "host = 'example.com' # Example") + assert.Contains(t, doc, "retries = 3 # Default") +} + +func TestDocsPutsNestedStructUsageInItsTableHeader(t *testing.T) { + type chain struct { + Host string `toml:"host" usage:"the host"` + } + type cfg struct { + Chain chain `toml:"chain" usage:"which chain to talk to"` + } + + toml, err := structToDocs(&cfg{}, "", DefaultTOMLOptions()) + require.NoError(t, err) + + // The struct's description sits in its table's header, above the fields. + assert.Contains(t, toml, "# which chain to talk to\n[chain]\n") +} + +func TestDocsFoldsSquashedStructUsageIntoEnclosingHeader(t *testing.T) { + type shared struct { + Host string `toml:"host" usage:"the host"` + } + type chain struct { + Shared shared `toml:",inline" usage:"settings shared by every chain"` + Name string `toml:"name" usage:"chain name"` + } + type cfg struct { + Chain chain `toml:"chain" usage:"which chain to talk to"` + } + + toml, err := structToDocs(&cfg{}, "", DefaultTOMLOptions()) + require.NoError(t, err) + + // A squashed struct gets no table of its own, so its description joins the header of the + // table it was flattened into, after that table's own description. + assert.Contains(t, toml, "# which chain to talk to\n# settings shared by every chain\n[chain]\n") + // Its fields still belong to that table, un-prefixed. + assert.Contains(t, toml, "host = ") + assert.NotContains(t, toml, "[chain.shared]") +} + +func TestDocsKeepsTopLevelSquashedStructUsage(t *testing.T) { + type shared struct { + Host string `toml:"host" usage:"the host"` + } + type cfg struct { + Shared shared `toml:",inline" usage:"settings shared by everything"` + } + + toml, err := structToDocs(&cfg{}, "", DefaultTOMLOptions()) + require.NoError(t, err) + + // No enclosing table at the top level, so it survives as standalone prose: a comment + // block ended by a blank line, which is what keeps it off the next field's description. + assert.True(t, strings.HasPrefix(toml, "# settings shared by everything\n\n"), toml) + + // And it still renders, rather than tripping configdoc's description checks. + root := newRoot(t) + require.NoError(t, RegisterCommandFlags(root, &cfg{}, DefaultTOMLOptions("TEST"))) + doc, err := GenerateDocs(root) + require.NoError(t, err) + assert.Contains(t, doc, "settings shared by everything") +} + +func TestNamespaceRootsKeysFlagsAndEnv(t *testing.T) { + type cfg struct { + URL string `toml:"url"` + } + + opts := DefaultTOMLOptions("TEST") + opts.Namespace = "database" + + t.Run("flag", func(t *testing.T) { + root := newRoot(t) + var c cfg + require.NoError(t, RegisterCommandFlags(root, &c, opts)) + + root.SetArgs([]string{"--database.url", "postgres://x"}) + require.NoError(t, root.Execute()) + assert.Equal(t, "postgres://x", c.URL) + }) + + t.Run("env", func(t *testing.T) { + t.Setenv("TEST_DATABASE_URL", "postgres://env") + + root := newRoot(t) + var c cfg + require.NoError(t, RegisterCommandFlags(root, &c, opts)) + + root.SetArgs(nil) + require.NoError(t, root.Execute()) + assert.Equal(t, "postgres://env", c.URL) + }) + + t.Run("config file", func(t *testing.T) { + path := writeConfig(t, "[database]\nurl = 'postgres://file'\n") + + root := newRoot(t) + var c cfg + require.NoError(t, RegisterCommandFlags(root, &c, opts)) + + root.SetArgs([]string{"--config", path}) + require.NoError(t, root.Execute()) + assert.Equal(t, "postgres://file", c.URL) + }) +} + +func TestNamespaceSeparatesSameNamedFields(t *testing.T) { + type dbCfg struct { + URL string `toml:"url"` + } + type evmCfg struct { + URL string `toml:"url"` + } + + dbOpts := DefaultTOMLOptions("TEST") + dbOpts.Namespace = "database" + evmOpts := DefaultTOMLOptions("TEST") + evmOpts.Namespace = "evm" + + root := newRoot(t) + var db dbCfg + var evm evmCfg + require.NoError(t, RegisterCommandFlags(root, &db, dbOpts)) + // Same field name, different namespace: no flag collision, and each keeps its own value. + require.NoError(t, RegisterCommandFlags(root, &evm, evmOpts)) + + root.SetArgs([]string{"--database.url", "postgres://x", "--evm.url", "https://y"}) + require.NoError(t, root.Execute()) + assert.Equal(t, "postgres://x", db.URL) + assert.Equal(t, "https://y", evm.URL) +} + +func TestNamespaceGroupsDocsUnderOneTable(t *testing.T) { + type cfg struct { + URL string `toml:"url" usage:"database url"` + } + + opts := DefaultTOMLOptions("TEST") + opts.Namespace = "database" + + toml, err := structToDocs(&cfg{}, opts.Namespace, opts) + require.NoError(t, err) + assert.Contains(t, toml, "[database]") + assert.Contains(t, toml, "url = ") +} + +func TestFlagDocsNoExampleOmitsFromExampleButKeepsDocs(t *testing.T) { + type cfg struct { + URL string `toml:"url" usage:"database url"` + RealDB bool `toml:"real-db" usage:"use a real database in fake mode" flagdocs:"noexample"` + } + + opts := DefaultTOMLOptions("TEST") + + example, err := exampleDoc(&cfg{}, "", opts) + require.NoError(t, err) + assert.NotContains(t, example, "real-db", "the example must not carry it") + assert.Contains(t, example, "url = ") + + docs, err := structToDocs(&cfg{}, "", opts) + require.NoError(t, err) + // Still documented, but marked so it is kept out of its table's code block too - every + // example in the document then describes the same working configuration. + assert.Contains(t, docs, "real-db = false "+configdoc.FieldDocsOnly) +} + +func TestDocsOnlyFieldIsAbsentFromItsTableCodeBlock(t *testing.T) { + type inner struct { + URL string `toml:"url" usage:"database url"` + RealDB bool `toml:"real-db" usage:"use a real database in fake mode" flagdocs:"noexample"` + } + type cfg struct { + Database inner `toml:"database"` + } + + root := newRoot(t) + require.NoError(t, RegisterCommandFlags(root, &cfg{}, DefaultTOMLOptions("TEST"))) + + doc, err := GenerateDocs(root) + require.NoError(t, err) + + // The table's own code block lists only what an example would contain... + section := doc[strings.Index(doc, "## database"):] + block := section[:strings.Index(section, "###")] + assert.Contains(t, block, "url = ") + assert.NotContains(t, block, "real-db") + + // ...while the field still gets its own entry further down. + assert.Contains(t, doc, "### real-db") +} + +func TestFlagDocsExampleOverridesTheValueShown(t *testing.T) { + type cfg struct { + Workers int `toml:"workers" usage:"how many workers" flagdocs:"example=8"` + } + + opts := DefaultTOMLOptions("TEST") + + example, err := exampleDoc(&cfg{Workers: 1}, "", opts) + require.NoError(t, err) + assert.Contains(t, example, "workers = 8", "the example shows the override") + + docs, err := structToDocs(&cfg{Workers: 1}, "", opts) + require.NoError(t, err) + assert.Contains(t, docs, "workers = 1 # Default", "the docs still show the real default") +} + +func TestExampleShowsOnlyTheFirstOfMutuallyExclusiveFields(t *testing.T) { + type cfg struct { + Listen []string `toml:"listen-addresses" usage:"listen here" validate:"required_without=Proxy,excluded_with=Proxy"` + Proxy string `toml:"proxy-address" usage:"or proxy there" validate:"excluded_with=Listen"` + } + + opts := DefaultTOMLOptions("TEST") + + example, err := exampleDoc(&cfg{}, "", opts) + require.NoError(t, err) + // Listing both would be a config that fails its own validation, so the example commits + // to the first. + assert.Contains(t, example, "listen-addresses = ") + assert.NotContains(t, example, "proxy-address = ") + + // Both are still documented; only the example has to choose. + docs, err := structToDocs(&cfg{}, "", opts) + require.NoError(t, err) + assert.Contains(t, docs, "listen-addresses = ") + assert.Contains(t, docs, "proxy-address = ") +} + +func TestExampleShowsOnlyTheFirstOfMutuallyExclusiveSections(t *testing.T) { + var c modesCfg + + example, err := exampleDoc(&c, "", DefaultTOMLOptions("TEST")) + require.NoError(t, err) + assert.Contains(t, example, "[b]") + assert.NotContains(t, example, "[c]", "the whole excluded section is dropped, not just its fields") +} + +func TestDocsTreatsExampleTagAsHavingNoDefault(t *testing.T) { + type cfg struct { + // Required by a rule this struct can't express, so it says so with an example. + URL string `toml:"url" usage:"database url" example:"'postgres://localhost/db'"` + } + + root := newRoot(t) + require.NoError(t, RegisterCommandFlags(root, &cfg{}, DefaultTOMLOptions("TEST"))) + + doc, err := GenerateDocs(root) + require.NoError(t, err) + assert.Contains(t, doc, "url = 'postgres://localhost/db' # Example") +} + +func TestDocsExplainsCrossFieldRulesUsingConfigKeys(t *testing.T) { + type cfg struct { + URL string `toml:"database-url" usage:"database url"` + RealDB bool `toml:"real-db" usage:"use a real db" validate:"excluded_without=URL"` + } + + root := newRoot(t) + require.NoError(t, RegisterCommandFlags(root, &cfg{}, DefaultTOMLOptions("TEST"))) + + doc, err := GenerateDocs(root) + require.NoError(t, err) + // Named by config key ("database-url"), not Go field name ("URL"). + assert.Contains(t, doc, "must not be set unless database-url is set") +} + +func TestDocsCoversSubcommands(t *testing.T) { + type rootCfg struct { + Host string `toml:"host" usage:"the host"` + } + type subCfg struct { + Retries int `toml:"retries" usage:"how many times to retry"` + } + + root := newSubcommand(t, &rootCfg{}, &subCfg{Retries: 3}, "sub") + + doc, err := GenerateDocs(root) + require.NoError(t, err) + assert.Contains(t, doc, "Global Configuration") + assert.Contains(t, doc, "Command: app sub") + assert.Contains(t, doc, "retries = 3 # Default", "subcommand fields are documented too") +} + +// spyFormat is configdoc.TOML with the value syntax changed, to prove the document really is +// assembled and rendered through Options.Format rather than hard-coded TOML. +type spyFormat struct { + configdoc.TOML +} + +func (f spyFormat) Field(key, value, marker string) string { + if marker == "" { + return key + ": " + value + } + return key + ": " + value + " " + marker +} + +// A format's two sides have to agree: whatever Field writes, ParseLine has to read back. +func (f spyFormat) ParseLine(line string) configdoc.Line { + l := f.TOML.ParseLine(line) + if l.Kind == configdoc.LineField { + l.Text = strings.TrimSuffix(l.Text, ":") + } + return l +} + +func TestDocsAssemblesWithFormatFromOptions(t *testing.T) { + type cfg struct { + Host string `toml:"host" usage:"the host"` + } + + opts := DefaultTOMLOptions("TEST") + opts.Format = spyFormat{} + + toml, err := structToDocs(&cfg{Host: "example.com"}, "", opts) + require.NoError(t, err) + // The format's Field syntax is used, not TOML's "key = value". + assert.Contains(t, toml, "host: 'example.com' # Default") +} + +func TestDocsCommandUsesFormatFromOptions(t *testing.T) { + type cfg struct { + Host string `toml:"host" usage:"the host"` + } + + opts := DefaultTOMLOptions("TEST") + opts.Format = spyFormat{} + + root := newRoot(t) + require.NoError(t, RegisterCommandFlags(root, &cfg{}, opts)) + + dir := t.TempDir() + t.Chdir(dir) + + // The format must reach the auto-added docs command too, not just GenerateDocs. + root.SetArgs([]string{"docs"}) + require.NoError(t, root.Execute()) + + written, err := os.ReadFile(filepath.Join(dir, docsOutputPath)) + require.NoError(t, err) + assert.Contains(t, string(written), "host: ") +} + +func TestBuiltinCommandsSkipValidation(t *testing.T) { + type cfg struct { + Host string `toml:"host" validate:"required"` + } + + // Reading the help that explains a required setting must not require that setting. docs + // is included because it is the same kind of command - it describes the config rather + // than consuming it - even though it opts out by its own route. + for _, args := range [][]string{ + {"help"}, + {"help", "docs"}, + {"docs"}, + {"completion", "bash"}, + {"__complete", ""}, + } { + t.Run(strings.Join(args, " "), func(t *testing.T) { + t.Chdir(t.TempDir()) // docs writes a file + + root := newRoot(t) + require.NoError(t, RegisterCommandFlags(root, &cfg{}, DefaultTOMLOptions("TEST"))) + + root.SetArgs(args) + require.NoError(t, root.Execute()) + }) + } +} + +func TestIsBuiltinCommandCoversCobrasOwnCommands(t *testing.T) { + // Callers chaining their own PersistentPreRunE need this to guard checks the library + // can't see; a chained check that misses one of these rejects `help` on a machine with no + // configuration, which is exactly what the skip exists to prevent. + root := newRoot(t) + require.NoError(t, RegisterCommandFlags(root, &struct{}{}, DefaultTOMLOptions("TEST"))) + root.InitDefaultHelpCmd() + root.InitDefaultCompletionCmd() + + byName := map[string]*cobra.Command{} + for _, c := range root.Commands() { + byName[c.Name()] = c + } + + for _, name := range []string{"help", "completion", "docs"} { + sub, ok := byName[name] + require.True(t, ok, "expected a %q command", name) + if name == "docs" { + // docs is ours: it opts out via its own PersistentPreRunE, not this check. + assert.False(t, IsBuiltinCommand(sub)) + continue + } + assert.True(t, IsBuiltinCommand(sub), name) + } + + assert.False(t, IsBuiltinCommand(root), "the root command itself runs the program") +} + +func TestNonBuiltinCommandStillValidates(t *testing.T) { + type cfg struct { + Host string `toml:"host" validate:"required"` + } + + root := newRoot(t) + require.NoError(t, RegisterCommandFlags(root, &cfg{}, DefaultTOMLOptions("TEST"))) + + root.SetArgs(nil) + require.ErrorContains(t, root.Execute(), "'required'") +} + +func TestDocsCommandIsAddedOnce(t *testing.T) { + type aCfg struct { + A string `toml:"a"` + } + type bCfg struct { + B string `toml:"b"` + } + + root := newRoot(t) + require.NoError(t, RegisterCommandFlags(root, &aCfg{}, DefaultTOMLOptions("TEST"))) + require.NoError(t, RegisterCommandFlags(root, &bCfg{}, DefaultTOMLOptions("TEST"))) + + var docsCmds int + for _, c := range root.Commands() { + if c.Name() == "docs" { + docsCmds++ + } + } + assert.Equal(t, 1, docsCmds) +} + +func TestDocsCommandSkipsValidation(t *testing.T) { + type cfg struct { + Host string `toml:"host" validate:"required"` + } + + root := newRoot(t) + require.NoError(t, RegisterCommandFlags(root, &cfg{}, DefaultTOMLOptions("TEST"))) + + // Writing docs must not require a valid config. + dir := t.TempDir() + t.Chdir(dir) + + root.SetArgs([]string{"docs"}) + require.NoError(t, root.Execute()) + assert.FileExists(t, filepath.Join(dir, docsOutputPath)) +} + +func TestUnsetFieldKeepsCallerDefault(t *testing.T) { + type cfg struct { + Retries int `toml:"retries"` + Host string `toml:"host"` + } + + c := cfg{Retries: 3, Host: "default-host"} + require.NoError(t, run(t, &c, "--host", "override")) + assert.Equal(t, 3, c.Retries, "untouched field should keep its compiled-in default") + assert.Equal(t, "override", c.Host) +} diff --git a/pkg/config/flags/options.go b/pkg/config/flags/options.go new file mode 100644 index 0000000000..61576f2bcc --- /dev/null +++ b/pkg/config/flags/options.go @@ -0,0 +1,129 @@ +package flags + +import ( + "fmt" + "reflect" + "strings" + + "github.com/go-viper/mapstructure/v2" + + "github.com/smartcontractkit/chainlink-common/pkg/config/configdoc" +) + +// Options configures how a target struct is bound, decoded, and documented. Use +// DefaultTOMLOptions for the standard `toml`-tagged setup; the zero value works too, falling +// back to mapstructure's own defaults. +type Options struct { + // Namespace roots every key of the registered struct under it, so a dependency's + // settings sit together (e.g. Namespace "database" gives the key database.url, the flag + // --database.url and the env var PREFIX_DATABASE_URL). Empty leaves the struct at the top + // level. Independent structs sharing one command should each take a namespace, both to + // group their settings and to keep same-named fields from colliding. + Namespace string + + // Prefixes are the env var prefixes a key is bound under, tried in order - e.g. "CRE" + // and "CL" bind chain.id to CRE_CHAIN_ID then CL_CHAIN_ID. Subcommands inherit the root + // command's prefixes when they specify none. + Prefixes []string + + // DecoderConfig controls how resolved values are decoded into the target. TagName, + // SquashTagOption and Squash also determine the config key (and therefore the flag name) + // of each field, so the flags, env vars, docs, and decoding all follow from this one + // setting and cannot disagree. Result is filled in per target and must be left unset. + DecoderConfig mapstructure.DecoderConfig + + // Format is the configuration file syntax the docs are written in: it renders the + // comments, tables, fields and value literals that the generated document is assembled + // from, and then renders the document itself. Defaults to configdoc.TOML. + Format configdoc.Format +} + +// DefaultTOMLOptions meant to be used with github.com/pelletier/go-toml/v2, used by default by viper for toml +// returns Options for structs tagged `toml:"key"`, with `,inline` marking a +// squashed (flattened) struct and embedded structs squashed automatically, decoded leniently +// enough for the string-typed values that env vars and pflag hand back, and documented with +// configdoc.Generate. +func DefaultTOMLOptions(prefixes ...string) Options { + return Options{ + Prefixes: prefixes, + DecoderConfig: mapstructure.DecoderConfig{ + TagName: "toml", + SquashTagOption: "inline", + // Embedded structs are flattened into the parent rather than becoming a table + // named after their type. + Squash: true, + // Env vars are always strings, and pflag hands back several types (uint64, + // duration, ...) as strings too, so the decoder has to coerce rather than + // demand exact types. Mirrors viper's own defaultDecoderConfig. + WeaklyTypedInput: true, + DecodeHook: mapstructure.ComposeDecodeHookFunc( + mapstructure.StringToTimeDurationHookFunc(), + // An env var arrives as a single string even when the field is a slice, so + // split it the way pflag splits a comma-separated StringSlice flag. + mapstructure.StringToSliceHookFunc(","), + mapstructure.TextUnmarshallerHookFunc(), + ), + }, + Format: configdoc.TOML{}, + } +} + +// tagName is the struct tag holding config keys, defaulting to mapstructure's own. +func (o Options) tagName() string { + if o.DecoderConfig.TagName == "" { + return "mapstructure" + } + return o.DecoderConfig.TagName +} + +// squashOption is the tag option marking a squashed struct, defaulting to mapstructure's own. +func (o Options) squashOption() string { + if o.DecoderConfig.SquashTagOption == "" { + return "squash" + } + return o.DecoderConfig.SquashTagOption +} + +// checkEmbeddedIsUnnamed rejects a name on an embedded struct that the decoder will squash. +// +// With squashing on, `toml:"foo"` on an embedded struct is dead text: mapstructure flattens the +// fields into the parent and the name is never used, so the config key, the flag, and the docs +// all ignore it. Other encoders do not agree - encoding/json would nest the same struct under +// "foo" - so the one struct would describe two different layouts depending on who read it. +// Rejecting the name up front keeps the tag honest; drop it to squash, or make the field named +// (non-embedded) to nest. +func (o Options) checkEmbeddedIsUnnamed(m fieldMeta) error { + if !o.DecoderConfig.Squash || !m.field.Anonymous || m.field.Type.Kind() != reflect.Struct { + return nil + } + if !o.hasExplicitName(m.field) { + return nil + } + return fmt.Errorf("%s: embedded struct %s must not be named by its %q tag while DecoderConfig.Squash is set; it is squashed into the parent, so the name is silently ignored here but would nest the struct under other encoders", + m.field.Name, m.elemType.Name(), o.tagName()) +} + +// hasExplicitName reports whether field's tag names it, as opposed to carrying only options +// (`toml:",inline"`) or no tag at all. +func (o Options) hasExplicitName(field reflect.StructField) bool { + tag := field.Tag.Get(o.tagName()) + if tag == "" || tag == "-" { + return false + } + return strings.Split(tag, ",")[0] != "" +} + +// format is Format, or configdoc.TOML if unset. +func (o Options) format() configdoc.Format { + if o.Format == nil { + return configdoc.TOML{} + } + return o.Format +} + +// decoderConfigFor returns a copy of o.DecoderConfig aimed at target. +func (o Options) decoderConfigFor(target any) *mapstructure.DecoderConfig { + dc := o.DecoderConfig + dc.Result = target + return &dc +} diff --git a/pkg/config/flags/tmp/docs/CONFIG.md b/pkg/config/flags/tmp/docs/CONFIG.md new file mode 100644 index 0000000000..a86a53475f --- /dev/null +++ b/pkg/config/flags/tmp/docs/CONFIG.md @@ -0,0 +1,122 @@ +# myapp Configuration + +## Example + +```toml +# ----- Global Configuration ----- + +[chain] +id = 1 +rpc = 'https://mainnet.infura.io/v3/API_KEY' +contract = '0xYourContractAddress' + +[system] +env = 'dev' +log_level = '' +metrics = false + +# ----- Command: myapp bar ----- +[bar] +retries = 3 +backoff = '2s' + +# ----- Command: myapp foo ----- +[foo] +timeout = '5s' + + +``` + +# Global Configuration + +## chain +```toml +[chain] +id = 1 # Default +rpc = 'https://mainnet.infura.io/v3/API_KEY' # Example +contract = '0xYourContractAddress' # Example +``` + + +### id +```toml +id = 1 # Default +``` +id Target chain ID selector + +### rpc +```toml +rpc = 'https://mainnet.infura.io/v3/API_KEY' # Example +``` +rpc RPC endpoint URL + +### contract +```toml +contract = '0xYourContractAddress' # Example +``` +contract Core contract address + +## system +```toml +[system] +env = 'dev' # Default +log_level = '' # Default +metrics = false # Default +``` + + +### env +```toml +env = 'dev' # Default +``` +env Environment profile selector (dev, prod) + +### log_level +```toml +log_level = '' # Default +``` +log_level Logging severity + +### metrics +```toml +metrics = false # Default +``` +metrics Enable metrics collection + +# Command: myapp bar + +## bar +```toml +[bar] +retries = 3 # Default +backoff = '2s' # Default +``` +Bar-specific settings + +### retries +```toml +retries = 3 # Default +``` +retries Bar max retries + +### backoff +```toml +backoff = '2s' # Default +``` +backoff Bar retry backoff + +# Command: myapp foo + +## foo +```toml +[foo] +timeout = '5s' # Default +``` +Foo-specific settings + +### timeout +```toml +timeout = '5s' # Default +``` +timeout Foo operation timeout + diff --git a/pkg/config/flags/tmp/main.go b/pkg/config/flags/tmp/main.go new file mode 100644 index 0000000000..d04333adf7 --- /dev/null +++ b/pkg/config/flags/tmp/main.go @@ -0,0 +1,158 @@ +package main + +import ( + "encoding/json" + "fmt" + "os" + "time" + + "github.com/smartcontractkit/chainlink-common/pkg/config" + "github.com/smartcontractkit/chainlink-common/pkg/config/flags" + "github.com/spf13/cobra" +) + +// --- CONFIG STRUCTS --- + +type ChainConfig struct { + ID uint32 `toml:"id" json:"id" usage:"Target chain ID selector"` + RPC string `toml:"rpc" json:"rpc" usage:"RPC endpoint URL" validate:"required" example:"'https://mainnet.infura.io/v3/API_KEY'"` + Contract string `toml:"contract" json:"contract" usage:"Core contract address" validate:"required" example:"'0xYourContractAddress'"` +} + +type SystemConfig struct { + Env string `toml:"env" json:"env" usage:"Environment profile selector (dev, prod)"` + LogLevel string `toml:"log_level" json:"log_level" usage:"Logging severity"` + Metrics bool `toml:"metrics" json:"metrics" usage:"Enable metrics collection"` +} + +type CommonConfig struct { + Chain ChainConfig `toml:"chain" json:"chain"` + System SystemConfig `toml:"system" json:"system"` +} + +type FooSettings struct { + Timeout *config.Duration `toml:"timeout" json:"timeout" usage:"Foo operation timeout"` +} + +type BarSettings struct { + Retries int `toml:"retries" json:"retries" usage:"Bar max retries"` + // non-pointer config.Duration, to exercise the value form (UnmarshalText has a + // pointer receiver, so this decodes differently from FooSettings.Timeout) + Backoff config.Duration `toml:"backoff" json:"backoff" usage:"Bar retry backoff"` +} + +// --- PROFILE DICTIONARIES --- + +var ChainProfiles = map[uint32]CommonConfig{ + 1: { + Chain: ChainConfig{ + RPC: "https://eth.blastapi.io", + Contract: "0x1111111111111111111111111111111111111111", + }, + }, + 137: { + Chain: ChainConfig{ + RPC: "https://polygon.blastapi.io", + Contract: "0x2222222222222222222222222222222222222222", + }, + }, +} + +var SystemProfiles = map[string]CommonConfig{ + "dev": { + System: SystemConfig{ + LogLevel: "debug", + Metrics: false, + }, + }, + "prod": { + System: SystemConfig{ + LogLevel: "info", + Metrics: true, + }, + }, +} + +// --- STATE DECLARATIONS --- + +var ( + configFile string + + AppConfig = CommonConfig{ + Chain: ChainConfig{ID: 1}, + System: SystemConfig{Env: "dev"}, + } + + fooSettings = FooSettings{Timeout: config.MustNewDuration(5 * time.Second)} + barSettings = BarSettings{Retries: 3, Backoff: *config.MustNewDuration(2 * time.Second)} +) + +// --- COMMAND DECLARATIONS --- + +var rootCmd = &cobra.Command{ + Use: "myapp", +} + +var fooCmd = &cobra.Command{ + Use: "foo", + Short: "Foo-specific settings", + RunE: func(cmd *cobra.Command, args []string) error { + fmt.Println("FOO") + return printConfig(AppConfig, fooSettings) + }, +} + +var barCmd = &cobra.Command{ + Use: "bar", + Short: "Bar-specific settings", + RunE: func(cmd *cobra.Command, args []string) error { + fmt.Println("BAR") + return printConfig(AppConfig, barSettings) + }, +} + +func printConfig(common CommonConfig, settings any) error { + out, err := json.MarshalIndent(struct { + Common CommonConfig `json:"common"` + Cmd any `json:"cmd"` + }{common, settings}, "", " ") + if err != nil { + return err + } + fmt.Println(string(out)) + return nil +} + +func init() { + rootCmd.PersistentFlags().StringVar(&configFile, "config", "", "Path to config file") + + // 1. Register base persistent flags on root with env prefixes ("CRE", "CL") + if err := flags.RegisterCommandFlags(rootCmd, &AppConfig, flags.DefaultTOMLOptions("CRE", "CL")); err != nil { + panic(err) + } + + // 2. Attach profile maps to root command + if err := flags.RegisterProfile(rootCmd, "Chain.ID", ChainProfiles, flags.DefaultTOMLOptions("CRE", "CL")); err != nil { + panic(err) + } + if err := flags.RegisterProfile(rootCmd, "System.Env", SystemProfiles, flags.DefaultTOMLOptions("CRE", "CL")); err != nil { + panic(err) + } + + // 3. Register subcommand flags (inherits "CRE" / "CL" prefixes automatically) + if err := flags.RegisterSubcommandFlags(fooCmd, "foo", &fooSettings, flags.DefaultTOMLOptions()); err != nil { + panic(err) + } + if err := flags.RegisterSubcommandFlags(barCmd, "bar", &barSettings, flags.DefaultTOMLOptions()); err != nil { + panic(err) + } + + rootCmd.AddCommand(fooCmd) + rootCmd.AddCommand(barCmd) +} + +func main() { + if err := rootCmd.Execute(); err != nil { + os.Exit(1) + } +} diff --git a/pkg/config/flags/tmp/tmp b/pkg/config/flags/tmp/tmp new file mode 100755 index 0000000000000000000000000000000000000000..5976e9c090db2e0b503ec0448427fbddc1af0e84 GIT binary patch literal 12250866 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

H(*Yrcb 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