-
Notifications
You must be signed in to change notification settings - Fork 110
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[DRAFT] feat: CLI integration for notification services #299
base: feat/slashing
Are you sure you want to change the base?
Conversation
would be great to see sample out of the commands. |
pkg/notifications/flags.go
Outdated
var ( | ||
AvsNameFlag = cli.StringFlag{ | ||
Name: "avs-name", | ||
Aliases: []string{"k"}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alias names seem off on a few of these flags.
"github.com/urfave/cli/v2" | ||
) | ||
|
||
func NotificationsCmd(p utils.Prompter) *cli.Command { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we use the BaseCommand pattern for new commands?
@@ -39,6 +39,7 @@ func main() { | |||
return nil | |||
} | |||
|
|||
app.Commands = append(app.Commands, pkg.NotificationsCmd(prompter)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: idiomatic go is New..
for factory methods.
pkg/notifications/list_events.go
Outdated
Message string `json:"message"` | ||
} | ||
|
||
const baseURL = "http://localhost:3000/api" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would suggest passing this as a variable for better testing and parameterization.
pkg/notifications/list_events.go
Outdated
type ErrorResponse struct { | ||
StatusCode int `json:"statusCode"` | ||
Message string `json:"message"` | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we move these to a shared location? It's going to become a common theme.
pkg/notifications/subscribe.go
Outdated
var DeliveryMethodFlag = cli.StringFlag{ | ||
Name: "delivery-method", | ||
Usage: "Method of delivery for notifications (email, webhook, or telegram)", | ||
Required: true, | ||
} | ||
|
||
var DeliveryDetailsFlag = cli.StringFlag{ | ||
Name: "delivery-details", | ||
Usage: "Details for the delivery method (email address, webhook URL, or telegram chat ID)", | ||
Required: true, | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any reason to not have this in the flag file?
pkg/notifications/subscribe.go
Outdated
} | ||
|
||
func validateDeliveryMethod(method string) error { | ||
validMethods := map[string]bool{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: every time you call this method you create a new map that never changes. You can turn this into a var. Once you do this you don't really need the method unless you are trying to standardize the error message. Any caller who uses the method anyways similarly has to have an if err { ... } clause.
pkg/notifications/subscribe.go
Outdated
} | ||
|
||
// Accept both 200 and 201 as success codes | ||
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the difference from your service perspective in these two cases?
return nil | ||
} | ||
|
||
func SubscribeEventsCmd() *cli.Command { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similar to the other long method, this should be broken down. There are very different, separated tasks here between input validation & parsing, http request dispatch & processing and response handling.
I think it's important for us trend away from monolithic commands and not towards them.
|
||
const baseURL = "http://localhost:3000/api" | ||
|
||
func ListEventsCmd() *cli.Command { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This command is a single function, 60 lines long. I think it's worth discussing why there seems to be a preference for large, monolithic commands, rather than following the principle of code segments doing one thing, and one thing clearly.
func NotificationsCmd(p utils.Prompter) *cli.Command { | ||
var notificationsCmd = &cli.Command{ | ||
Name: "notifications", | ||
Usage: "Subscribe and unsubscribe to EigenLayer events via the notification service", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Notification Service?
Usage: "List all the AVSs available to be subscribed to via notification service", | ||
UsageText: "list-avs", | ||
Description: ` | ||
This command provides a listing of all AVS services with events available to be subscribed via the notification service. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This command provides -> Provides
'This command' is implied by this being CLI help
Thanks for the feedback on the draft, will work through it. I have pushed some refactoring already that should resolve some of the code issues. |
if !isSuccessStatusCode(resp.StatusCode) { | ||
return handleErrorResponse(resp.StatusCode, body) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This can be moved into the HTTP code path, no? Same for the other place it's called.
return APIBaseURL | ||
} | ||
|
||
// doHTTPRequest performs the HTTP request and processes the response |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this really utils
or is it an http client? Seems like something we want to generalize and use in other places, like ListOperatorApplications for RMS.
|
||
// getHTTPClient returns the shared HTTP client instance | ||
func getHTTPClient() *http.Client { | ||
httpClientOnce.Do(func() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you turn this into a normal object you don't need to enforce only-once instantiation.
) | ||
|
||
// defaultTimeout is the default timeout for HTTP requests | ||
const defaultTimeout = 10 * time.Second |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's good to have a default, but this should also be parameterized for overriding and testing.
return makeRequest(ctx, config) | ||
} | ||
|
||
// makeDeleteRequest makes a DELETE request to the specified URL with proper headers |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: is it meaningful to have comments like this?
} | ||
|
||
// AvailableAvsResponseDto represents the API response for available AVS services. | ||
type AvailableAvsResponseDto struct { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Service question: how does available AVS list get populated server side?
SubscriptionIDFlag = cli.StringFlag{ | ||
Name: "subscription-id", | ||
Aliases: []string{"id"}, | ||
Required: false, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Based on your validation, this is required.
AvsNameFlag = cli.StringFlag{ | ||
Name: "avs-name", | ||
Aliases: []string{"avs"}, | ||
Required: false, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Based on your validation, this is required.
func validateSubscribeParams(cliCtx *cli.Context) (*SubscriptionParams, error) { | ||
params := &SubscriptionParams{} | ||
|
||
params.AvsName = cliCtx.String("avs-name") | ||
if params.AvsName == "" { | ||
return nil, fmt.Errorf("avs-name is required") | ||
} | ||
|
||
params.EventName = cliCtx.String("event-name") | ||
if params.EventName == "" { | ||
return nil, fmt.Errorf("event-name is required") | ||
} | ||
|
||
params.OperatorID = cliCtx.String("operator-id") | ||
if params.OperatorID == "" { | ||
return nil, fmt.Errorf("operator-id is required") | ||
} | ||
|
||
params.DeliveryMethod = cliCtx.String("delivery-method") | ||
if err := validateDeliveryMethod(params.DeliveryMethod); err != nil { | ||
return nil, err | ||
} | ||
|
||
params.DeliveryDetails = cliCtx.String("delivery-details") | ||
if params.DeliveryDetails == "" { | ||
return nil, fmt.Errorf("delivery-details is required") | ||
} | ||
|
||
return params, nil | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not clear to me why we need this if you are marking these flags as required with urfacecli directly. It will automatically validate them, right?
Samples with stub data generated by AI: