Skip to content

Add x/config/cli - #2423

Open
nolag wants to merge 6 commits into
mainfrom
rtinianov_x_config_cli
Open

nolag wants to merge 6 commits into
mainfrom
rtinianov_x_config_cli

Conversation

@nolag

@nolag nolag commented Sep 28, 2026

Copy link
Copy Markdown
Contributor

No description provided.

@github-actions

github-actions Bot commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

📊 API Diff Results

No changes detected for module github.com/smartcontractkit/chainlink-common

View full report

@nolag

nolag commented Sep 28, 2026

Copy link
Copy Markdown
Contributor Author

Copilot review says it's done, but didn't post here. It left two comments. The first was because Copilot didn't know about generic methods in Go (get with the times, 1.27 released on Aug 19th changed that).

Second was

{
    "start_line": 214,
    "end_line": 229,
    "fixed": false,
    "fix_skip_reason": "requires_broader_context",
    "fix_skip_note": "The supplied-field bookkeeping needs to cover both pointer fields and their dereferenced values."
  }

Claude says it's wrong, and we have a test to prove it. I agree with Claude.

The comment is wrong, so there's nothing to fix.

  • What it claims: validator passes the pointer field to isSupplied, but decode records the address after dereferencing, so the two never match.
  • Why that's wrong: validator dereferences a non-nil pointer before it calls a custom rule. fl.Field() is therefore the int the pointer points to, the same value decode records, and the addresses match.
  • The nil case: with a nil pointer and nothing supplied, set sees the nil pointer, finds it can't be addressed, and returns false. That's correct: unset.
  • The test agrees: the pointer subtest in TestSetIgnoresADefault passes all four cases (pointer set by default or nil, each with and without --value 5). If the comment were right, both --value 5 cases would fail.

The reviewer probably assumed Field() returns the raw struct field. It didn't run the test, which is why it skipped with "requires_broader_context".

@nolag
nolag marked this pull request as ready for review September 28, 2026 19:55
@nolag
nolag requested a review from a team as a code owner September 28, 2026 19:55
Comment thread x/config/cli/examples/namespaced/settings/settings.go Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Comment thread x/config/cli/examples/namespaced/settings/gen/main.go Outdated
Comment thread x/config/cli/binder.go Outdated
Comment thread x/config/cli/binder.go Outdated
Comment thread x/config/cli/binder.go Outdated
Comment thread x/config/cli/binder.go Outdated
…emove list, map and set validations for later PRs. Remove redundant test coverage. Redo comments and divide everything up clearer.
@nolag
nolag enabled auto-merge September 30, 2026 16:48
Comment thread x/config/cli/options.go

// RegisterOption customizes one [Binder.Register] call. None exist yet; the parameter lets options be added without
// breaking callers.
type RegisterOption[T any] struct {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: this doesn't need to be generic since it doesn't return anything anymore

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's intentional. The function that will create them wants to ensure the input was the same type as the root.

The one I have in mind right now is for profiles. It'll allow you to say things like "this is the default for EVM with chain ID 1, this is for 2 etc". The T ensures that the profile belongs to the config you provided Register.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Profile's signature

func Profile[T any, K comparable, S any](selector func(*T) *K, profiles map[K]S) RegisterOption[T]

an example use is here

Without the generics returned, you could use profile for the wrong struct. It's unlikely, but possible to do. The generics on the returned RegisterOption forces you to start at the config your registering.

It's still possible to do something wrong if you don't navigate the object itself, but that would be much more intentional. It would be caught (and so would others without the safeguard), but it makes most errors compilation instead of runtime this way.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Above is subject to some change, I'm evaluating an alternative.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What makes something "wrong" to use though, if that's not backed by the type system?

I'm still digesting the rest, but it seems like targetEntry[T] would connect the dots, and prevent those implementations from having to type cast themselves - did that not work for some reason?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wrong in my mind is if something can be a complier error but becomes a runtime error. Eg: In this case, I passed a selector function that expects a different type than the main config to be the entry.

I don't get how targetEntry would be generic. Binder holds it, but binder can bind to different types for different commands.

I don't get what you mean by implementations casting it, do you mean specifying T?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Draft for profiles, note I haven't fully reviewed the AI code or comments yet, but the examples seem good enough to demonstrate usage. I may iterate a bit more on the exact API.

This PR is based on a few others that add support for lists and maps as CLI and env vars, the set flag being added, and namespaces. None of them should show up in that PR, so profiles is issolated, but if sourinding code doesn't align with this PR, that's why.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please document what T is in this context? It reads like an arbitrary additional constraint, and my intuition was apparently way off.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants