From b414c838f92ad28efac57a0543a1f2844c1a8e9b Mon Sep 17 00:00:00 2001 From: Pierre-Emmanuel Jacquier <15922119+pierre-emmanuelJ@users.noreply.github.com> Date: Wed, 24 Jan 2024 11:00:14 +0100 Subject: [PATCH 1/2] V3: Add better message on validator (#615) # Description More explicit error message on the default validator ## Checklist (For exoscale contributors) V3 alpha development Signed-off-by: Pierre-Emmanuel Jacquier <15922119+pierre-emmanuelJ@users.noreply.github.com> --- v3/api.go | 27 +++++++++++++- v3/client.go | 15 ++++++++ v3/generator/client/client.go | 1 + v3/generator/client/client.tmpl | 66 ++++++++++++++++++++------------- 4 files changed, 81 insertions(+), 28 deletions(-) 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, } } From 2f4651b35464a5908869e3a6aaf08a014f9fc218 Mon Sep 17 00:00:00 2001 From: Pierre-Emmanuel Jacquier <15922119+pierre-emmanuelJ@users.noreply.github.com> Date: Wed, 31 Jan 2024 16:49:32 +0000 Subject: [PATCH 2/2] V3 Generate Signed-off-by: Pierre-Emmanuel Jacquier <15922119+pierre-emmanuelJ@users.noreply.github.com> --- v3/operations.go | 20 ++++---------------- v3/schemas.go | 10 ++++++++++ 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/v3/operations.go b/v3/operations.go index d0e87fa52..ab31d0bf1 100755 --- a/v3/operations.go +++ b/v3/operations.go @@ -4124,10 +4124,9 @@ type CreateDBAASServicePGRequest struct { // Service is protected against termination and powering off TerminationProtection *bool `json:"termination-protection,omitempty"` // TimescaleDB extension configuration values - TimescaledbSettings JSONSchemaTimescaledb `json:"timescaledb-settings,omitempty"` - Variant *EnumPGVariant `json:"variant,omitempty"` - // PostgreSQL major version - Version string `json:"version,omitempty" validate:"omitempty,gte=1"` + TimescaledbSettings JSONSchemaTimescaledb `json:"timescaledb-settings,omitempty"` + Variant *EnumPGVariant `json:"variant,omitempty"` + Version *DBAASPGTargetVersions `json:"version,omitempty"` // Sets the maximum amount of memory to be used by a query operation (such as a sort or hash table) before writing to temporary disk files, in MB. Default is 1MB + 0.075% of total RAM (up to 32MB). WorkMem int64 `json:"work-mem,omitempty" validate:"omitempty,gte=1,lte=1024"` } @@ -4847,19 +4846,8 @@ func (c Client) ResetDBAASPostgresUserPassword(ctx context.Context, serviceName return bodyresp, nil } -type CreateDBAASPGUpgradeCheckRequestTargetVersion string - -const ( - CreateDBAASPGUpgradeCheckRequestTargetVersion14 CreateDBAASPGUpgradeCheckRequestTargetVersion = "14" - CreateDBAASPGUpgradeCheckRequestTargetVersion15 CreateDBAASPGUpgradeCheckRequestTargetVersion = "15" - CreateDBAASPGUpgradeCheckRequestTargetVersion12 CreateDBAASPGUpgradeCheckRequestTargetVersion = "12" - CreateDBAASPGUpgradeCheckRequestTargetVersion13 CreateDBAASPGUpgradeCheckRequestTargetVersion = "13" - CreateDBAASPGUpgradeCheckRequestTargetVersion11 CreateDBAASPGUpgradeCheckRequestTargetVersion = "11" -) - type CreateDBAASPGUpgradeCheckRequest struct { - // Target version for upgrade - TargetVersion CreateDBAASPGUpgradeCheckRequestTargetVersion `json:"target-version" validate:"required"` + TargetVersion *DBAASPGTargetVersions `json:"target-version" validate:"required"` } // Check whether you can upgrade Postgres service to a newer version diff --git a/v3/schemas.go b/v3/schemas.go index 4acd67929..bcc5aa945 100755 --- a/v3/schemas.go +++ b/v3/schemas.go @@ -404,6 +404,16 @@ type DBAASPGPoolSize int64 type DBAASPGPoolUsername string +type DBAASPGTargetVersions string + +const ( + DBAASPGTargetVersions14 DBAASPGTargetVersions = "14" + DBAASPGTargetVersions15 DBAASPGTargetVersions = "15" + DBAASPGTargetVersions12 DBAASPGTargetVersions = "12" + DBAASPGTargetVersions13 DBAASPGTargetVersions = "13" + DBAASPGTargetVersions16 DBAASPGTargetVersions = "16" +) + // DBaaS plan type DBAASPlan struct { // Requires authorization or publicly available