diff --git a/v3/api.go b/v3/api.go index e9ce979ea..cbdb31b28 100644 --- a/v3/api.go +++ b/v3/api.go @@ -127,8 +127,31 @@ func Ptr[T any](v T) *T { } // Validate any struct from schema or request -func Validate(r any) error { - return validator.New().Struct(r) +func (c Client) Validate(s any) error { + err := c.validate.Struct(s) + if err == nil { + return nil + } + + // Print better error messages + validationErrors := err.(validator.ValidationErrors) + + if len(validationErrors) > 0 { + e := validationErrors[0] + errorString := fmt.Sprintf( + "Request validation error: '%s' = '%v' does not validate ", + e.StructNamespace(), + e.Value(), + ) + if e.Param() == "" { + errorString += fmt.Sprintf("'%s'", e.ActualTag()) + } else { + errorString += fmt.Sprintf("'%s=%v'", e.ActualTag(), e.Param()) + } + return fmt.Errorf(errorString) + } + + return err } func prepareJSONBody(body any) (*bytes.Reader, error) { diff --git a/v3/client.go b/v3/client.go index c1134ba2c..c81261414 100755 --- a/v3/client.go +++ b/v3/client.go @@ -12,6 +12,7 @@ import ( "time" "github.com/exoscale/egoscale/version" + "github.com/go-playground/validator/v10" ) // URL represents a zoned url endpoint. @@ -57,6 +58,7 @@ type Client struct { httpClient *http.Client timeout time.Duration pollingInterval time.Duration + validate *validator.Validate trace bool // A list of callbacks for modifying requests which are generated before sending over @@ -99,6 +101,14 @@ func ClientOptWithTrace() ClientOpt { } } +// ClientOptWithValidator returns a ClientOpt with a given validator. +func ClientOptWithValidator(validate *validator.Validate) ClientOpt { + return func(c *Client) error { + c.validate = validate + return nil + } +} + // ClientOptWithURL returns a ClientOpt With a given zone URL. func ClientOptWithURL(url URL) ClientOpt { return func(c *Client) error { @@ -141,6 +151,7 @@ func NewClient(apiKey, apiSecret string, opts ...ClientOpt) (*Client, error) { serverURL: string(CHGva2), httpClient: http.DefaultClient, pollingInterval: pollingInterval, + validate: validator.New(), } for _, opt := range opts { @@ -161,6 +172,7 @@ func (c *Client) WithURL(url URL) *Client { httpClient: c.httpClient, requestInterceptors: c.requestInterceptors, pollingInterval: c.pollingInterval, + validate: c.validate, } } @@ -174,6 +186,7 @@ func (c *Client) WithTrace() *Client { requestInterceptors: c.requestInterceptors, pollingInterval: c.pollingInterval, trace: true, + validate: c.validate, } } @@ -186,6 +199,7 @@ func (c *Client) WithHttpClient(client *http.Client) *Client { httpClient: client, requestInterceptors: c.requestInterceptors, pollingInterval: c.pollingInterval, + validate: c.validate, } } @@ -198,6 +212,7 @@ func (c *Client) WithRequestInterceptor(f ...RequestInterceptorFn) *Client { httpClient: c.httpClient, requestInterceptors: append(c.requestInterceptors, f...), pollingInterval: c.pollingInterval, + validate: c.validate, } } diff --git a/v3/generator/client/client.go b/v3/generator/client/client.go index 9d1b2838a..24b497bd4 100644 --- a/v3/generator/client/client.go +++ b/v3/generator/client/client.go @@ -35,6 +35,7 @@ func Generate(doc libopenapi.Document, path, packageName string) error { "errors" "github.com/exoscale/egoscale/version" + "github.com/go-playground/validator/v10" ) `, packageName)) diff --git a/v3/generator/client/client.tmpl b/v3/generator/client/client.tmpl index 7dda67974..521f2ca36 100644 --- a/v3/generator/client/client.tmpl +++ b/v3/generator/client/client.tmpl @@ -29,6 +29,7 @@ type Client struct { httpClient *http.Client timeout time.Duration pollingInterval time.Duration + validate *validator.Validate trace bool // A list of callbacks for modifying requests which are generated before sending over @@ -71,6 +72,14 @@ func ClientOptWithTrace() ClientOpt { } } +// ClientOptWithValidator returns a ClientOpt with a given validator. +func ClientOptWithValidator(validate *validator.Validate) ClientOpt { + return func(c *Client) error { + c.validate = validate + return nil + } +} + // ClientOptWithURL returns a ClientOpt With a given zone URL. func ClientOptWithURL(url URL) ClientOpt { return func(c *Client) error { @@ -108,11 +117,12 @@ func NewClient(apiKey, apiSecret string, opts ...ClientOpt) (*Client, error) { } client := &Client{ - apiKey: apiKey, - apiSecret: apiSecret, - serverURL: string({{ .ServerURL }}), - httpClient: http.DefaultClient, - pollingInterval: pollingInterval, + apiKey: apiKey, + apiSecret: apiSecret, + serverURL: string({{ .ServerURL }}), + httpClient: http.DefaultClient, + pollingInterval: pollingInterval, + validate: validator.New(), } for _, opt := range opts { @@ -127,49 +137,53 @@ func NewClient(apiKey, apiSecret string, opts ...ClientOpt) (*Client, error) { // WithURL returns a copy of Client with new zone URL. func (c *Client) WithURL(url URL) *Client { return &Client{ - apiKey: c.apiKey, - apiSecret: c.apiSecret, - serverURL: string(url), - httpClient: c.httpClient, + apiKey: c.apiKey, + apiSecret: c.apiSecret, + serverURL: string(url), + httpClient: c.httpClient, requestInterceptors: c.requestInterceptors, - pollingInterval: c.pollingInterval, + pollingInterval: c.pollingInterval, + validate: c.validate, } } // WithTrace returns a copy of Client with tracing enabled. func (c *Client) WithTrace() *Client { return &Client{ - apiKey: c.apiKey, - apiSecret: c.apiSecret, - serverURL: c.serverURL, - httpClient: c.httpClient, + apiKey: c.apiKey, + apiSecret: c.apiSecret, + serverURL: c.serverURL, + httpClient: c.httpClient, requestInterceptors: c.requestInterceptors, - pollingInterval: c.pollingInterval, - trace: true, + pollingInterval: c.pollingInterval, + trace: true, + validate: c.validate, } } // WithHttpClient returns a copy of Client with new http.Client. func (c *Client) WithHttpClient(client *http.Client) *Client { return &Client{ - apiKey: c.apiKey, - apiSecret: c.apiSecret, - serverURL: c.serverURL, - httpClient: client, + apiKey: c.apiKey, + apiSecret: c.apiSecret, + serverURL: c.serverURL, + httpClient: client, requestInterceptors: c.requestInterceptors, - pollingInterval: c.pollingInterval, + pollingInterval: c.pollingInterval, + validate: c.validate, } } // WithRequestInterceptor returns a copy of Client with new RequestInterceptors. func (c *Client) WithRequestInterceptor(f ...RequestInterceptorFn) *Client { return &Client{ - apiKey: c.apiKey, - apiSecret: c.apiSecret, - serverURL: c.serverURL, - httpClient: c.httpClient, + apiKey: c.apiKey, + apiSecret: c.apiSecret, + serverURL: c.serverURL, + httpClient: c.httpClient, requestInterceptors: append(c.requestInterceptors, f...), - pollingInterval: c.pollingInterval, + pollingInterval: c.pollingInterval, + validate: c.validate, } }