diff --git a/audiences.go b/audiences.go index a5492cb..df65066 100644 --- a/audiences.go +++ b/audiences.go @@ -10,24 +10,24 @@ import ( type AudiencesListParams struct { // A unique identifier that allows for fetching the next set of audiences - Cursor *string `json:"-"` + Cursor *string `json:"-" url:"cursor,omitempty"` } type AudienceMembersListParams struct { // A unique identifier that allows for fetching the next set of members - Cursor *string `json:"-"` + Cursor *string `json:"-" url:"cursor,omitempty"` } type Audience struct { // A unique identifier representing the audience_id - Id string `json:"id"` + Id string `json:"id" url:"id"` // The name of the audience - Name string `json:"name"` + Name string `json:"name" url:"name"` // A description of the audience - Description string `json:"description"` - Filter *Filter `json:"filter,omitempty"` - CreatedAt string `json:"created_at"` - UpdatedAt string `json:"updated_at"` + Description string `json:"description" url:"description"` + Filter *Filter `json:"filter,omitempty" url:"filter,omitempty"` + CreatedAt string `json:"created_at" url:"created_at"` + UpdatedAt string `json:"updated_at" url:"updated_at"` _rawJSON json.RawMessage } @@ -56,8 +56,8 @@ func (a *Audience) String() string { } type AudienceListResponse struct { - Items []*Audience `json:"items,omitempty"` - Paging *Paging `json:"paging,omitempty"` + Items []*Audience `json:"items,omitempty" url:"items,omitempty"` + Paging *Paging `json:"paging,omitempty" url:"paging,omitempty"` _rawJSON json.RawMessage } @@ -86,8 +86,8 @@ func (a *AudienceListResponse) String() string { } type AudienceMemberListResponse struct { - Items []*AudienceMember `json:"items,omitempty"` - Paging *Paging `json:"paging,omitempty"` + Items []*AudienceMember `json:"items,omitempty" url:"items,omitempty"` + Paging *Paging `json:"paging,omitempty" url:"paging,omitempty"` _rawJSON json.RawMessage } @@ -116,7 +116,7 @@ func (a *AudienceMemberListResponse) String() string { } type AudienceUpdateResponse struct { - Audience *Audience `json:"audience,omitempty"` + Audience *Audience `json:"audience,omitempty" url:"audience,omitempty"` _rawJSON json.RawMessage } @@ -203,8 +203,8 @@ func (f *Filter) Accept(visitor FilterVisitor) error { type AudienceUpdateParams struct { // The name of the audience - Name *string `json:"name,omitempty"` + Name *string `json:"name,omitempty" url:"name,omitempty"` // A description of the audience - Description *string `json:"description,omitempty"` - Filter *Filter `json:"filter,omitempty"` + Description *string `json:"description,omitempty" url:"description,omitempty"` + Filter *Filter `json:"filter,omitempty" url:"filter,omitempty"` } diff --git a/audiences/client.go b/audiences/client.go index f970657..04b18dc 100644 --- a/audiences/client.go +++ b/audiences/client.go @@ -10,9 +10,9 @@ import ( fmt "fmt" v3 "github.com/trycourier/courier-go/v3" core "github.com/trycourier/courier-go/v3/core" + option "github.com/trycourier/courier-go/v3/option" io "io" http "net/http" - url "net/url" ) type Client struct { @@ -21,36 +21,50 @@ type Client struct { header http.Header } -func NewClient(opts ...core.ClientOption) *Client { - options := core.NewClientOptions() - for _, opt := range opts { - opt(options) - } +func NewClient(opts ...option.RequestOption) *Client { + options := core.NewRequestOptions(opts...) return &Client{ baseURL: options.BaseURL, - caller: core.NewCaller(options.HTTPClient), - header: options.ToHeader(), + caller: core.NewCaller( + &core.CallerParams{ + Client: options.HTTPClient, + MaxAttempts: options.MaxAttempts, + }, + ), + header: options.ToHeader(), } } // Returns the specified audience by id. -// -// A unique identifier representing the audience_id -func (c *Client) Get(ctx context.Context, audienceId string) (*v3.Audience, error) { +func (c *Client) Get( + ctx context.Context, + // A unique identifier representing the audience_id + audienceId string, + opts ...option.RequestOption, +) (*v3.Audience, error) { + options := core.NewRequestOptions(opts...) + baseURL := "https://api.courier.com" if c.baseURL != "" { baseURL = c.baseURL } + if options.BaseURL != "" { + baseURL = options.BaseURL + } endpointURL := fmt.Sprintf(baseURL+"/"+"audiences/%v", audienceId) + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + var response *v3.Audience if err := c.caller.Call( ctx, &core.CallParams{ - URL: endpointURL, - Method: http.MethodGet, - Headers: c.header, - Response: &response, + URL: endpointURL, + Method: http.MethodGet, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, + Response: &response, }, ); err != nil { return nil, err @@ -59,24 +73,37 @@ func (c *Client) Get(ctx context.Context, audienceId string) (*v3.Audience, erro } // Creates or updates audience. -// -// A unique identifier representing the audience id -func (c *Client) Update(ctx context.Context, audienceId string, request *v3.AudienceUpdateParams) (*v3.AudienceUpdateResponse, error) { +func (c *Client) Update( + ctx context.Context, + // A unique identifier representing the audience id + audienceId string, + request *v3.AudienceUpdateParams, + opts ...option.RequestOption, +) (*v3.AudienceUpdateResponse, error) { + options := core.NewRequestOptions(opts...) + baseURL := "https://api.courier.com" if c.baseURL != "" { baseURL = c.baseURL } + if options.BaseURL != "" { + baseURL = options.BaseURL + } endpointURL := fmt.Sprintf(baseURL+"/"+"audiences/%v", audienceId) + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + var response *v3.AudienceUpdateResponse if err := c.caller.Call( ctx, &core.CallParams{ - URL: endpointURL, - Method: http.MethodPut, - Headers: c.header, - Request: request, - Response: &response, + URL: endpointURL, + Method: http.MethodPut, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, + Request: request, + Response: &response, }, ); err != nil { return nil, err @@ -85,21 +112,33 @@ func (c *Client) Update(ctx context.Context, audienceId string, request *v3.Audi } // Deletes the specified audience. -// -// A unique identifier representing the audience id -func (c *Client) Delete(ctx context.Context, audienceId string) error { +func (c *Client) Delete( + ctx context.Context, + // A unique identifier representing the audience id + audienceId string, + opts ...option.RequestOption, +) error { + options := core.NewRequestOptions(opts...) + baseURL := "https://api.courier.com" if c.baseURL != "" { baseURL = c.baseURL } + if options.BaseURL != "" { + baseURL = options.BaseURL + } endpointURL := fmt.Sprintf(baseURL+"/"+"audiences/%v", audienceId) + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + if err := c.caller.Call( ctx, &core.CallParams{ - URL: endpointURL, - Method: http.MethodDelete, - Headers: c.header, + URL: endpointURL, + Method: http.MethodDelete, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, }, ); err != nil { return err @@ -108,23 +147,34 @@ func (c *Client) Delete(ctx context.Context, audienceId string) error { } // Get list of members of an audience. -// -// A unique identifier representing the audience id -func (c *Client) ListMembers(ctx context.Context, audienceId string, request *v3.AudienceMembersListParams) (*v3.AudienceMemberListResponse, error) { +func (c *Client) ListMembers( + ctx context.Context, + // A unique identifier representing the audience id + audienceId string, + request *v3.AudienceMembersListParams, + opts ...option.RequestOption, +) (*v3.AudienceMemberListResponse, error) { + options := core.NewRequestOptions(opts...) + baseURL := "https://api.courier.com" if c.baseURL != "" { baseURL = c.baseURL } + if options.BaseURL != "" { + baseURL = options.BaseURL + } endpointURL := fmt.Sprintf(baseURL+"/"+"audiences/%v/members", audienceId) - queryParams := make(url.Values) - if request.Cursor != nil { - queryParams.Add("cursor", fmt.Sprintf("%v", *request.Cursor)) + queryParams, err := core.QueryValues(request) + if err != nil { + return nil, err } if len(queryParams) > 0 { endpointURL += "?" + queryParams.Encode() } + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + errorDecoder := func(statusCode int, body io.Reader) error { raw, err := io.ReadAll(body) if err != nil { @@ -150,7 +200,9 @@ func (c *Client) ListMembers(ctx context.Context, audienceId string, request *v3 &core.CallParams{ URL: endpointURL, Method: http.MethodGet, - Headers: c.header, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, Response: &response, ErrorDecoder: errorDecoder, }, @@ -161,21 +213,32 @@ func (c *Client) ListMembers(ctx context.Context, audienceId string, request *v3 } // Get the audiences associated with the authorization token. -func (c *Client) ListAudiences(ctx context.Context, request *v3.AudiencesListParams) (*v3.AudienceListResponse, error) { +func (c *Client) ListAudiences( + ctx context.Context, + request *v3.AudiencesListParams, + opts ...option.RequestOption, +) (*v3.AudienceListResponse, error) { + options := core.NewRequestOptions(opts...) + baseURL := "https://api.courier.com" if c.baseURL != "" { baseURL = c.baseURL } + if options.BaseURL != "" { + baseURL = options.BaseURL + } endpointURL := baseURL + "/" + "audiences" - queryParams := make(url.Values) - if request.Cursor != nil { - queryParams.Add("cursor", fmt.Sprintf("%v", *request.Cursor)) + queryParams, err := core.QueryValues(request) + if err != nil { + return nil, err } if len(queryParams) > 0 { endpointURL += "?" + queryParams.Encode() } + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + errorDecoder := func(statusCode int, body io.Reader) error { raw, err := io.ReadAll(body) if err != nil { @@ -201,7 +264,9 @@ func (c *Client) ListAudiences(ctx context.Context, request *v3.AudiencesListPar &core.CallParams{ URL: endpointURL, Method: http.MethodGet, - Headers: c.header, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, Response: &response, ErrorDecoder: errorDecoder, }, diff --git a/audit_events.go b/audit_events.go index 0d60b94..3276c4f 100644 --- a/audit_events.go +++ b/audit_events.go @@ -10,16 +10,16 @@ import ( type ListAuditEventsRequest struct { // A unique identifier that allows for fetching the next set of audit events. - Cursor *string `json:"-"` + Cursor *string `json:"-" url:"cursor,omitempty"` } type AuditEvent struct { - Actor *Actor `json:"actor,omitempty"` - Target *Target `json:"target,omitempty"` - AuditEventId string `json:"auditEventId"` - Source string `json:"source"` - Timestamp string `json:"timestamp"` - Type string `json:"type"` + Actor *Actor `json:"actor,omitempty" url:"actor,omitempty"` + Target *Target `json:"target,omitempty" url:"target,omitempty"` + AuditEventId string `json:"auditEventId" url:"auditEventId"` + Source string `json:"source" url:"source"` + Timestamp string `json:"timestamp" url:"timestamp"` + Type string `json:"type" url:"type"` _rawJSON json.RawMessage } @@ -48,8 +48,8 @@ func (a *AuditEvent) String() string { } type ListAuditEventsResponse struct { - Paging *Paging `json:"paging,omitempty"` - Results []*AuditEvent `json:"results,omitempty"` + Paging *Paging `json:"paging,omitempty" url:"paging,omitempty"` + Results []*AuditEvent `json:"results,omitempty" url:"results,omitempty"` _rawJSON json.RawMessage } diff --git a/auditevents/client.go b/auditevents/client.go index e1774f6..ce34ce9 100644 --- a/auditevents/client.go +++ b/auditevents/client.go @@ -7,8 +7,8 @@ import ( fmt "fmt" v3 "github.com/trycourier/courier-go/v3" core "github.com/trycourier/courier-go/v3/core" + option "github.com/trycourier/courier-go/v3/option" http "net/http" - url "net/url" ) type Client struct { @@ -17,42 +17,57 @@ type Client struct { header http.Header } -func NewClient(opts ...core.ClientOption) *Client { - options := core.NewClientOptions() - for _, opt := range opts { - opt(options) - } +func NewClient(opts ...option.RequestOption) *Client { + options := core.NewRequestOptions(opts...) return &Client{ baseURL: options.BaseURL, - caller: core.NewCaller(options.HTTPClient), - header: options.ToHeader(), + caller: core.NewCaller( + &core.CallerParams{ + Client: options.HTTPClient, + MaxAttempts: options.MaxAttempts, + }, + ), + header: options.ToHeader(), } } // Fetch the list of audit events -func (c *Client) List(ctx context.Context, request *v3.ListAuditEventsRequest) (*v3.ListAuditEventsResponse, error) { +func (c *Client) List( + ctx context.Context, + request *v3.ListAuditEventsRequest, + opts ...option.RequestOption, +) (*v3.ListAuditEventsResponse, error) { + options := core.NewRequestOptions(opts...) + baseURL := "https://api.courier.com" if c.baseURL != "" { baseURL = c.baseURL } + if options.BaseURL != "" { + baseURL = options.BaseURL + } endpointURL := baseURL + "/" + "audit-events" - queryParams := make(url.Values) - if request.Cursor != nil { - queryParams.Add("cursor", fmt.Sprintf("%v", *request.Cursor)) + queryParams, err := core.QueryValues(request) + if err != nil { + return nil, err } if len(queryParams) > 0 { endpointURL += "?" + queryParams.Encode() } + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + var response *v3.ListAuditEventsResponse if err := c.caller.Call( ctx, &core.CallParams{ - URL: endpointURL, - Method: http.MethodGet, - Headers: c.header, - Response: &response, + URL: endpointURL, + Method: http.MethodGet, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, + Response: &response, }, ); err != nil { return nil, err @@ -61,23 +76,35 @@ func (c *Client) List(ctx context.Context, request *v3.ListAuditEventsRequest) ( } // Fetch a specific audit event by ID. -// -// A unique identifier associated with the audit event you wish to retrieve -func (c *Client) Get(ctx context.Context, auditEventId string) (*v3.AuditEvent, error) { +func (c *Client) Get( + ctx context.Context, + // A unique identifier associated with the audit event you wish to retrieve + auditEventId string, + opts ...option.RequestOption, +) (*v3.AuditEvent, error) { + options := core.NewRequestOptions(opts...) + baseURL := "https://api.courier.com" if c.baseURL != "" { baseURL = c.baseURL } + if options.BaseURL != "" { + baseURL = options.BaseURL + } endpointURL := fmt.Sprintf(baseURL+"/"+"audit-events/%v", auditEventId) + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + var response *v3.AuditEvent if err := c.caller.Call( ctx, &core.CallParams{ - URL: endpointURL, - Method: http.MethodGet, - Headers: c.header, - Response: &response, + URL: endpointURL, + Method: http.MethodGet, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, + Response: &response, }, ); err != nil { return nil, err diff --git a/auth_tokens.go b/auth_tokens.go index 47beab2..f8f1661 100644 --- a/auth_tokens.go +++ b/auth_tokens.go @@ -9,12 +9,12 @@ import ( ) type IssueTokenParams struct { - Scope string `json:"scope"` - ExpiresIn string `json:"expires_in"` + Scope string `json:"scope" url:"scope"` + ExpiresIn string `json:"expires_in" url:"expires_in"` } type IssueTokenResponse struct { - Token *string `json:"token,omitempty"` + Token *string `json:"token,omitempty" url:"token,omitempty"` _rawJSON json.RawMessage } diff --git a/authtokens/client.go b/authtokens/client.go index 96d83d8..d4e7988 100644 --- a/authtokens/client.go +++ b/authtokens/client.go @@ -6,6 +6,7 @@ import ( context "context" v3 "github.com/trycourier/courier-go/v3" core "github.com/trycourier/courier-go/v3/core" + option "github.com/trycourier/courier-go/v3/option" http "net/http" ) @@ -15,35 +16,50 @@ type Client struct { header http.Header } -func NewClient(opts ...core.ClientOption) *Client { - options := core.NewClientOptions() - for _, opt := range opts { - opt(options) - } +func NewClient(opts ...option.RequestOption) *Client { + options := core.NewRequestOptions(opts...) return &Client{ baseURL: options.BaseURL, - caller: core.NewCaller(options.HTTPClient), - header: options.ToHeader(), + caller: core.NewCaller( + &core.CallerParams{ + Client: options.HTTPClient, + MaxAttempts: options.MaxAttempts, + }, + ), + header: options.ToHeader(), } } // Returns a new access token. -func (c *Client) IssueToken(ctx context.Context, request *v3.IssueTokenParams) (*v3.IssueTokenResponse, error) { +func (c *Client) IssueToken( + ctx context.Context, + request *v3.IssueTokenParams, + opts ...option.IdempotentRequestOption, +) (*v3.IssueTokenResponse, error) { + options := core.NewIdempotentRequestOptions(opts...) + baseURL := "https://api.courier.com" if c.baseURL != "" { baseURL = c.baseURL } + if options.BaseURL != "" { + baseURL = options.BaseURL + } endpointURL := baseURL + "/" + "auth/issue-token" + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + var response *v3.IssueTokenResponse if err := c.caller.Call( ctx, &core.CallParams{ - URL: endpointURL, - Method: http.MethodPost, - Headers: c.header, - Request: request, - Response: &response, + URL: endpointURL, + Method: http.MethodPost, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, + Request: request, + Response: &response, }, ); err != nil { return nil, err diff --git a/automations.go b/automations.go index e7304f6..ce575e8 100644 --- a/automations.go +++ b/automations.go @@ -9,12 +9,12 @@ import ( ) type AutomationAdHocInvokeParams struct { - Brand *string `json:"brand,omitempty"` - Data map[string]interface{} `json:"data,omitempty"` - Profile *Profile `json:"profile,omitempty"` - Recipient *string `json:"recipient,omitempty"` - Template *string `json:"template,omitempty"` - Automation *Automation `json:"automation,omitempty"` + Brand *string `json:"brand,omitempty" url:"brand,omitempty"` + Data map[string]interface{} `json:"data,omitempty" url:"data,omitempty"` + Profile *Profile `json:"profile,omitempty" url:"profile,omitempty"` + Recipient *string `json:"recipient,omitempty" url:"recipient,omitempty"` + Template *string `json:"template,omitempty" url:"template,omitempty"` + Automation *Automation `json:"automation,omitempty" url:"automation,omitempty"` _rawJSON json.RawMessage } @@ -43,11 +43,11 @@ func (a *AutomationAdHocInvokeParams) String() string { } type AutomationInvokeParams struct { - Brand *string `json:"brand,omitempty"` - Data map[string]interface{} `json:"data,omitempty"` - Profile *Profile `json:"profile,omitempty"` - Recipient *string `json:"recipient,omitempty"` - Template *string `json:"template,omitempty"` + Brand *string `json:"brand,omitempty" url:"brand,omitempty"` + Data map[string]interface{} `json:"data,omitempty" url:"data,omitempty"` + Profile *Profile `json:"profile,omitempty" url:"profile,omitempty"` + Recipient *string `json:"recipient,omitempty" url:"recipient,omitempty"` + Template *string `json:"template,omitempty" url:"template,omitempty"` _rawJSON json.RawMessage } @@ -76,7 +76,7 @@ func (a *AutomationInvokeParams) String() string { } type AutomationInvokeResponse struct { - RunId string `json:"runId"` + RunId string `json:"runId" url:"runId"` _rawJSON json.RawMessage } diff --git a/automations/client.go b/automations/client.go index 0cd1eff..775c911 100644 --- a/automations/client.go +++ b/automations/client.go @@ -7,6 +7,7 @@ import ( fmt "fmt" v3 "github.com/trycourier/courier-go/v3" core "github.com/trycourier/courier-go/v3/core" + option "github.com/trycourier/courier-go/v3/option" http "net/http" ) @@ -16,37 +17,52 @@ type Client struct { header http.Header } -func NewClient(opts ...core.ClientOption) *Client { - options := core.NewClientOptions() - for _, opt := range opts { - opt(options) - } +func NewClient(opts ...option.RequestOption) *Client { + options := core.NewRequestOptions(opts...) return &Client{ baseURL: options.BaseURL, - caller: core.NewCaller(options.HTTPClient), - header: options.ToHeader(), + caller: core.NewCaller( + &core.CallerParams{ + Client: options.HTTPClient, + MaxAttempts: options.MaxAttempts, + }, + ), + header: options.ToHeader(), } } // Invoke an automation run from an automation template. -// -// A unique identifier representing the automation template to be invoked. This could be the Automation Template ID or the Automation Template Alias. -func (c *Client) InvokeAutomationTemplate(ctx context.Context, templateId string, request *v3.AutomationInvokeParams) (*v3.AutomationInvokeResponse, error) { +func (c *Client) InvokeAutomationTemplate( + ctx context.Context, + // A unique identifier representing the automation template to be invoked. This could be the Automation Template ID or the Automation Template Alias. + templateId string, + request *v3.AutomationInvokeParams, + opts ...option.IdempotentRequestOption, +) (*v3.AutomationInvokeResponse, error) { + options := core.NewIdempotentRequestOptions(opts...) + baseURL := "https://api.courier.com" if c.baseURL != "" { baseURL = c.baseURL } + if options.BaseURL != "" { + baseURL = options.BaseURL + } endpointURL := fmt.Sprintf(baseURL+"/"+"automations/%v/invoke", templateId) + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + var response *v3.AutomationInvokeResponse if err := c.caller.Call( ctx, &core.CallParams{ - URL: endpointURL, - Method: http.MethodPost, - Headers: c.header, - Request: request, - Response: &response, + URL: endpointURL, + Method: http.MethodPost, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, + Request: request, + Response: &response, }, ); err != nil { return nil, err @@ -55,22 +71,35 @@ func (c *Client) InvokeAutomationTemplate(ctx context.Context, templateId string } // Invoke an ad hoc automation run. This endpoint accepts a JSON payload with a series of automation steps. For information about what steps are available, checkout the ad hoc automation guide [here](https://www.courier.com/docs/automations/steps/). -func (c *Client) InvokeAdHocAutomation(ctx context.Context, request *v3.AutomationAdHocInvokeParams) (*v3.AutomationInvokeResponse, error) { +func (c *Client) InvokeAdHocAutomation( + ctx context.Context, + request *v3.AutomationAdHocInvokeParams, + opts ...option.IdempotentRequestOption, +) (*v3.AutomationInvokeResponse, error) { + options := core.NewIdempotentRequestOptions(opts...) + baseURL := "https://api.courier.com" if c.baseURL != "" { baseURL = c.baseURL } + if options.BaseURL != "" { + baseURL = options.BaseURL + } endpointURL := baseURL + "/" + "automations/invoke" + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + var response *v3.AutomationInvokeResponse if err := c.caller.Call( ctx, &core.CallParams{ - URL: endpointURL, - Method: http.MethodPost, - Headers: c.header, - Request: request, - Response: &response, + URL: endpointURL, + Method: http.MethodPost, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, + Request: request, + Response: &response, }, ); err != nil { return nil, err diff --git a/brands.go b/brands.go index ecbe5c9..1579066 100644 --- a/brands.go +++ b/brands.go @@ -10,31 +10,31 @@ import ( type ListBrandsRequest struct { // A unique identifier that allows for fetching the next set of brands. - Cursor *string `json:"-"` + Cursor *string `json:"-" url:"cursor,omitempty"` } type BrandUpdateParameters struct { // The name of the brand. - Name string `json:"name"` - Settings *BrandSettings `json:"settings,omitempty"` - Snippets *BrandSnippets `json:"snippets,omitempty"` + Name string `json:"name" url:"name"` + Settings *BrandSettings `json:"settings,omitempty" url:"settings,omitempty"` + Snippets *BrandSnippets `json:"snippets,omitempty" url:"snippets,omitempty"` } type Brand struct { // The date/time of when the brand was created. Represented in milliseconds since Unix epoch. - Created int `json:"created"` + Created int `json:"created" url:"created"` // Brand Identifier - Id *string `json:"id,omitempty"` + Id *string `json:"id,omitempty" url:"id,omitempty"` // Brand name - Name string `json:"name"` + Name string `json:"name" url:"name"` // The date/time of when the brand was published. Represented in milliseconds since Unix epoch. - Published int `json:"published"` - Settings *BrandSettings `json:"settings,omitempty"` + Published int `json:"published" url:"published"` + Settings *BrandSettings `json:"settings,omitempty" url:"settings,omitempty"` // The date/time of when the brand was updated. Represented in milliseconds since Unix epoch. - Updated int `json:"updated"` - Snippets *BrandSnippets `json:"snippets,omitempty"` + Updated int `json:"updated" url:"updated"` + Snippets *BrandSnippets `json:"snippets,omitempty" url:"snippets,omitempty"` // The version identifier for the brand - Version string `json:"version"` + Version string `json:"version" url:"version"` _rawJSON json.RawMessage } @@ -63,11 +63,11 @@ func (b *Brand) String() string { } type BrandParameters struct { - Id *string `json:"id,omitempty"` + Id *string `json:"id,omitempty" url:"id,omitempty"` // The name of the brand. - Name string `json:"name"` - Settings *BrandSettings `json:"settings,omitempty"` - Snippets *BrandSnippets `json:"snippets,omitempty"` + Name string `json:"name" url:"name"` + Settings *BrandSettings `json:"settings,omitempty" url:"settings,omitempty"` + Snippets *BrandSnippets `json:"snippets,omitempty" url:"snippets,omitempty"` _rawJSON json.RawMessage } @@ -96,9 +96,9 @@ func (b *BrandParameters) String() string { } type BrandSettings struct { - Colors *BrandColors `json:"colors,omitempty"` - Inapp interface{} `json:"inapp,omitempty"` - Email *Email `json:"email,omitempty"` + Colors *BrandColors `json:"colors,omitempty" url:"colors,omitempty"` + Inapp interface{} `json:"inapp,omitempty" url:"inapp,omitempty"` + Email *Email `json:"email,omitempty" url:"email,omitempty"` _rawJSON json.RawMessage } @@ -127,7 +127,7 @@ func (b *BrandSettings) String() string { } type BrandSnippets struct { - Items []*BrandSnippet `json:"items,omitempty"` + Items []*BrandSnippet `json:"items,omitempty" url:"items,omitempty"` _rawJSON json.RawMessage } @@ -156,8 +156,8 @@ func (b *BrandSnippets) String() string { } type BrandsResponse struct { - Paging *Paging `json:"paging,omitempty"` - Results []*Brand `json:"results,omitempty"` + Paging *Paging `json:"paging,omitempty" url:"paging,omitempty"` + Results []*Brand `json:"results,omitempty" url:"results,omitempty"` _rawJSON json.RawMessage } diff --git a/brands/client.go b/brands/client.go index e1eeb4a..43745d6 100644 --- a/brands/client.go +++ b/brands/client.go @@ -10,9 +10,9 @@ import ( fmt "fmt" v3 "github.com/trycourier/courier-go/v3" core "github.com/trycourier/courier-go/v3/core" + option "github.com/trycourier/courier-go/v3/option" io "io" http "net/http" - url "net/url" ) type Client struct { @@ -21,25 +21,38 @@ type Client struct { header http.Header } -func NewClient(opts ...core.ClientOption) *Client { - options := core.NewClientOptions() - for _, opt := range opts { - opt(options) - } +func NewClient(opts ...option.RequestOption) *Client { + options := core.NewRequestOptions(opts...) return &Client{ baseURL: options.BaseURL, - caller: core.NewCaller(options.HTTPClient), - header: options.ToHeader(), + caller: core.NewCaller( + &core.CallerParams{ + Client: options.HTTPClient, + MaxAttempts: options.MaxAttempts, + }, + ), + header: options.ToHeader(), } } -func (c *Client) Create(ctx context.Context, request *v3.BrandParameters) (*v3.Brand, error) { +func (c *Client) Create( + ctx context.Context, + request *v3.BrandParameters, + opts ...option.IdempotentRequestOption, +) (*v3.Brand, error) { + options := core.NewIdempotentRequestOptions(opts...) + baseURL := "https://api.courier.com" if c.baseURL != "" { baseURL = c.baseURL } + if options.BaseURL != "" { + baseURL = options.BaseURL + } endpointURL := baseURL + "/" + "brands" + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + errorDecoder := func(statusCode int, body io.Reader) error { raw, err := io.ReadAll(body) if err != nil { @@ -79,7 +92,9 @@ func (c *Client) Create(ctx context.Context, request *v3.BrandParameters) (*v3.B &core.CallParams{ URL: endpointURL, Method: http.MethodPost, - Headers: c.header, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, Request: request, Response: &response, ErrorDecoder: errorDecoder, @@ -91,23 +106,35 @@ func (c *Client) Create(ctx context.Context, request *v3.BrandParameters) (*v3.B } // Fetch a specific brand by brand ID. -// -// A unique identifier associated with the brand you wish to retrieve. -func (c *Client) Get(ctx context.Context, brandId string) (*v3.Brand, error) { +func (c *Client) Get( + ctx context.Context, + // A unique identifier associated with the brand you wish to retrieve. + brandId string, + opts ...option.RequestOption, +) (*v3.Brand, error) { + options := core.NewRequestOptions(opts...) + baseURL := "https://api.courier.com" if c.baseURL != "" { baseURL = c.baseURL } + if options.BaseURL != "" { + baseURL = options.BaseURL + } endpointURL := fmt.Sprintf(baseURL+"/"+"brands/%v", brandId) + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + var response *v3.Brand if err := c.caller.Call( ctx, &core.CallParams{ - URL: endpointURL, - Method: http.MethodGet, - Headers: c.header, - Response: &response, + URL: endpointURL, + Method: http.MethodGet, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, + Response: &response, }, ); err != nil { return nil, err @@ -116,29 +143,42 @@ func (c *Client) Get(ctx context.Context, brandId string) (*v3.Brand, error) { } // Get the list of brands. -func (c *Client) List(ctx context.Context, request *v3.ListBrandsRequest) (*v3.BrandsResponse, error) { +func (c *Client) List( + ctx context.Context, + request *v3.ListBrandsRequest, + opts ...option.RequestOption, +) (*v3.BrandsResponse, error) { + options := core.NewRequestOptions(opts...) + baseURL := "https://api.courier.com" if c.baseURL != "" { baseURL = c.baseURL } + if options.BaseURL != "" { + baseURL = options.BaseURL + } endpointURL := baseURL + "/" + "brands" - queryParams := make(url.Values) - if request.Cursor != nil { - queryParams.Add("cursor", fmt.Sprintf("%v", *request.Cursor)) + queryParams, err := core.QueryValues(request) + if err != nil { + return nil, err } if len(queryParams) > 0 { endpointURL += "?" + queryParams.Encode() } + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + var response *v3.BrandsResponse if err := c.caller.Call( ctx, &core.CallParams{ - URL: endpointURL, - Method: http.MethodGet, - Headers: c.header, - Response: &response, + URL: endpointURL, + Method: http.MethodGet, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, + Response: &response, }, ); err != nil { return nil, err @@ -147,15 +187,25 @@ func (c *Client) List(ctx context.Context, request *v3.ListBrandsRequest) (*v3.B } // Delete a brand by brand ID. -// -// A unique identifier associated with the brand you wish to retrieve. -func (c *Client) Delete(ctx context.Context, brandId string) error { +func (c *Client) Delete( + ctx context.Context, + // A unique identifier associated with the brand you wish to retrieve. + brandId string, + opts ...option.RequestOption, +) error { + options := core.NewRequestOptions(opts...) + baseURL := "https://api.courier.com" if c.baseURL != "" { baseURL = c.baseURL } + if options.BaseURL != "" { + baseURL = options.BaseURL + } endpointURL := fmt.Sprintf(baseURL+"/"+"brands/%v", brandId) + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + errorDecoder := func(statusCode int, body io.Reader) error { raw, err := io.ReadAll(body) if err != nil { @@ -180,7 +230,9 @@ func (c *Client) Delete(ctx context.Context, brandId string) error { &core.CallParams{ URL: endpointURL, Method: http.MethodDelete, - Headers: c.header, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, ErrorDecoder: errorDecoder, }, ); err != nil { @@ -190,24 +242,37 @@ func (c *Client) Delete(ctx context.Context, brandId string) error { } // Replace an existing brand with the supplied values. -// -// A unique identifier associated with the brand you wish to update. -func (c *Client) Replace(ctx context.Context, brandId string, request *v3.BrandUpdateParameters) (*v3.Brand, error) { +func (c *Client) Replace( + ctx context.Context, + // A unique identifier associated with the brand you wish to update. + brandId string, + request *v3.BrandUpdateParameters, + opts ...option.RequestOption, +) (*v3.Brand, error) { + options := core.NewRequestOptions(opts...) + baseURL := "https://api.courier.com" if c.baseURL != "" { baseURL = c.baseURL } + if options.BaseURL != "" { + baseURL = options.BaseURL + } endpointURL := fmt.Sprintf(baseURL+"/"+"brands/%v", brandId) + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + var response *v3.Brand if err := c.caller.Call( ctx, &core.CallParams{ - URL: endpointURL, - Method: http.MethodPut, - Headers: c.header, - Request: request, - Response: &response, + URL: endpointURL, + Method: http.MethodPut, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, + Request: request, + Response: &response, }, ); err != nil { return nil, err diff --git a/bulk.go b/bulk.go index 1be31c5..f87a55e 100644 --- a/bulk.go +++ b/bulk.go @@ -9,11 +9,11 @@ import ( ) type BulkCreateJobParams struct { - Message *InboundBulkMessage `json:"message,omitempty"` + Message *InboundBulkMessage `json:"message,omitempty" url:"message,omitempty"` } type BulkCreateJobResponse struct { - JobId string `json:"jobId"` + JobId string `json:"jobId" url:"jobId"` _rawJSON json.RawMessage } @@ -42,7 +42,7 @@ func (b *BulkCreateJobResponse) String() string { } type BulkGetJobResponse struct { - Job *JobDetails `json:"job,omitempty"` + Job *JobDetails `json:"job,omitempty" url:"job,omitempty"` _rawJSON json.RawMessage } @@ -71,8 +71,8 @@ func (b *BulkGetJobResponse) String() string { } type BulkGetJobUsersResponse struct { - Items []*BulkMessageUserResponse `json:"items,omitempty"` - Paging *Paging `json:"paging,omitempty"` + Items []*BulkMessageUserResponse `json:"items,omitempty" url:"items,omitempty"` + Paging *Paging `json:"paging,omitempty" url:"paging,omitempty"` _rawJSON json.RawMessage } @@ -101,7 +101,7 @@ func (b *BulkGetJobUsersResponse) String() string { } type BulkIngestUsersParams struct { - Users []*InboundBulkMessageUser `json:"users,omitempty"` + Users []*InboundBulkMessageUser `json:"users,omitempty" url:"users,omitempty"` _rawJSON json.RawMessage } @@ -132,17 +132,17 @@ func (b *BulkIngestUsersParams) String() string { type InboundBulkMessage struct { // A unique identifier that represents the brand that should be used // for rendering the notification. - Brand *string `json:"brand,omitempty"` + Brand *string `json:"brand,omitempty" url:"brand,omitempty"` // JSON that includes any data you want to pass to a message template. // The data will populate the corresponding template variables. - Data map[string]interface{} `json:"data,omitempty"` - Event *string `json:"event,omitempty"` - Locale map[string]interface{} `json:"locale,omitempty"` + Data map[string]interface{} `json:"data,omitempty" url:"data,omitempty"` + Event *string `json:"event,omitempty" url:"event,omitempty"` + Locale map[string]interface{} `json:"locale,omitempty" url:"locale,omitempty"` // JSON that is merged into the request sent by Courier to the provider // to override properties or to gain access to features in the provider // API that are not natively supported by Courier. - Override interface{} `json:"override,omitempty"` - Message *InboundBulkMessageV2 `json:"message,omitempty"` + Override interface{} `json:"override,omitempty" url:"override,omitempty"` + Message *InboundBulkMessageV2 `json:"message,omitempty" url:"message,omitempty"` _rawJSON json.RawMessage } diff --git a/bulk/client.go b/bulk/client.go index b693d08..ffb3ce6 100644 --- a/bulk/client.go +++ b/bulk/client.go @@ -10,6 +10,7 @@ import ( fmt "fmt" v3 "github.com/trycourier/courier-go/v3" core "github.com/trycourier/courier-go/v3/core" + option "github.com/trycourier/courier-go/v3/option" io "io" http "net/http" ) @@ -20,25 +21,38 @@ type Client struct { header http.Header } -func NewClient(opts ...core.ClientOption) *Client { - options := core.NewClientOptions() - for _, opt := range opts { - opt(options) - } +func NewClient(opts ...option.RequestOption) *Client { + options := core.NewRequestOptions(opts...) return &Client{ baseURL: options.BaseURL, - caller: core.NewCaller(options.HTTPClient), - header: options.ToHeader(), + caller: core.NewCaller( + &core.CallerParams{ + Client: options.HTTPClient, + MaxAttempts: options.MaxAttempts, + }, + ), + header: options.ToHeader(), } } -func (c *Client) CreateJob(ctx context.Context, request *v3.BulkCreateJobParams) (*v3.BulkCreateJobResponse, error) { +func (c *Client) CreateJob( + ctx context.Context, + request *v3.BulkCreateJobParams, + opts ...option.IdempotentRequestOption, +) (*v3.BulkCreateJobResponse, error) { + options := core.NewIdempotentRequestOptions(opts...) + baseURL := "https://api.courier.com" if c.baseURL != "" { baseURL = c.baseURL } + if options.BaseURL != "" { + baseURL = options.BaseURL + } endpointURL := baseURL + "/" + "bulk" + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + errorDecoder := func(statusCode int, body io.Reader) error { raw, err := io.ReadAll(body) if err != nil { @@ -64,7 +78,9 @@ func (c *Client) CreateJob(ctx context.Context, request *v3.BulkCreateJobParams) &core.CallParams{ URL: endpointURL, Method: http.MethodPost, - Headers: c.header, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, Request: request, Response: &response, ErrorDecoder: errorDecoder, @@ -76,22 +92,35 @@ func (c *Client) CreateJob(ctx context.Context, request *v3.BulkCreateJobParams) } // Ingest user data into a Bulk Job -// -// A unique identifier representing the bulk job -func (c *Client) IngestUsers(ctx context.Context, jobId string, request *v3.BulkIngestUsersParams) error { +func (c *Client) IngestUsers( + ctx context.Context, + // A unique identifier representing the bulk job + jobId string, + request *v3.BulkIngestUsersParams, + opts ...option.IdempotentRequestOption, +) error { + options := core.NewIdempotentRequestOptions(opts...) + baseURL := "https://api.courier.com" if c.baseURL != "" { baseURL = c.baseURL } + if options.BaseURL != "" { + baseURL = options.BaseURL + } endpointURL := fmt.Sprintf(baseURL+"/"+"bulk/%v", jobId) + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + if err := c.caller.Call( ctx, &core.CallParams{ - URL: endpointURL, - Method: http.MethodPost, - Headers: c.header, - Request: request, + URL: endpointURL, + Method: http.MethodPost, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, + Request: request, }, ); err != nil { return err @@ -100,15 +129,25 @@ func (c *Client) IngestUsers(ctx context.Context, jobId string, request *v3.Bulk } // Run a bulk job -// -// A unique identifier representing the bulk job -func (c *Client) RunJob(ctx context.Context, jobId string) error { +func (c *Client) RunJob( + ctx context.Context, + // A unique identifier representing the bulk job + jobId string, + opts ...option.IdempotentRequestOption, +) error { + options := core.NewIdempotentRequestOptions(opts...) + baseURL := "https://api.courier.com" if c.baseURL != "" { baseURL = c.baseURL } + if options.BaseURL != "" { + baseURL = options.BaseURL + } endpointURL := fmt.Sprintf(baseURL+"/"+"bulk/%v/run", jobId) + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + errorDecoder := func(statusCode int, body io.Reader) error { raw, err := io.ReadAll(body) if err != nil { @@ -133,7 +172,9 @@ func (c *Client) RunJob(ctx context.Context, jobId string) error { &core.CallParams{ URL: endpointURL, Method: http.MethodPost, - Headers: c.header, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, ErrorDecoder: errorDecoder, }, ); err != nil { @@ -143,15 +184,25 @@ func (c *Client) RunJob(ctx context.Context, jobId string) error { } // Get a bulk job -// -// A unique identifier representing the bulk job -func (c *Client) GetJob(ctx context.Context, jobId string) (*v3.BulkGetJobResponse, error) { +func (c *Client) GetJob( + ctx context.Context, + // A unique identifier representing the bulk job + jobId string, + opts ...option.RequestOption, +) (*v3.BulkGetJobResponse, error) { + options := core.NewRequestOptions(opts...) + baseURL := "https://api.courier.com" if c.baseURL != "" { baseURL = c.baseURL } + if options.BaseURL != "" { + baseURL = options.BaseURL + } endpointURL := fmt.Sprintf(baseURL+"/"+"bulk/%v", jobId) + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + errorDecoder := func(statusCode int, body io.Reader) error { raw, err := io.ReadAll(body) if err != nil { @@ -177,7 +228,9 @@ func (c *Client) GetJob(ctx context.Context, jobId string) (*v3.BulkGetJobRespon &core.CallParams{ URL: endpointURL, Method: http.MethodGet, - Headers: c.header, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, Response: &response, ErrorDecoder: errorDecoder, }, @@ -188,15 +241,25 @@ func (c *Client) GetJob(ctx context.Context, jobId string) (*v3.BulkGetJobRespon } // Get Bulk Job Users -// -// A unique identifier representing the bulk job -func (c *Client) GetUsers(ctx context.Context, jobId string) (*v3.BulkGetJobUsersResponse, error) { +func (c *Client) GetUsers( + ctx context.Context, + // A unique identifier representing the bulk job + jobId string, + opts ...option.RequestOption, +) (*v3.BulkGetJobUsersResponse, error) { + options := core.NewRequestOptions(opts...) + baseURL := "https://api.courier.com" if c.baseURL != "" { baseURL = c.baseURL } + if options.BaseURL != "" { + baseURL = options.BaseURL + } endpointURL := fmt.Sprintf(baseURL+"/"+"bulk/%v/users", jobId) + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + errorDecoder := func(statusCode int, body io.Reader) error { raw, err := io.ReadAll(body) if err != nil { @@ -222,7 +285,9 @@ func (c *Client) GetUsers(ctx context.Context, jobId string) (*v3.BulkGetJobUser &core.CallParams{ URL: endpointURL, Method: http.MethodGet, - Headers: c.header, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, Response: &response, ErrorDecoder: errorDecoder, }, diff --git a/client/client.go b/client/client.go index 4706352..a7bee8a 100644 --- a/client/client.go +++ b/client/client.go @@ -15,6 +15,7 @@ import ( lists "github.com/trycourier/courier-go/v3/lists" messages "github.com/trycourier/courier-go/v3/messages" notifications "github.com/trycourier/courier-go/v3/notifications" + option "github.com/trycourier/courier-go/v3/option" profiles "github.com/trycourier/courier-go/v3/profiles" templates "github.com/trycourier/courier-go/v3/templates" tenants "github.com/trycourier/courier-go/v3/tenants" @@ -44,14 +45,16 @@ type Client struct { Users *usersclient.Client } -func NewClient(opts ...core.ClientOption) *Client { - options := core.NewClientOptions() - for _, opt := range opts { - opt(options) - } +func NewClient(opts ...option.RequestOption) *Client { + options := core.NewRequestOptions(opts...) return &Client{ - baseURL: options.BaseURL, - caller: core.NewCaller(options.HTTPClient), + baseURL: options.BaseURL, + caller: core.NewCaller( + &core.CallerParams{ + Client: options.HTTPClient, + MaxAttempts: options.MaxAttempts, + }, + ), header: options.ToHeader(), Audiences: audiences.NewClient(opts...), AuditEvents: auditevents.NewClient(opts...), @@ -71,22 +74,35 @@ func NewClient(opts ...core.ClientOption) *Client { } // Use the send API to send a message to one or more recipients. -func (c *Client) Send(ctx context.Context, request *v3.SendMessageRequest) (*v3.SendMessageResponse, error) { +func (c *Client) Send( + ctx context.Context, + request *v3.SendMessageRequest, + opts ...option.IdempotentRequestOption, +) (*v3.SendMessageResponse, error) { + options := core.NewIdempotentRequestOptions(opts...) + baseURL := "https://api.courier.com" if c.baseURL != "" { baseURL = c.baseURL } + if options.BaseURL != "" { + baseURL = options.BaseURL + } endpointURL := baseURL + "/" + "send" + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + var response *v3.SendMessageResponse if err := c.caller.Call( ctx, &core.CallParams{ - URL: endpointURL, - Method: http.MethodPost, - Headers: c.header, - Request: request, - Response: &response, + URL: endpointURL, + Method: http.MethodPost, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, + Request: request, + Response: &response, }, ); err != nil { return nil, err diff --git a/client/client_test.go b/client/client_test.go index a0fa36e..e93a027 100644 --- a/client/client_test.go +++ b/client/client_test.go @@ -1,11 +1,13 @@ +// This file was auto-generated by Fern from our API Definition. + package client import ( - "net/http" - "testing" - "time" - - "github.com/stretchr/testify/assert" + assert "github.com/stretchr/testify/assert" + option "github.com/trycourier/courier-go/v3/option" + http "net/http" + testing "testing" + time "time" ) func TestNewClient(t *testing.T) { @@ -16,7 +18,7 @@ func TestNewClient(t *testing.T) { t.Run("base url", func(t *testing.T) { c := NewClient( - WithBaseURL("test.co"), + option.WithBaseURL("test.co"), ) assert.Equal(t, "test.co", c.baseURL) }) @@ -26,7 +28,7 @@ func TestNewClient(t *testing.T) { Timeout: 5 * time.Second, } c := NewClient( - WithHTTPClient(httpClient), + option.WithHTTPClient(httpClient), ) assert.Empty(t, c.baseURL) }) @@ -35,7 +37,7 @@ func TestNewClient(t *testing.T) { header := make(http.Header) header.Set("X-API-Tenancy", "test") c := NewClient( - WithHTTPHeader(header), + option.WithHTTPHeader(header), ) assert.Empty(t, c.baseURL) assert.Equal(t, "test", c.header.Get("X-API-Tenancy")) diff --git a/client/options.go b/client/options.go deleted file mode 100644 index 55acfbd..0000000 --- a/client/options.go +++ /dev/null @@ -1,39 +0,0 @@ -// This file was auto-generated by Fern from our API Definition. - -package client - -import ( - core "github.com/trycourier/courier-go/v3/core" - http "net/http" -) - -// WithBaseURL sets the client's base URL, overriding the -// default environment, if any. -func WithBaseURL(baseURL string) core.ClientOption { - return func(opts *core.ClientOptions) { - opts.BaseURL = baseURL - } -} - -// WithHTTPClient uses the given HTTPClient to issue all HTTP requests. -func WithHTTPClient(httpClient core.HTTPClient) core.ClientOption { - return func(opts *core.ClientOptions) { - opts.HTTPClient = httpClient - } -} - -// WithHTTPHeader adds the given http.Header to all requests -// issued by the client. -func WithHTTPHeader(httpHeader http.Header) core.ClientOption { - return func(opts *core.ClientOptions) { - // Clone the headers so they can't be modified after the option call. - opts.HTTPHeader = httpHeader.Clone() - } -} - -// WithAuthorizationToken sets the 'Authorization: Bearer ' header on every request. -func WithAuthorizationToken(authorizationToken string) core.ClientOption { - return func(opts *core.ClientOptions) { - opts.AuthorizationToken = authorizationToken - } -} diff --git a/core/client_option.go b/core/client_option.go deleted file mode 100644 index b3ab443..0000000 --- a/core/client_option.go +++ /dev/null @@ -1,48 +0,0 @@ -// This file was auto-generated by Fern from our API Definition. - -package core - -import ( - http "net/http" -) - -// ClientOption adapts the behavior of the generated client. -type ClientOption func(*ClientOptions) - -// ClientOptions defines all of the possible client options. -// This type is primarily used by the generated code and is -// not meant to be used directly; use ClientOption instead. -type ClientOptions struct { - BaseURL string - HTTPClient HTTPClient - HTTPHeader http.Header - AuthorizationToken string -} - -// NewClientOptions returns a new *ClientOptions value. -// This function is primarily used by the generated code and is -// not meant to be used directly; use ClientOption instead. -func NewClientOptions() *ClientOptions { - return &ClientOptions{ - HTTPClient: http.DefaultClient, - HTTPHeader: make(http.Header), - } -} - -// ToHeader maps the configured client options into a http.Header issued -// on every request. -func (c *ClientOptions) ToHeader() http.Header { - header := c.cloneHeader() - if c.AuthorizationToken != "" { - header.Set("Authorization", "Bearer "+c.AuthorizationToken) - } - return header -} - -func (c *ClientOptions) cloneHeader() http.Header { - headers := c.HTTPHeader.Clone() - headers.Set("X-Fern-Language", "Go") - headers.Set("X-Fern-SDK-Name", "github.com/trycourier/courier-go/v3") - headers.Set("X-Fern-SDK-Version", "v3.0.5") - return headers -} diff --git a/core/core.go b/core/core.go index 10e757f..5277d13 100644 --- a/core/core.go +++ b/core/core.go @@ -22,6 +22,21 @@ type HTTPClient interface { Do(*http.Request) (*http.Response, error) } +// MergeHeaders merges the given headers together, where the right +// takes precedence over the left. +func MergeHeaders(left, right http.Header) http.Header { + for key, values := range right { + if len(values) > 1 { + left[key] = values + continue + } + if value := right.Get(key); value != "" { + left.Set(key, value) + } + } + return left +} + // WriteMultipartJSON writes the given value as a JSON part. // This is used to serialize non-primitive multipart properties // (i.e. lists, objects, etc). @@ -78,13 +93,29 @@ type ErrorDecoder func(statusCode int, body io.Reader) error // Caller calls APIs and deserializes their response, if any. type Caller struct { - client HTTPClient + client HTTPClient + retrier *Retrier +} + +// CallerParams represents the parameters used to constrcut a new *Caller. +type CallerParams struct { + Client HTTPClient + MaxAttempts uint } -// NewCaller returns a new *Caller backed by the given HTTP client. -func NewCaller(client HTTPClient) *Caller { +// NewCaller returns a new *Caller backed by the given parameters. +func NewCaller(params *CallerParams) *Caller { + var httpClient HTTPClient = http.DefaultClient + if params.Client != nil { + httpClient = params.Client + } + var retryOptions []RetryOption + if params.MaxAttempts > 0 { + retryOptions = append(retryOptions, WithMaxAttempts(params.MaxAttempts)) + } return &Caller{ - client: client, + client: httpClient, + retrier: NewRetrier(retryOptions...), } } @@ -92,7 +123,9 @@ func NewCaller(client HTTPClient) *Caller { type CallParams struct { URL string Method string + MaxAttempts uint Headers http.Header + Client HTTPClient Request interface{} Response interface{} ResponseIsOptional bool @@ -111,7 +144,23 @@ func (c *Caller) Call(ctx context.Context, params *CallParams) error { return err } - resp, err := c.client.Do(req) + client := c.client + if params.Client != nil { + // Use the HTTP client scoped to the request. + client = params.Client + } + + var retryOptions []RetryOption + if params.MaxAttempts > 0 { + retryOptions = append(retryOptions, WithMaxAttempts(params.MaxAttempts)) + } + + resp, err := c.retrier.Run( + client.Do, + req, + params.ErrorDecoder, + retryOptions..., + ) if err != nil { return err } diff --git a/core/core_test.go b/core/core_test.go index 70a59ed..f476f9e 100644 --- a/core/core_test.go +++ b/core/core_test.go @@ -113,7 +113,11 @@ func TestCall(t *testing.T) { server = newTestServer(t, test) client = server.Client() ) - caller := NewCaller(client) + caller := NewCaller( + &CallerParams{ + Client: client, + }, + ) var response *Response err := caller.Call( context.Background(), @@ -137,6 +141,68 @@ func TestCall(t *testing.T) { } } +func TestMergeHeaders(t *testing.T) { + t.Run("both empty", func(t *testing.T) { + merged := MergeHeaders(make(http.Header), make(http.Header)) + assert.Empty(t, merged) + }) + + t.Run("empty left", func(t *testing.T) { + left := make(http.Header) + + right := make(http.Header) + right.Set("X-API-Version", "0.0.1") + + merged := MergeHeaders(left, right) + assert.Equal(t, "0.0.1", merged.Get("X-API-Version")) + }) + + t.Run("empty right", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Version", "0.0.1") + + right := make(http.Header) + + merged := MergeHeaders(left, right) + assert.Equal(t, "0.0.1", merged.Get("X-API-Version")) + }) + + t.Run("single value override", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Version", "0.0.0") + + right := make(http.Header) + right.Set("X-API-Version", "0.0.1") + + merged := MergeHeaders(left, right) + assert.Equal(t, []string{"0.0.1"}, merged.Values("X-API-Version")) + }) + + t.Run("multiple value override", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Versions", "0.0.0") + + right := make(http.Header) + right.Add("X-API-Versions", "0.0.1") + right.Add("X-API-Versions", "0.0.2") + + merged := MergeHeaders(left, right) + assert.Equal(t, []string{"0.0.1", "0.0.2"}, merged.Values("X-API-Versions")) + }) + + t.Run("disjoint merge", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Tenancy", "test") + + right := make(http.Header) + right.Set("X-API-Version", "0.0.1") + + merged := MergeHeaders(left, right) + assert.Equal(t, []string{"test"}, merged.Values("X-API-Tenancy")) + assert.Equal(t, []string{"0.0.1"}, merged.Values("X-API-Version")) + }) +} + // newTestServer returns a new *httptest.Server configured with the // given test parameters. func newTestServer(t *testing.T, tc *TestCase) *httptest.Server { @@ -206,8 +272,7 @@ func newTestErrorDecoder(t *testing.T) func(int, io.Reader) error { apiError = NewAPIError(statusCode, errors.New(string(raw))) decoder = json.NewDecoder(bytes.NewReader(raw)) ) - switch statusCode { - case 404: + if statusCode == http.StatusNotFound { value := new(NotFoundError) value.APIError = apiError require.NoError(t, decoder.Decode(value)) diff --git a/core/idempotent_request_option.go b/core/idempotent_request_option.go new file mode 100644 index 0000000..d396091 --- /dev/null +++ b/core/idempotent_request_option.go @@ -0,0 +1,72 @@ +// This file was auto-generated by Fern from our API Definition. + +package core + +import ( + fmt "fmt" + http "net/http" +) + +// IdempotentRequestOption adapts the behavior of an individual request. +type IdempotentRequestOption interface { + applyIdempotentRequestOptions(*IdempotentRequestOptions) +} + +// IdempotentRequestOptions defines all of the possible idempotent request options. +// +// This type is primarily used by the generated code and is not meant +// to be used directly; use the option package instead. +type IdempotentRequestOptions struct { + *RequestOptions + + IdempotencyKey *string + IdempotencyExpiry *int +} + +// NewIdempotentRequestOptions returns a new *IdempotentRequestOptions value. +// +// This function is primarily used by the generated code and is not meant +// to be used directly; use IdempotentRequestOption instead. +func NewIdempotentRequestOptions(opts ...IdempotentRequestOption) *IdempotentRequestOptions { + options := &IdempotentRequestOptions{ + RequestOptions: NewRequestOptions(), + } + for _, opt := range opts { + if requestOption, ok := opt.(RequestOption); ok { + requestOption.applyRequestOptions(options.RequestOptions) + } + opt.applyIdempotentRequestOptions(options) + } + return options +} + +// IdempotencyKeyOption implements the RequestOption interface. +type IdempotencyKeyOption struct { + IdempotencyKey *string +} + +func (i *IdempotencyKeyOption) applyIdempotentRequestOptions(opts *IdempotentRequestOptions) { + opts.IdempotencyKey = i.IdempotencyKey +} + +// IdempotencyExpiryOption implements the RequestOption interface. +type IdempotencyExpiryOption struct { + IdempotencyExpiry *int +} + +func (i *IdempotencyExpiryOption) applyIdempotentRequestOptions(opts *IdempotentRequestOptions) { + opts.IdempotencyExpiry = i.IdempotencyExpiry +} + +// ToHeader maps the configured request options into a http.Header used +// for the request. +func (i *IdempotentRequestOptions) ToHeader() http.Header { + header := i.RequestOptions.ToHeader() + if i.IdempotencyKey != nil { + header.Set("Idempotency-Key", fmt.Sprintf("*%v", *i.IdempotencyKey)) + } + if i.IdempotencyExpiry != nil { + header.Set("X-Idempotency-Expiration", fmt.Sprintf("*%v", *i.IdempotencyExpiry)) + } + return header +} diff --git a/core/query.go b/core/query.go new file mode 100644 index 0000000..479cbb2 --- /dev/null +++ b/core/query.go @@ -0,0 +1,219 @@ +package core + +import ( + "encoding/base64" + "fmt" + "net/url" + "reflect" + "strings" + "time" + + "github.com/google/uuid" +) + +var ( + bytesType = reflect.TypeOf([]byte{}) + queryEncoderType = reflect.TypeOf(new(QueryEncoder)).Elem() + timeType = reflect.TypeOf(time.Time{}) + uuidType = reflect.TypeOf(uuid.UUID{}) +) + +// QueryEncoder is an interface implemented by any type that wishes to encode +// itself into URL values in a non-standard way. +type QueryEncoder interface { + EncodeQueryValues(key string, v *url.Values) error +} + +// QueryValues encodes url.Values from request objects. +// +// Note: This type is inspired by Google's query encoding library, but +// supports far less customization and is tailored to fit this SDK's use case. +// +// Ref: https://github.com/google/go-querystring +func QueryValues(v interface{}) (url.Values, error) { + values := make(url.Values) + val := reflect.ValueOf(v) + for val.Kind() == reflect.Ptr { + if val.IsNil() { + return values, nil + } + val = val.Elem() + } + + if v == nil { + return values, nil + } + + if val.Kind() != reflect.Struct { + return nil, fmt.Errorf("query: Values() expects struct input. Got %v", val.Kind()) + } + + err := reflectValue(values, val, "") + return values, err +} + +// reflectValue populates the values parameter from the struct fields in val. +// Embedded structs are followed recursively (using the rules defined in the +// Values function documentation) breadth-first. +func reflectValue(values url.Values, val reflect.Value, scope string) error { + typ := val.Type() + for i := 0; i < typ.NumField(); i++ { + sf := typ.Field(i) + if sf.PkgPath != "" && !sf.Anonymous { + // Skip unexported fields. + continue + } + + sv := val.Field(i) + tag := sf.Tag.Get("url") + if tag == "" || tag == "-" { + continue + } + + name, opts := parseTag(tag) + if name == "" { + name = sf.Name + } + + if scope != "" { + name = scope + "[" + name + "]" + } + + if opts.Contains("omitempty") && isEmptyValue(sv) { + continue + } + + if sv.Type().Implements(queryEncoderType) { + // If sv is a nil pointer and the custom encoder is defined on a non-pointer + // method receiver, set sv to the zero value of the underlying type + if !reflect.Indirect(sv).IsValid() && sv.Type().Elem().Implements(queryEncoderType) { + sv = reflect.New(sv.Type().Elem()) + } + + m := sv.Interface().(QueryEncoder) + if err := m.EncodeQueryValues(name, &values); err != nil { + return err + } + continue + } + + // Recursively dereference pointers, but stop at nil pointers. + for sv.Kind() == reflect.Ptr { + if sv.IsNil() { + break + } + sv = sv.Elem() + } + + if sv.Type() == uuidType || sv.Type() == bytesType || sv.Type() == timeType { + values.Add(name, valueString(sv, opts, sf)) + continue + } + + if sv.Kind() == reflect.Slice || sv.Kind() == reflect.Array { + if sv.Len() == 0 { + // Skip if slice or array is empty. + continue + } + for i := 0; i < sv.Len(); i++ { + values.Add(name, valueString(sv.Index(i), opts, sf)) + } + continue + } + + if sv.Kind() == reflect.Struct { + if err := reflectValue(values, sv, name); err != nil { + return err + } + continue + } + + values.Add(name, valueString(sv, opts, sf)) + } + + return nil +} + +// valueString returns the string representation of a value. +func valueString(v reflect.Value, opts tagOptions, sf reflect.StructField) string { + for v.Kind() == reflect.Ptr { + if v.IsNil() { + return "" + } + v = v.Elem() + } + + if v.Type() == timeType { + t := v.Interface().(time.Time) + if format := sf.Tag.Get("format"); format == "date" { + return t.Format("2006-01-02") + } + return t.Format(time.RFC3339) + } + + if v.Type() == uuidType { + u := v.Interface().(uuid.UUID) + return u.String() + } + + if v.Type() == bytesType { + b := v.Interface().([]byte) + return base64.StdEncoding.EncodeToString(b) + } + + return fmt.Sprint(v.Interface()) +} + +// isEmptyValue checks if a value should be considered empty for the purposes +// of omitting fields with the "omitempty" option. +func isEmptyValue(v reflect.Value) bool { + type zeroable interface { + IsZero() bool + } + + if !v.IsNil() { + if z, ok := v.Interface().(zeroable); ok { + return z.IsZero() + } + } + + switch v.Kind() { + case reflect.Array, reflect.Map, reflect.Slice, reflect.String: + return v.Len() == 0 + case reflect.Bool: + return !v.Bool() + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return v.Int() == 0 + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return v.Uint() == 0 + case reflect.Float32, reflect.Float64: + return v.Float() == 0 + case reflect.Interface, reflect.Ptr: + return v.IsNil() + case reflect.Invalid, reflect.Complex64, reflect.Complex128, reflect.Chan, reflect.Func, reflect.Struct, reflect.UnsafePointer: + return false + } + + return false +} + +// tagOptions is the string following a comma in a struct field's "url" tag, or +// the empty string. It does not include the leading comma. +type tagOptions []string + +// parseTag splits a struct field's url tag into its name and comma-separated +// options. +func parseTag(tag string) (string, tagOptions) { + s := strings.Split(tag, ",") + return s[0], s[1:] +} + +// Contains checks whether the tagOptions contains the specified option. +func (o tagOptions) Contains(option string) bool { + for _, s := range o { + if s == option { + return true + } + } + return false +} diff --git a/core/query_test.go b/core/query_test.go new file mode 100644 index 0000000..4f0d392 --- /dev/null +++ b/core/query_test.go @@ -0,0 +1,146 @@ +package core + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestQueryValues(t *testing.T) { + t.Run("empty optional", func(t *testing.T) { + type nested struct { + Value *string `json:"value,omitempty" url:"value,omitempty"` + } + type example struct { + Nested *nested `json:"nested,omitempty" url:"nested,omitempty"` + } + + values, err := QueryValues(&example{}) + require.NoError(t, err) + assert.Empty(t, values) + }) + + t.Run("empty required", func(t *testing.T) { + type nested struct { + Value *string `json:"value,omitempty" url:"value,omitempty"` + } + type example struct { + Required string `json:"required" url:"required"` + Nested *nested `json:"nested,omitempty" url:"nested,omitempty"` + } + + values, err := QueryValues(&example{}) + require.NoError(t, err) + assert.Equal(t, "required=", values.Encode()) + }) + + t.Run("allow multiple", func(t *testing.T) { + type example struct { + Values []string `json:"values" url:"values"` + } + + values, err := QueryValues( + &example{ + Values: []string{"foo", "bar", "baz"}, + }, + ) + require.NoError(t, err) + assert.Equal(t, "values=foo&values=bar&values=baz", values.Encode()) + }) + + t.Run("nested object", func(t *testing.T) { + type nested struct { + Value *string `json:"value,omitempty" url:"value,omitempty"` + } + type example struct { + Required string `json:"required" url:"required"` + Nested *nested `json:"nested,omitempty" url:"nested,omitempty"` + } + + nestedValue := "nestedValue" + values, err := QueryValues( + &example{ + Required: "requiredValue", + Nested: &nested{ + Value: &nestedValue, + }, + }, + ) + require.NoError(t, err) + assert.Equal(t, "nested%5Bvalue%5D=nestedValue&required=requiredValue", values.Encode()) + }) + + t.Run("url unspecified", func(t *testing.T) { + type example struct { + Required string `json:"required" url:"required"` + NotFound string `json:"notFound"` + } + + values, err := QueryValues( + &example{ + Required: "requiredValue", + NotFound: "notFound", + }, + ) + require.NoError(t, err) + assert.Equal(t, "required=requiredValue", values.Encode()) + }) + + t.Run("url ignored", func(t *testing.T) { + type example struct { + Required string `json:"required" url:"required"` + NotFound string `json:"notFound" url:"-"` + } + + values, err := QueryValues( + &example{ + Required: "requiredValue", + NotFound: "notFound", + }, + ) + require.NoError(t, err) + assert.Equal(t, "required=requiredValue", values.Encode()) + }) + + t.Run("datetime", func(t *testing.T) { + type example struct { + DateTime time.Time `json:"dateTime" url:"dateTime"` + } + + values, err := QueryValues( + &example{ + DateTime: time.Date(1994, 3, 16, 12, 34, 56, 0, time.UTC), + }, + ) + require.NoError(t, err) + assert.Equal(t, "dateTime=1994-03-16T12%3A34%3A56Z", values.Encode()) + }) + + t.Run("date", func(t *testing.T) { + type example struct { + Date time.Time `json:"date" url:"date" format:"date"` + } + + values, err := QueryValues( + &example{ + Date: time.Date(1994, 3, 16, 12, 34, 56, 0, time.UTC), + }, + ) + require.NoError(t, err) + assert.Equal(t, "date=1994-03-16", values.Encode()) + }) + + t.Run("optional time", func(t *testing.T) { + type example struct { + Date *time.Time `json:"date,omitempty" url:"date,omitempty" format:"date"` + } + + values, err := QueryValues( + &example{}, + ) + require.NoError(t, err) + assert.Empty(t, values.Encode()) + }) +} diff --git a/core/request_option.go b/core/request_option.go new file mode 100644 index 0000000..1bbf6e1 --- /dev/null +++ b/core/request_option.go @@ -0,0 +1,121 @@ +// This file was auto-generated by Fern from our API Definition. + +package core + +import ( + http "net/http" +) + +// RequestOption adapts the behavior of the client or an individual request. +type RequestOption interface { + applyRequestOptions(*RequestOptions) +} + +// RequestOptions defines all of the possible request options. +// +// This type is primarily used by the generated code and is not meant +// to be used directly; use the option package instead. +type RequestOptions struct { + BaseURL string + HTTPClient HTTPClient + HTTPHeader http.Header + MaxAttempts uint + AuthorizationToken string +} + +// NewRequestOptions returns a new *RequestOptions value. +// +// This function is primarily used by the generated code and is not meant +// to be used directly; use RequestOption instead. +func NewRequestOptions(opts ...RequestOption) *RequestOptions { + options := &RequestOptions{ + HTTPHeader: make(http.Header), + } + for _, opt := range opts { + opt.applyRequestOptions(options) + } + return options +} + +// ToHeader maps the configured request options into a http.Header used +// for the request(s). +func (r *RequestOptions) ToHeader() http.Header { + header := r.cloneHeader() + if r.AuthorizationToken != "" { + header.Set("Authorization", "Bearer "+r.AuthorizationToken) + } + return header +} + +func (r *RequestOptions) cloneHeader() http.Header { + headers := r.HTTPHeader.Clone() + headers.Set("X-Fern-Language", "Go") + headers.Set("X-Fern-SDK-Name", "github.com/trycourier/courier-go/v3") + headers.Set("X-Fern-SDK-Version", "v3.0.6") + return headers +} + +// BaseURLOption implements the RequestOption interface. +type BaseURLOption struct { + BaseURL string +} + +func (b *BaseURLOption) applyRequestOptions(opts *RequestOptions) { + opts.BaseURL = b.BaseURL +} + +func (b *BaseURLOption) applyIdempotentRequestOptions(opts *IdempotentRequestOptions) { + opts.BaseURL = b.BaseURL +} + +// HTTPClientOption implements the RequestOption interface. +type HTTPClientOption struct { + HTTPClient HTTPClient +} + +func (h *HTTPClientOption) applyRequestOptions(opts *RequestOptions) { + opts.HTTPClient = h.HTTPClient +} + +func (h *HTTPClientOption) applyIdempotentRequestOptions(opts *IdempotentRequestOptions) { + opts.HTTPClient = h.HTTPClient +} + +// HTTPHeaderOption implements the RequestOption interface. +type HTTPHeaderOption struct { + HTTPHeader http.Header +} + +func (h *HTTPHeaderOption) applyRequestOptions(opts *RequestOptions) { + opts.HTTPHeader = h.HTTPHeader +} + +func (h *HTTPHeaderOption) applyIdempotentRequestOptions(opts *IdempotentRequestOptions) { + opts.HTTPHeader = h.HTTPHeader +} + +// MaxAttemptsOption implements the RequestOption interface. +type MaxAttemptsOption struct { + MaxAttempts uint +} + +func (m *MaxAttemptsOption) applyRequestOptions(opts *RequestOptions) { + opts.MaxAttempts = m.MaxAttempts +} + +func (m *MaxAttemptsOption) applyIdempotentRequestOptions(opts *IdempotentRequestOptions) { + opts.MaxAttempts = m.MaxAttempts +} + +// AuthorizationTokenOption implements the RequestOption interface. +type AuthorizationTokenOption struct { + AuthorizationToken string +} + +func (a *AuthorizationTokenOption) applyRequestOptions(opts *RequestOptions) { + opts.AuthorizationToken = a.AuthorizationToken +} + +func (a *AuthorizationTokenOption) applyIdempotentRequestOptions(opts *IdempotentRequestOptions) { + opts.AuthorizationToken = a.AuthorizationToken +} diff --git a/core/retrier.go b/core/retrier.go new file mode 100644 index 0000000..ea24916 --- /dev/null +++ b/core/retrier.go @@ -0,0 +1,166 @@ +package core + +import ( + "crypto/rand" + "math/big" + "net/http" + "time" +) + +const ( + defaultRetryAttempts = 2 + minRetryDelay = 500 * time.Millisecond + maxRetryDelay = 5000 * time.Millisecond +) + +// RetryOption adapts the behavior the *Retrier. +type RetryOption func(*retryOptions) + +// RetryFunc is a retriable HTTP function call (i.e. *http.Client.Do). +type RetryFunc func(*http.Request) (*http.Response, error) + +// WithMaxAttempts configures the maximum number of attempts +// of the *Retrier. +func WithMaxAttempts(attempts uint) RetryOption { + return func(opts *retryOptions) { + opts.attempts = attempts + } +} + +// Retrier retries failed requests a configurable number of times with an +// exponential back-off between each retry. +type Retrier struct { + attempts uint +} + +// NewRetrier constructs a new *Retrier with the given options, if any. +func NewRetrier(opts ...RetryOption) *Retrier { + options := new(retryOptions) + for _, opt := range opts { + opt(options) + } + attempts := uint(defaultRetryAttempts) + if options.attempts > 0 { + attempts = options.attempts + } + return &Retrier{ + attempts: attempts, + } +} + +// Run issues the request and, upon failure, retries the request if possible. +// +// The request will be retried as long as the request is deemed retriable and the +// number of retry attempts has not grown larger than the configured retry limit. +func (r *Retrier) Run( + fn RetryFunc, + request *http.Request, + errorDecoder ErrorDecoder, + opts ...RetryOption, +) (*http.Response, error) { + options := new(retryOptions) + for _, opt := range opts { + opt(options) + } + maxRetryAttempts := r.attempts + if options.attempts > 0 { + maxRetryAttempts = options.attempts + } + var ( + retryAttempt uint + previousError error + ) + return r.run( + fn, + request, + errorDecoder, + maxRetryAttempts, + retryAttempt, + previousError, + ) +} + +func (r *Retrier) run( + fn RetryFunc, + request *http.Request, + errorDecoder ErrorDecoder, + maxRetryAttempts uint, + retryAttempt uint, + previousError error, +) (*http.Response, error) { + if retryAttempt >= maxRetryAttempts { + return nil, previousError + } + + // If the call has been cancelled, don't issue the request. + if err := request.Context().Err(); err != nil { + return nil, err + } + + response, err := fn(request) + if err != nil { + return nil, err + } + + if r.shouldRetry(response) { + defer response.Body.Close() + + delay, err := r.retryDelay(retryAttempt) + if err != nil { + return nil, err + } + + time.Sleep(delay) + + return r.run( + fn, + request, + errorDecoder, + maxRetryAttempts, + retryAttempt+1, + decodeError(response, errorDecoder), + ) + } + + return response, nil +} + +// shouldRetry returns true if the request should be retried based on the given +// response status code. +func (r *Retrier) shouldRetry(response *http.Response) bool { + return response.StatusCode == http.StatusTooManyRequests || + response.StatusCode == http.StatusRequestTimeout || + response.StatusCode == http.StatusConflict || + response.StatusCode >= http.StatusInternalServerError +} + +// retryDelay calculates the delay time in milliseconds based on the retry attempt. +func (r *Retrier) retryDelay(retryAttempt uint) (time.Duration, error) { + // Apply exponential backoff. + delay := minRetryDelay + minRetryDelay*time.Duration(retryAttempt*retryAttempt) + + // Do not allow the number to exceed maxRetryDelay. + if delay > maxRetryDelay { + delay = maxRetryDelay + } + + // Apply some itter by randomizing the value in the range of 75%-100%. + max := big.NewInt(int64(delay / 4)) + jitter, err := rand.Int(rand.Reader, max) + if err != nil { + return 0, err + } + + delay -= time.Duration(jitter.Int64()) + + // Never sleep less than the base sleep seconds. + if delay < minRetryDelay { + delay = minRetryDelay + } + + return delay, nil +} + +type retryOptions struct { + attempts uint +} diff --git a/core/time.go b/core/time.go new file mode 100644 index 0000000..d009ab3 --- /dev/null +++ b/core/time.go @@ -0,0 +1,137 @@ +package core + +import ( + "encoding/json" + "time" +) + +const dateFormat = "2006-01-02" + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date (e.g. 2006-01-02). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type Date struct { + t *time.Time +} + +// NewDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewDate(t time.Time) *Date { + return &Date{t: &t} +} + +// NewOptionalDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDate(t *time.Time) *Date { + if t == nil { + return nil + } + return &Date{t: t} +} + +// Time returns the Date's underlying time, if any. If the +// date is nil, the zero value is returned. +func (d *Date) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the Date's underlying time.Time, if any. +func (d *Date) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *Date) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(dateFormat)) +} + +func (d *Date) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(dateFormat, raw) + if err != nil { + return err + } + + *d = Date{t: &parsedTime} + return nil +} + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type DateTime struct { + t *time.Time +} + +// NewDateTime returns a new *DateTime. +func NewDateTime(t time.Time) *DateTime { + return &DateTime{t: &t} +} + +// NewOptionalDateTime returns a new *DateTime. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDateTime(t *time.Time) *DateTime { + if t == nil { + return nil + } + return &DateTime{t: t} +} + +// Time returns the DateTime's underlying time, if any. If the +// date-time is nil, the zero value is returned. +func (d *DateTime) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. +func (d *DateTime) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *DateTime) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(time.RFC3339)) +} + +func (d *DateTime) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(time.RFC3339, raw) + if err != nil { + return err + } + + *d = DateTime{t: &parsedTime} + return nil +} diff --git a/environments.go b/environments.go index d3a1370..44774dc 100644 --- a/environments.go +++ b/environments.go @@ -4,7 +4,7 @@ package api // Environments defines all of the API environments. // These values can be used with the WithBaseURL -// ClientOption to override the client's default environment, +// RequestOption to override the client's default environment, // if any. var Environments = struct { Production string diff --git a/go.mod b/go.mod index 558e4ef..19725bc 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/trycourier/courier-go/v3 go 1.13 require ( + github.com/google/uuid v1.4.0 github.com/stretchr/testify v1.7.0 gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index fc3dd9e..b3766d4 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= +github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= diff --git a/lists.go b/lists.go index 2e15fbf..60092a4 100644 --- a/lists.go +++ b/lists.go @@ -10,23 +10,23 @@ import ( type GetSubscriptionForListRequest struct { // A unique identifier that allows for fetching the next set of list subscriptions - Cursor *string `json:"-"` + Cursor *string `json:"-" url:"cursor,omitempty"` } type GetAllListsRequest struct { // A unique identifier that allows for fetching the next page of lists. - Cursor *string `json:"-"` + Cursor *string `json:"-" url:"cursor,omitempty"` // "A pattern used to filter the list items returned. Pattern types supported: exact match on `list_id` or a pattern of one or more pattern parts. you may replace a part with either: `*` to match all parts in that position, or `**` to signify a wildcard `endsWith` pattern match." - Pattern *string `json:"-"` + Pattern *string `json:"-" url:"pattern,omitempty"` } type SubscribeUserToListRequest struct { - Preferences *RecipientPreferences `json:"preferences,omitempty"` + Preferences *RecipientPreferences `json:"preferences,omitempty" url:"preferences,omitempty"` } type RecipientPreferences struct { - Categories *NotificationPreferences `json:"categories,omitempty"` - Notifications *NotificationPreferences `json:"notifications,omitempty"` + Categories *NotificationPreferences `json:"categories,omitempty" url:"categories,omitempty"` + Notifications *NotificationPreferences `json:"notifications,omitempty" url:"notifications,omitempty"` _rawJSON json.RawMessage } @@ -55,10 +55,10 @@ func (r *RecipientPreferences) String() string { } type List struct { - Id string `json:"id"` - Name string `json:"name"` - Created *int `json:"created,omitempty"` - Updated *int `json:"updated,omitempty"` + Id string `json:"id" url:"id"` + Name string `json:"name" url:"name"` + Created *int `json:"created,omitempty" url:"created,omitempty"` + Updated *int `json:"updated,omitempty" url:"updated,omitempty"` _rawJSON json.RawMessage } @@ -87,8 +87,8 @@ func (l *List) String() string { } type ListGetAllResponse struct { - Paging *Paging `json:"paging,omitempty"` - Items []*List `json:"items,omitempty"` + Paging *Paging `json:"paging,omitempty" url:"paging,omitempty"` + Items []*List `json:"items,omitempty" url:"items,omitempty"` _rawJSON json.RawMessage } @@ -117,8 +117,8 @@ func (l *ListGetAllResponse) String() string { } type ListGetSubscriptionsResponse struct { - Paging *Paging `json:"paging,omitempty"` - Items []*ListSubscriptionRecipient `json:"items,omitempty"` + Paging *Paging `json:"paging,omitempty" url:"paging,omitempty"` + Items []*ListSubscriptionRecipient `json:"items,omitempty" url:"items,omitempty"` _rawJSON json.RawMessage } @@ -147,8 +147,8 @@ func (l *ListGetSubscriptionsResponse) String() string { } type ListPutParams struct { - Name string `json:"name"` - Preferences *RecipientPreferences `json:"preferences,omitempty"` + Name string `json:"name" url:"name"` + Preferences *RecipientPreferences `json:"preferences,omitempty" url:"preferences,omitempty"` _rawJSON json.RawMessage } @@ -177,8 +177,8 @@ func (l *ListPutParams) String() string { } type PutSubscriptionsRecipient struct { - RecipientId string `json:"recipientId"` - Preferences *RecipientPreferences `json:"preferences,omitempty"` + RecipientId string `json:"recipientId" url:"recipientId"` + Preferences *RecipientPreferences `json:"preferences,omitempty" url:"preferences,omitempty"` _rawJSON json.RawMessage } diff --git a/lists/client.go b/lists/client.go index f468228..615eab5 100644 --- a/lists/client.go +++ b/lists/client.go @@ -10,9 +10,9 @@ import ( fmt "fmt" v3 "github.com/trycourier/courier-go/v3" core "github.com/trycourier/courier-go/v3/core" + option "github.com/trycourier/courier-go/v3/option" io "io" http "net/http" - url "net/url" ) type Client struct { @@ -21,37 +21,47 @@ type Client struct { header http.Header } -func NewClient(opts ...core.ClientOption) *Client { - options := core.NewClientOptions() - for _, opt := range opts { - opt(options) - } +func NewClient(opts ...option.RequestOption) *Client { + options := core.NewRequestOptions(opts...) return &Client{ baseURL: options.BaseURL, - caller: core.NewCaller(options.HTTPClient), - header: options.ToHeader(), + caller: core.NewCaller( + &core.CallerParams{ + Client: options.HTTPClient, + MaxAttempts: options.MaxAttempts, + }, + ), + header: options.ToHeader(), } } // Returns all of the lists, with the ability to filter based on a pattern. -func (c *Client) List(ctx context.Context, request *v3.GetAllListsRequest) (*v3.ListGetAllResponse, error) { +func (c *Client) List( + ctx context.Context, + request *v3.GetAllListsRequest, + opts ...option.RequestOption, +) (*v3.ListGetAllResponse, error) { + options := core.NewRequestOptions(opts...) + baseURL := "https://api.courier.com" if c.baseURL != "" { baseURL = c.baseURL } + if options.BaseURL != "" { + baseURL = options.BaseURL + } endpointURL := baseURL + "/" + "lists" - queryParams := make(url.Values) - if request.Cursor != nil { - queryParams.Add("cursor", fmt.Sprintf("%v", *request.Cursor)) - } - if request.Pattern != nil { - queryParams.Add("pattern", fmt.Sprintf("%v", *request.Pattern)) + queryParams, err := core.QueryValues(request) + if err != nil { + return nil, err } if len(queryParams) > 0 { endpointURL += "?" + queryParams.Encode() } + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + errorDecoder := func(statusCode int, body io.Reader) error { raw, err := io.ReadAll(body) if err != nil { @@ -77,7 +87,9 @@ func (c *Client) List(ctx context.Context, request *v3.GetAllListsRequest) (*v3. &core.CallParams{ URL: endpointURL, Method: http.MethodGet, - Headers: c.header, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, Response: &response, ErrorDecoder: errorDecoder, }, @@ -88,15 +100,25 @@ func (c *Client) List(ctx context.Context, request *v3.GetAllListsRequest) (*v3. } // Returns a list based on the list ID provided. -// -// A unique identifier representing the list you wish to retrieve. -func (c *Client) Get(ctx context.Context, listId string) (*v3.List, error) { +func (c *Client) Get( + ctx context.Context, + // A unique identifier representing the list you wish to retrieve. + listId string, + opts ...option.RequestOption, +) (*v3.List, error) { + options := core.NewRequestOptions(opts...) + baseURL := "https://api.courier.com" if c.baseURL != "" { baseURL = c.baseURL } + if options.BaseURL != "" { + baseURL = options.BaseURL + } endpointURL := fmt.Sprintf(baseURL+"/"+"lists/%v", listId) + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + errorDecoder := func(statusCode int, body io.Reader) error { raw, err := io.ReadAll(body) if err != nil { @@ -122,7 +144,9 @@ func (c *Client) Get(ctx context.Context, listId string) (*v3.List, error) { &core.CallParams{ URL: endpointURL, Method: http.MethodGet, - Headers: c.header, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, Response: &response, ErrorDecoder: errorDecoder, }, @@ -133,24 +157,37 @@ func (c *Client) Get(ctx context.Context, listId string) (*v3.List, error) { } // Create or replace an existing list with the supplied values. -// -// A unique identifier representing the list you wish to retrieve. -func (c *Client) Update(ctx context.Context, listId string, request *v3.ListPutParams) (*v3.List, error) { +func (c *Client) Update( + ctx context.Context, + // A unique identifier representing the list you wish to retrieve. + listId string, + request *v3.ListPutParams, + opts ...option.RequestOption, +) (*v3.List, error) { + options := core.NewRequestOptions(opts...) + baseURL := "https://api.courier.com" if c.baseURL != "" { baseURL = c.baseURL } + if options.BaseURL != "" { + baseURL = options.BaseURL + } endpointURL := fmt.Sprintf(baseURL+"/"+"lists/%v", listId) + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + var response *v3.List if err := c.caller.Call( ctx, &core.CallParams{ - URL: endpointURL, - Method: http.MethodPut, - Headers: c.header, - Request: request, - Response: &response, + URL: endpointURL, + Method: http.MethodPut, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, + Request: request, + Response: &response, }, ); err != nil { return nil, err @@ -159,21 +196,33 @@ func (c *Client) Update(ctx context.Context, listId string, request *v3.ListPutP } // Delete a list by list ID. -// -// A unique identifier representing the list you wish to retrieve. -func (c *Client) Delete(ctx context.Context, listId string) error { +func (c *Client) Delete( + ctx context.Context, + // A unique identifier representing the list you wish to retrieve. + listId string, + opts ...option.RequestOption, +) error { + options := core.NewRequestOptions(opts...) + baseURL := "https://api.courier.com" if c.baseURL != "" { baseURL = c.baseURL } + if options.BaseURL != "" { + baseURL = options.BaseURL + } endpointURL := fmt.Sprintf(baseURL+"/"+"lists/%v", listId) + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + if err := c.caller.Call( ctx, &core.CallParams{ - URL: endpointURL, - Method: http.MethodDelete, - Headers: c.header, + URL: endpointURL, + Method: http.MethodDelete, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, }, ); err != nil { return err @@ -182,21 +231,33 @@ func (c *Client) Delete(ctx context.Context, listId string) error { } // Restore a previously deleted list. -// -// A unique identifier representing the list you wish to retrieve. -func (c *Client) Restore(ctx context.Context, listId string) error { +func (c *Client) Restore( + ctx context.Context, + // A unique identifier representing the list you wish to retrieve. + listId string, + opts ...option.RequestOption, +) error { + options := core.NewRequestOptions(opts...) + baseURL := "https://api.courier.com" if c.baseURL != "" { baseURL = c.baseURL } + if options.BaseURL != "" { + baseURL = options.BaseURL + } endpointURL := fmt.Sprintf(baseURL+"/"+"lists/%v/restore", listId) + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + if err := c.caller.Call( ctx, &core.CallParams{ - URL: endpointURL, - Method: http.MethodPut, - Headers: c.header, + URL: endpointURL, + Method: http.MethodPut, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, }, ); err != nil { return err @@ -205,23 +266,34 @@ func (c *Client) Restore(ctx context.Context, listId string) error { } // Get the list's subscriptions. -// -// A unique identifier representing the list you wish to retrieve. -func (c *Client) GetSubscribers(ctx context.Context, listId string, request *v3.GetSubscriptionForListRequest) (*v3.ListGetSubscriptionsResponse, error) { +func (c *Client) GetSubscribers( + ctx context.Context, + // A unique identifier representing the list you wish to retrieve. + listId string, + request *v3.GetSubscriptionForListRequest, + opts ...option.RequestOption, +) (*v3.ListGetSubscriptionsResponse, error) { + options := core.NewRequestOptions(opts...) + baseURL := "https://api.courier.com" if c.baseURL != "" { baseURL = c.baseURL } + if options.BaseURL != "" { + baseURL = options.BaseURL + } endpointURL := fmt.Sprintf(baseURL+"/"+"lists/%v/subscriptions", listId) - queryParams := make(url.Values) - if request.Cursor != nil { - queryParams.Add("cursor", fmt.Sprintf("%v", *request.Cursor)) + queryParams, err := core.QueryValues(request) + if err != nil { + return nil, err } if len(queryParams) > 0 { endpointURL += "?" + queryParams.Encode() } + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + errorDecoder := func(statusCode int, body io.Reader) error { raw, err := io.ReadAll(body) if err != nil { @@ -247,7 +319,9 @@ func (c *Client) GetSubscribers(ctx context.Context, listId string, request *v3. &core.CallParams{ URL: endpointURL, Method: http.MethodGet, - Headers: c.header, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, Response: &response, ErrorDecoder: errorDecoder, }, @@ -258,15 +332,26 @@ func (c *Client) GetSubscribers(ctx context.Context, listId string, request *v3. } // Subscribes the users to the list, overwriting existing subscriptions. If the list does not exist, it will be automatically created. -// -// A unique identifier representing the list you wish to retrieve. -func (c *Client) UpdateSubscribers(ctx context.Context, listId string, request []*v3.PutSubscriptionsRecipient) error { +func (c *Client) UpdateSubscribers( + ctx context.Context, + // A unique identifier representing the list you wish to retrieve. + listId string, + request []*v3.PutSubscriptionsRecipient, + opts ...option.RequestOption, +) error { + options := core.NewRequestOptions(opts...) + baseURL := "https://api.courier.com" if c.baseURL != "" { baseURL = c.baseURL } + if options.BaseURL != "" { + baseURL = options.BaseURL + } endpointURL := fmt.Sprintf(baseURL+"/"+"lists/%v/subscriptions", listId) + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + errorDecoder := func(statusCode int, body io.Reader) error { raw, err := io.ReadAll(body) if err != nil { @@ -291,7 +376,9 @@ func (c *Client) UpdateSubscribers(ctx context.Context, listId string, request [ &core.CallParams{ URL: endpointURL, Method: http.MethodPut, - Headers: c.header, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, Request: request, ErrorDecoder: errorDecoder, }, @@ -302,15 +389,26 @@ func (c *Client) UpdateSubscribers(ctx context.Context, listId string, request [ } // Subscribes additional users to the list, without modifying existing subscriptions. If the list does not exist, it will be automatically created. -// -// A unique identifier representing the list you wish to retrieve. -func (c *Client) AddSubscribers(ctx context.Context, listId string, request []*v3.PutSubscriptionsRecipient) error { +func (c *Client) AddSubscribers( + ctx context.Context, + // A unique identifier representing the list you wish to retrieve. + listId string, + request []*v3.PutSubscriptionsRecipient, + opts ...option.IdempotentRequestOption, +) error { + options := core.NewIdempotentRequestOptions(opts...) + baseURL := "https://api.courier.com" if c.baseURL != "" { baseURL = c.baseURL } + if options.BaseURL != "" { + baseURL = options.BaseURL + } endpointURL := fmt.Sprintf(baseURL+"/"+"lists/%v/subscriptions", listId) + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + errorDecoder := func(statusCode int, body io.Reader) error { raw, err := io.ReadAll(body) if err != nil { @@ -335,7 +433,9 @@ func (c *Client) AddSubscribers(ctx context.Context, listId string, request []*v &core.CallParams{ URL: endpointURL, Method: http.MethodPost, - Headers: c.header, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, Request: request, ErrorDecoder: errorDecoder, }, @@ -346,23 +446,37 @@ func (c *Client) AddSubscribers(ctx context.Context, listId string, request []*v } // Subscribe a user to an existing list (note: if the List does not exist, it will be automatically created). -// -// A unique identifier representing the list you wish to retrieve. -// A unique identifier representing the recipient associated with the list -func (c *Client) Subscribe(ctx context.Context, listId string, userId string, request *v3.SubscribeUserToListRequest) error { +func (c *Client) Subscribe( + ctx context.Context, + // A unique identifier representing the list you wish to retrieve. + listId string, + // A unique identifier representing the recipient associated with the list + userId string, + request *v3.SubscribeUserToListRequest, + opts ...option.RequestOption, +) error { + options := core.NewRequestOptions(opts...) + baseURL := "https://api.courier.com" if c.baseURL != "" { baseURL = c.baseURL } + if options.BaseURL != "" { + baseURL = options.BaseURL + } endpointURL := fmt.Sprintf(baseURL+"/"+"lists/%v/subscriptions/%v", listId, userId) + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + if err := c.caller.Call( ctx, &core.CallParams{ - URL: endpointURL, - Method: http.MethodPut, - Headers: c.header, - Request: request, + URL: endpointURL, + Method: http.MethodPut, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, + Request: request, }, ); err != nil { return err @@ -371,16 +485,27 @@ func (c *Client) Subscribe(ctx context.Context, listId string, userId string, re } // Delete a subscription to a list by list ID and user ID. -// -// A unique identifier representing the list you wish to retrieve. -// A unique identifier representing the recipient associated with the list -func (c *Client) Unsubscribe(ctx context.Context, listId string, userId string) error { +func (c *Client) Unsubscribe( + ctx context.Context, + // A unique identifier representing the list you wish to retrieve. + listId string, + // A unique identifier representing the recipient associated with the list + userId string, + opts ...option.RequestOption, +) error { + options := core.NewRequestOptions(opts...) + baseURL := "https://api.courier.com" if c.baseURL != "" { baseURL = c.baseURL } + if options.BaseURL != "" { + baseURL = options.BaseURL + } endpointURL := fmt.Sprintf(baseURL+"/"+"lists/%v/subscriptions/%v", listId, userId) + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + errorDecoder := func(statusCode int, body io.Reader) error { raw, err := io.ReadAll(body) if err != nil { @@ -405,7 +530,9 @@ func (c *Client) Unsubscribe(ctx context.Context, listId string, userId string) &core.CallParams{ URL: endpointURL, Method: http.MethodDelete, - Headers: c.header, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, ErrorDecoder: errorDecoder, }, ); err != nil { diff --git a/messages.go b/messages.go index d43b8f1..a676209 100644 --- a/messages.go +++ b/messages.go @@ -10,39 +10,39 @@ import ( type GetMessageHistoryRequest struct { // A supported Message History type that will filter the events returned. - Type *string `json:"-"` + Type *string `json:"-" url:"type,omitempty"` } type ListMessagesRequest struct { // A boolean value that indicates whether archived messages should be included in the response. - Archived *bool `json:"-"` + Archived *bool `json:"-" url:"archived,omitempty"` // A unique identifier that allows for fetching the next set of message statuses. - Cursor *string `json:"-"` + Cursor *string `json:"-" url:"cursor,omitempty"` // A unique identifier representing the event that was used to send the event. - Event *string `json:"-"` + Event *string `json:"-" url:"event,omitempty"` // A unique identifier representing the list the message was sent to. - List *string `json:"-"` + List *string `json:"-" url:"list,omitempty"` // A unique identifier representing the message_id returned from either /send or /send/list. - MessageId *string `json:"-"` + MessageId *string `json:"-" url:"messageId,omitempty"` // A unique identifier representing the notification that was used to send the event. - Notification *string `json:"-"` + Notification *string `json:"-" url:"notification,omitempty"` // A unique identifier representing the recipient associated with the requested profile. - Recipient *string `json:"-"` + Recipient *string `json:"-" url:"recipient,omitempty"` // An indicator of the current status of the message. Multiple status values can be passed in. - Status []*string `json:"-"` + Status []*string `json:"-" url:"status,omitempty"` // A comma delimited list of 'tags'. Messages will be returned if they match any of the tags passed in. - Tags *string `json:"-"` + Tags *string `json:"-" url:"tags,omitempty"` // The enqueued datetime of a message to filter out messages received before. - EnqueuedAfter *string `json:"-"` + EnqueuedAfter *string `json:"-" url:"enqueued_after,omitempty"` // The unique identifier used to trace the requests - TraceId *string `json:"-"` + TraceId *string `json:"-" url:"traceId,omitempty"` } type ListMessagesResponse struct { // Paging information for the result set. - Paging *Paging `json:"paging,omitempty"` + Paging *Paging `json:"paging,omitempty" url:"paging,omitempty"` // An array of messages with their details. - Results []*MessageDetails `json:"results,omitempty"` + Results []*MessageDetails `json:"results,omitempty" url:"results,omitempty"` _rawJSON json.RawMessage } @@ -72,29 +72,29 @@ func (l *ListMessagesResponse) String() string { type MessageDetails struct { // A unique identifier associated with the message you wish to retrieve (results from a send). - Id string `json:"id"` + Id string `json:"id" url:"id"` // The current status of the message. - Status MessageStatus `json:"status,omitempty"` + Status MessageStatus `json:"status,omitempty" url:"status,omitempty"` // A UTC timestamp at which Courier received the message request. Stored as a millisecond representation of the Unix epoch. - Enqueued int `json:"enqueued"` + Enqueued int `json:"enqueued" url:"enqueued"` // A UTC timestamp at which Courier passed the message to the Integration provider. Stored as a millisecond representation of the Unix epoch. - Sent int `json:"sent"` + Sent int `json:"sent" url:"sent"` // A UTC timestamp at which the Integration provider delivered the message. Stored as a millisecond representation of the Unix epoch. - Delivered int `json:"delivered"` + Delivered int `json:"delivered" url:"delivered"` // A UTC timestamp at which the recipient opened a message for the first time. Stored as a millisecond representation of the Unix epoch. - Opened int `json:"opened"` + Opened int `json:"opened" url:"opened"` // A UTC timestamp at which the recipient clicked on a tracked link for the first time. Stored as a millisecond representation of the Unix epoch. - Clicked int `json:"clicked"` + Clicked int `json:"clicked" url:"clicked"` // A unique identifier associated with the recipient of the delivered message. - Recipient string `json:"recipient"` + Recipient string `json:"recipient" url:"recipient"` // A unique identifier associated with the event of the delivered message. - Event string `json:"event"` + Event string `json:"event" url:"event"` // A unique identifier associated with the notification of the delivered message. - Notification string `json:"notification"` + Notification string `json:"notification" url:"notification"` // A message describing the error that occurred. - Error *string `json:"error,omitempty"` + Error *string `json:"error,omitempty" url:"error,omitempty"` // The reason for the current status of the message. - Reason *Reason `json:"reason,omitempty"` + Reason *Reason `json:"reason,omitempty" url:"reason,omitempty"` _rawJSON json.RawMessage } @@ -123,7 +123,7 @@ func (m *MessageDetails) String() string { } type MessageHistoryResponse struct { - Results []*MessageDetails `json:"results,omitempty"` + Results []*MessageDetails `json:"results,omitempty" url:"results,omitempty"` _rawJSON json.RawMessage } @@ -153,7 +153,7 @@ func (m *MessageHistoryResponse) String() string { type RenderOutputResponse struct { // An array of render output of a previously sent message. - Results []*RenderOutput `json:"results,omitempty"` + Results []*RenderOutput `json:"results,omitempty" url:"results,omitempty"` _rawJSON json.RawMessage } diff --git a/messages/client.go b/messages/client.go index 9571d7f..ecf66c1 100644 --- a/messages/client.go +++ b/messages/client.go @@ -10,9 +10,9 @@ import ( fmt "fmt" v3 "github.com/trycourier/courier-go/v3" core "github.com/trycourier/courier-go/v3/core" + option "github.com/trycourier/courier-go/v3/option" io "io" http "net/http" - url "net/url" ) type Client struct { @@ -21,72 +21,57 @@ type Client struct { header http.Header } -func NewClient(opts ...core.ClientOption) *Client { - options := core.NewClientOptions() - for _, opt := range opts { - opt(options) - } +func NewClient(opts ...option.RequestOption) *Client { + options := core.NewRequestOptions(opts...) return &Client{ baseURL: options.BaseURL, - caller: core.NewCaller(options.HTTPClient), - header: options.ToHeader(), + caller: core.NewCaller( + &core.CallerParams{ + Client: options.HTTPClient, + MaxAttempts: options.MaxAttempts, + }, + ), + header: options.ToHeader(), } } // Fetch the statuses of messages you've previously sent. -func (c *Client) List(ctx context.Context, request *v3.ListMessagesRequest) (*v3.ListMessagesResponse, error) { +func (c *Client) List( + ctx context.Context, + request *v3.ListMessagesRequest, + opts ...option.RequestOption, +) (*v3.ListMessagesResponse, error) { + options := core.NewRequestOptions(opts...) + baseURL := "https://api.courier.com" if c.baseURL != "" { baseURL = c.baseURL } + if options.BaseURL != "" { + baseURL = options.BaseURL + } endpointURL := baseURL + "/" + "messages" - queryParams := make(url.Values) - if request.Archived != nil { - queryParams.Add("archived", fmt.Sprintf("%v", *request.Archived)) - } - if request.Cursor != nil { - queryParams.Add("cursor", fmt.Sprintf("%v", *request.Cursor)) - } - if request.Event != nil { - queryParams.Add("event", fmt.Sprintf("%v", *request.Event)) - } - if request.List != nil { - queryParams.Add("list", fmt.Sprintf("%v", *request.List)) - } - if request.MessageId != nil { - queryParams.Add("messageId", fmt.Sprintf("%v", *request.MessageId)) - } - if request.Notification != nil { - queryParams.Add("notification", fmt.Sprintf("%v", *request.Notification)) - } - if request.Recipient != nil { - queryParams.Add("recipient", fmt.Sprintf("%v", *request.Recipient)) - } - for _, value := range request.Status { - queryParams.Add("status", fmt.Sprintf("%v", *value)) - } - if request.Tags != nil { - queryParams.Add("tags", fmt.Sprintf("%v", *request.Tags)) - } - if request.EnqueuedAfter != nil { - queryParams.Add("enqueued_after", fmt.Sprintf("%v", *request.EnqueuedAfter)) - } - if request.TraceId != nil { - queryParams.Add("traceId", fmt.Sprintf("%v", *request.TraceId)) + queryParams, err := core.QueryValues(request) + if err != nil { + return nil, err } if len(queryParams) > 0 { endpointURL += "?" + queryParams.Encode() } + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + var response *v3.ListMessagesResponse if err := c.caller.Call( ctx, &core.CallParams{ - URL: endpointURL, - Method: http.MethodGet, - Headers: c.header, - Response: &response, + URL: endpointURL, + Method: http.MethodGet, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, + Response: &response, }, ); err != nil { return nil, err @@ -95,15 +80,25 @@ func (c *Client) List(ctx context.Context, request *v3.ListMessagesRequest) (*v3 } // Fetch the status of a message you've previously sent. -// -// A unique identifier associated with the message you wish to retrieve (results from a send). -func (c *Client) Get(ctx context.Context, messageId string) (*v3.MessageDetails, error) { +func (c *Client) Get( + ctx context.Context, + // A unique identifier associated with the message you wish to retrieve (results from a send). + messageId string, + opts ...option.RequestOption, +) (*v3.MessageDetails, error) { + options := core.NewRequestOptions(opts...) + baseURL := "https://api.courier.com" if c.baseURL != "" { baseURL = c.baseURL } + if options.BaseURL != "" { + baseURL = options.BaseURL + } endpointURL := fmt.Sprintf(baseURL+"/"+"messages/%v", messageId) + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + errorDecoder := func(statusCode int, body io.Reader) error { raw, err := io.ReadAll(body) if err != nil { @@ -136,7 +131,9 @@ func (c *Client) Get(ctx context.Context, messageId string) (*v3.MessageDetails, &core.CallParams{ URL: endpointURL, Method: http.MethodGet, - Headers: c.header, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, Response: &response, ErrorDecoder: errorDecoder, }, @@ -147,23 +144,35 @@ func (c *Client) Get(ctx context.Context, messageId string) (*v3.MessageDetails, } // Cancel a message that is currently in the process of being delivered. A well-formatted API call to the cancel message API will return either `200` status code for a successful cancellation or `409` status code for an unsuccessful cancellation. Both cases will include the actual message record in the response body (see details below). -// -// A unique identifier representing the message ID -func (c *Client) Cancel(ctx context.Context, messageId string) (*v3.MessageDetails, error) { +func (c *Client) Cancel( + ctx context.Context, + // A unique identifier representing the message ID + messageId string, + opts ...option.IdempotentRequestOption, +) (*v3.MessageDetails, error) { + options := core.NewIdempotentRequestOptions(opts...) + baseURL := "https://api.courier.com" if c.baseURL != "" { baseURL = c.baseURL } + if options.BaseURL != "" { + baseURL = options.BaseURL + } endpointURL := fmt.Sprintf(baseURL+"/"+"messages/%v/cancel", messageId) + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + var response *v3.MessageDetails if err := c.caller.Call( ctx, &core.CallParams{ - URL: endpointURL, - Method: http.MethodPost, - Headers: c.header, - Response: &response, + URL: endpointURL, + Method: http.MethodPost, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, + Response: &response, }, ); err != nil { return nil, err @@ -172,23 +181,34 @@ func (c *Client) Cancel(ctx context.Context, messageId string) (*v3.MessageDetai } // Fetch the array of events of a message you've previously sent. -// -// A unique identifier representing the message ID -func (c *Client) GetHistory(ctx context.Context, messageId string, request *v3.GetMessageHistoryRequest) (*v3.MessageHistoryResponse, error) { +func (c *Client) GetHistory( + ctx context.Context, + // A unique identifier representing the message ID + messageId string, + request *v3.GetMessageHistoryRequest, + opts ...option.RequestOption, +) (*v3.MessageHistoryResponse, error) { + options := core.NewRequestOptions(opts...) + baseURL := "https://api.courier.com" if c.baseURL != "" { baseURL = c.baseURL } + if options.BaseURL != "" { + baseURL = options.BaseURL + } endpointURL := fmt.Sprintf(baseURL+"/"+"messages/%v/history", messageId) - queryParams := make(url.Values) - if request.Type != nil { - queryParams.Add("type", fmt.Sprintf("%v", *request.Type)) + queryParams, err := core.QueryValues(request) + if err != nil { + return nil, err } if len(queryParams) > 0 { endpointURL += "?" + queryParams.Encode() } + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + errorDecoder := func(statusCode int, body io.Reader) error { raw, err := io.ReadAll(body) if err != nil { @@ -221,7 +241,9 @@ func (c *Client) GetHistory(ctx context.Context, messageId string, request *v3.G &core.CallParams{ URL: endpointURL, Method: http.MethodGet, - Headers: c.header, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, Response: &response, ErrorDecoder: errorDecoder, }, @@ -231,14 +253,25 @@ func (c *Client) GetHistory(ctx context.Context, messageId string, request *v3.G return response, nil } -// A unique identifier associated with the message you wish to retrieve (results from a send). -func (c *Client) GetContent(ctx context.Context, messageId string) (*v3.RenderOutputResponse, error) { +func (c *Client) GetContent( + ctx context.Context, + // A unique identifier associated with the message you wish to retrieve (results from a send). + messageId string, + opts ...option.RequestOption, +) (*v3.RenderOutputResponse, error) { + options := core.NewRequestOptions(opts...) + baseURL := "https://api.courier.com" if c.baseURL != "" { baseURL = c.baseURL } + if options.BaseURL != "" { + baseURL = options.BaseURL + } endpointURL := fmt.Sprintf(baseURL+"/"+"messages/%v/output", messageId) + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + errorDecoder := func(statusCode int, body io.Reader) error { raw, err := io.ReadAll(body) if err != nil { @@ -271,7 +304,9 @@ func (c *Client) GetContent(ctx context.Context, messageId string) (*v3.RenderOu &core.CallParams{ URL: endpointURL, Method: http.MethodGet, - Headers: c.header, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, Response: &response, ErrorDecoder: errorDecoder, }, @@ -281,20 +316,33 @@ func (c *Client) GetContent(ctx context.Context, messageId string) (*v3.RenderOu return response, nil } -// A unique identifier representing the request ID -func (c *Client) Archive(ctx context.Context, requestId string) error { +func (c *Client) Archive( + ctx context.Context, + // A unique identifier representing the request ID + requestId string, + opts ...option.RequestOption, +) error { + options := core.NewRequestOptions(opts...) + baseURL := "https://api.courier.com" if c.baseURL != "" { baseURL = c.baseURL } + if options.BaseURL != "" { + baseURL = options.BaseURL + } endpointURL := fmt.Sprintf(baseURL+"/"+"requests/%v/archive", requestId) + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + if err := c.caller.Call( ctx, &core.CallParams{ - URL: endpointURL, - Method: http.MethodPut, - Headers: c.header, + URL: endpointURL, + Method: http.MethodPut, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, }, ); err != nil { return err diff --git a/notifications.go b/notifications.go index d02b0e7..ba5010e 100644 --- a/notifications.go +++ b/notifications.go @@ -9,16 +9,16 @@ import ( ) type NotificationListParams struct { - Cursor *string `json:"-"` + Cursor *string `json:"-" url:"cursor,omitempty"` } type SubmissionChecksReplaceParams struct { - Checks []*BaseCheck `json:"checks,omitempty"` + Checks []*BaseCheck `json:"checks,omitempty" url:"checks,omitempty"` } type BaseCheck struct { - Id string `json:"id"` - Status CheckStatus `json:"status,omitempty"` + Id string `json:"id" url:"id"` + Status CheckStatus `json:"status,omitempty" url:"status,omitempty"` type_ string _rawJSON json.RawMessage @@ -29,12 +29,16 @@ func (b *BaseCheck) Type() string { } func (b *BaseCheck) UnmarshalJSON(data []byte) error { - type unmarshaler BaseCheck - var value unmarshaler - if err := json.Unmarshal(data, &value); err != nil { + type embed BaseCheck + var unmarshaler = struct { + embed + }{ + embed: embed(*b), + } + if err := json.Unmarshal(data, &unmarshaler); err != nil { return err } - *b = BaseCheck(value) + *b = BaseCheck(unmarshaler.embed) b.type_ = "custom" b._rawJSON = json.RawMessage(data) return nil @@ -65,13 +69,13 @@ func (b *BaseCheck) String() string { } type NotificationBlock struct { - Alias *string `json:"alias,omitempty"` - Context *string `json:"context,omitempty"` - Id string `json:"id"` - Type BlockType `json:"type,omitempty"` - Content *NotificationContent `json:"content,omitempty"` - Locales map[string]*NotificationContent `json:"locales,omitempty"` - Checksum *string `json:"checksum,omitempty"` + Alias *string `json:"alias,omitempty" url:"alias,omitempty"` + Context *string `json:"context,omitempty" url:"context,omitempty"` + Id string `json:"id" url:"id"` + Type BlockType `json:"type,omitempty" url:"type,omitempty"` + Content *NotificationContent `json:"content,omitempty" url:"content,omitempty"` + Locales map[string]*NotificationContent `json:"locales,omitempty" url:"locales,omitempty"` + Checksum *string `json:"checksum,omitempty" url:"checksum,omitempty"` _rawJSON json.RawMessage } @@ -100,11 +104,11 @@ func (n *NotificationBlock) String() string { } type NotificationChannel struct { - Id string `json:"id"` - Type *string `json:"type,omitempty"` - Content *NotificationChannelContent `json:"content,omitempty"` - Locales map[string]*NotificationChannelContent `json:"locales,omitempty"` - Checksum *string `json:"checksum,omitempty"` + Id string `json:"id" url:"id"` + Type *string `json:"type,omitempty" url:"type,omitempty"` + Content *NotificationChannelContent `json:"content,omitempty" url:"content,omitempty"` + Locales map[string]*NotificationChannelContent `json:"locales,omitempty" url:"locales,omitempty"` + Checksum *string `json:"checksum,omitempty" url:"checksum,omitempty"` _rawJSON json.RawMessage } @@ -133,9 +137,9 @@ func (n *NotificationChannel) String() string { } type NotificationGetContentResponse struct { - Blocks []*NotificationBlock `json:"blocks,omitempty"` - Channels []*NotificationChannel `json:"channels,omitempty"` - Checksum *string `json:"checksum,omitempty"` + Blocks []*NotificationBlock `json:"blocks,omitempty" url:"blocks,omitempty"` + Channels []*NotificationChannel `json:"channels,omitempty" url:"channels,omitempty"` + Checksum *string `json:"checksum,omitempty" url:"checksum,omitempty"` _rawJSON json.RawMessage } @@ -164,8 +168,8 @@ func (n *NotificationGetContentResponse) String() string { } type NotificationListResponse struct { - Paging *Paging `json:"paging,omitempty"` - Results []*Notification `json:"results,omitempty"` + Paging *Paging `json:"paging,omitempty" url:"paging,omitempty"` + Results []*Notification `json:"results,omitempty" url:"results,omitempty"` _rawJSON json.RawMessage } @@ -194,7 +198,7 @@ func (n *NotificationListResponse) String() string { } type SubmissionChecksGetResponse struct { - Checks []*Check `json:"checks,omitempty"` + Checks []*Check `json:"checks,omitempty" url:"checks,omitempty"` _rawJSON json.RawMessage } @@ -223,7 +227,7 @@ func (s *SubmissionChecksGetResponse) String() string { } type SubmissionChecksReplaceResponse struct { - Checks []*Check `json:"checks,omitempty"` + Checks []*Check `json:"checks,omitempty" url:"checks,omitempty"` _rawJSON json.RawMessage } @@ -252,11 +256,11 @@ func (s *SubmissionChecksReplaceResponse) String() string { } type NotificationDraftUpdateVariationsParams struct { - Blocks []*NotificationBlock `json:"blocks,omitempty"` - Channels []*NotificationChannel `json:"channels,omitempty"` + Blocks []*NotificationBlock `json:"blocks,omitempty" url:"blocks,omitempty"` + Channels []*NotificationChannel `json:"channels,omitempty" url:"channels,omitempty"` } type NotificationUpdateVariationsParams struct { - Blocks []*NotificationBlock `json:"blocks,omitempty"` - Channels []*NotificationChannel `json:"channels,omitempty"` + Blocks []*NotificationBlock `json:"blocks,omitempty" url:"blocks,omitempty"` + Channels []*NotificationChannel `json:"channels,omitempty" url:"channels,omitempty"` } diff --git a/notifications/client.go b/notifications/client.go index 6a6b2c4..22332f7 100644 --- a/notifications/client.go +++ b/notifications/client.go @@ -7,8 +7,8 @@ import ( fmt "fmt" v3 "github.com/trycourier/courier-go/v3" core "github.com/trycourier/courier-go/v3/core" + option "github.com/trycourier/courier-go/v3/option" http "net/http" - url "net/url" ) type Client struct { @@ -17,41 +17,56 @@ type Client struct { header http.Header } -func NewClient(opts ...core.ClientOption) *Client { - options := core.NewClientOptions() - for _, opt := range opts { - opt(options) - } +func NewClient(opts ...option.RequestOption) *Client { + options := core.NewRequestOptions(opts...) return &Client{ baseURL: options.BaseURL, - caller: core.NewCaller(options.HTTPClient), - header: options.ToHeader(), + caller: core.NewCaller( + &core.CallerParams{ + Client: options.HTTPClient, + MaxAttempts: options.MaxAttempts, + }, + ), + header: options.ToHeader(), } } -func (c *Client) List(ctx context.Context, request *v3.NotificationListParams) (*v3.NotificationListResponse, error) { +func (c *Client) List( + ctx context.Context, + request *v3.NotificationListParams, + opts ...option.RequestOption, +) (*v3.NotificationListResponse, error) { + options := core.NewRequestOptions(opts...) + baseURL := "https://api.courier.com" if c.baseURL != "" { baseURL = c.baseURL } + if options.BaseURL != "" { + baseURL = options.BaseURL + } endpointURL := baseURL + "/" + "notifications" - queryParams := make(url.Values) - if request.Cursor != nil { - queryParams.Add("cursor", fmt.Sprintf("%v", *request.Cursor)) + queryParams, err := core.QueryValues(request) + if err != nil { + return nil, err } if len(queryParams) > 0 { endpointURL += "?" + queryParams.Encode() } + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + var response *v3.NotificationListResponse if err := c.caller.Call( ctx, &core.CallParams{ - URL: endpointURL, - Method: http.MethodGet, - Headers: c.header, - Response: &response, + URL: endpointURL, + Method: http.MethodGet, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, + Response: &response, }, ); err != nil { return nil, err @@ -59,21 +74,34 @@ func (c *Client) List(ctx context.Context, request *v3.NotificationListParams) ( return response, nil } -func (c *Client) GetContent(ctx context.Context, id string) (*v3.NotificationGetContentResponse, error) { +func (c *Client) GetContent( + ctx context.Context, + id string, + opts ...option.RequestOption, +) (*v3.NotificationGetContentResponse, error) { + options := core.NewRequestOptions(opts...) + baseURL := "https://api.courier.com" if c.baseURL != "" { baseURL = c.baseURL } + if options.BaseURL != "" { + baseURL = options.BaseURL + } endpointURL := fmt.Sprintf(baseURL+"/"+"notifications/%v/content", id) + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + var response *v3.NotificationGetContentResponse if err := c.caller.Call( ctx, &core.CallParams{ - URL: endpointURL, - Method: http.MethodGet, - Headers: c.header, - Response: &response, + URL: endpointURL, + Method: http.MethodGet, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, + Response: &response, }, ); err != nil { return nil, err @@ -81,21 +109,34 @@ func (c *Client) GetContent(ctx context.Context, id string) (*v3.NotificationGet return response, nil } -func (c *Client) GetDraftContent(ctx context.Context, id string) (*v3.NotificationGetContentResponse, error) { +func (c *Client) GetDraftContent( + ctx context.Context, + id string, + opts ...option.RequestOption, +) (*v3.NotificationGetContentResponse, error) { + options := core.NewRequestOptions(opts...) + baseURL := "https://api.courier.com" if c.baseURL != "" { baseURL = c.baseURL } + if options.BaseURL != "" { + baseURL = options.BaseURL + } endpointURL := fmt.Sprintf(baseURL+"/"+"notifications/%v/draft/content", id) + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + var response *v3.NotificationGetContentResponse if err := c.caller.Call( ctx, &core.CallParams{ - URL: endpointURL, - Method: http.MethodGet, - Headers: c.header, - Response: &response, + URL: endpointURL, + Method: http.MethodGet, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, + Response: &response, }, ); err != nil { return nil, err @@ -103,20 +144,34 @@ func (c *Client) GetDraftContent(ctx context.Context, id string) (*v3.Notificati return response, nil } -func (c *Client) UpdateVariations(ctx context.Context, id string, request *v3.NotificationUpdateVariationsParams) error { +func (c *Client) UpdateVariations( + ctx context.Context, + id string, + request *v3.NotificationUpdateVariationsParams, + opts ...option.RequestOption, +) error { + options := core.NewRequestOptions(opts...) + baseURL := "https://api.courier.com" if c.baseURL != "" { baseURL = c.baseURL } + if options.BaseURL != "" { + baseURL = options.BaseURL + } endpointURL := fmt.Sprintf(baseURL+"/"+"notifications/%v/variations", id) + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + if err := c.caller.Call( ctx, &core.CallParams{ - URL: endpointURL, - Method: http.MethodPost, - Headers: c.header, - Request: request, + URL: endpointURL, + Method: http.MethodPost, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, + Request: request, }, ); err != nil { return err @@ -124,20 +179,34 @@ func (c *Client) UpdateVariations(ctx context.Context, id string, request *v3.No return nil } -func (c *Client) UpdateDraftVariations(ctx context.Context, id string, request *v3.NotificationDraftUpdateVariationsParams) error { +func (c *Client) UpdateDraftVariations( + ctx context.Context, + id string, + request *v3.NotificationDraftUpdateVariationsParams, + opts ...option.RequestOption, +) error { + options := core.NewRequestOptions(opts...) + baseURL := "https://api.courier.com" if c.baseURL != "" { baseURL = c.baseURL } + if options.BaseURL != "" { + baseURL = options.BaseURL + } endpointURL := fmt.Sprintf(baseURL+"/"+"notifications/%v/draft/variations", id) + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + if err := c.caller.Call( ctx, &core.CallParams{ - URL: endpointURL, - Method: http.MethodPost, - Headers: c.header, - Request: request, + URL: endpointURL, + Method: http.MethodPost, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, + Request: request, }, ); err != nil { return err @@ -145,21 +214,35 @@ func (c *Client) UpdateDraftVariations(ctx context.Context, id string, request * return nil } -func (c *Client) GetSubmissionChecks(ctx context.Context, id string, submissionId string) (*v3.SubmissionChecksGetResponse, error) { +func (c *Client) GetSubmissionChecks( + ctx context.Context, + id string, + submissionId string, + opts ...option.RequestOption, +) (*v3.SubmissionChecksGetResponse, error) { + options := core.NewRequestOptions(opts...) + baseURL := "https://api.courier.com" if c.baseURL != "" { baseURL = c.baseURL } + if options.BaseURL != "" { + baseURL = options.BaseURL + } endpointURL := fmt.Sprintf(baseURL+"/"+"notifications/%v/%v/checks", id, submissionId) + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + var response *v3.SubmissionChecksGetResponse if err := c.caller.Call( ctx, &core.CallParams{ - URL: endpointURL, - Method: http.MethodGet, - Headers: c.header, - Response: &response, + URL: endpointURL, + Method: http.MethodGet, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, + Response: &response, }, ); err != nil { return nil, err @@ -167,22 +250,37 @@ func (c *Client) GetSubmissionChecks(ctx context.Context, id string, submissionI return response, nil } -func (c *Client) ReplaceSubmissionChecks(ctx context.Context, id string, submissionId string, request *v3.SubmissionChecksReplaceParams) (*v3.SubmissionChecksReplaceResponse, error) { +func (c *Client) ReplaceSubmissionChecks( + ctx context.Context, + id string, + submissionId string, + request *v3.SubmissionChecksReplaceParams, + opts ...option.RequestOption, +) (*v3.SubmissionChecksReplaceResponse, error) { + options := core.NewRequestOptions(opts...) + baseURL := "https://api.courier.com" if c.baseURL != "" { baseURL = c.baseURL } + if options.BaseURL != "" { + baseURL = options.BaseURL + } endpointURL := fmt.Sprintf(baseURL+"/"+"notifications/%v/%v/checks", id, submissionId) + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + var response *v3.SubmissionChecksReplaceResponse if err := c.caller.Call( ctx, &core.CallParams{ - URL: endpointURL, - Method: http.MethodPut, - Headers: c.header, - Request: request, - Response: &response, + URL: endpointURL, + Method: http.MethodPut, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, + Request: request, + Response: &response, }, ); err != nil { return nil, err @@ -190,19 +288,33 @@ func (c *Client) ReplaceSubmissionChecks(ctx context.Context, id string, submiss return response, nil } -func (c *Client) CancelSubmission(ctx context.Context, id string, submissionId string) error { +func (c *Client) CancelSubmission( + ctx context.Context, + id string, + submissionId string, + opts ...option.RequestOption, +) error { + options := core.NewRequestOptions(opts...) + baseURL := "https://api.courier.com" if c.baseURL != "" { baseURL = c.baseURL } + if options.BaseURL != "" { + baseURL = options.BaseURL + } endpointURL := fmt.Sprintf(baseURL+"/"+"notifications/%v/%v/checks", id, submissionId) + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + if err := c.caller.Call( ctx, &core.CallParams{ - URL: endpointURL, - Method: http.MethodDelete, - Headers: c.header, + URL: endpointURL, + Method: http.MethodDelete, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, }, ); err != nil { return err diff --git a/option/idempotent_request_option.go b/option/idempotent_request_option.go new file mode 100644 index 0000000..8a21de8 --- /dev/null +++ b/option/idempotent_request_option.go @@ -0,0 +1,24 @@ +// This file was auto-generated by Fern from our API Definition. + +package option + +import ( + core "github.com/trycourier/courier-go/v3/core" +) + +// IdempotentRequestOption adapts the behavior of an indivdual request. +type IdempotentRequestOption = core.IdempotentRequestOption + +// WithIdempotencyKey sets the idempotencyKey request header. +func WithIdempotencyKey(idempotencyKey *string) *core.IdempotencyKeyOption { + return &core.IdempotencyKeyOption{ + IdempotencyKey: idempotencyKey, + } +} + +// WithIdempotencyExpiry sets the idempotencyExpiry request header. +func WithIdempotencyExpiry(idempotencyExpiry *int) *core.IdempotencyExpiryOption { + return &core.IdempotencyExpiryOption{ + IdempotencyExpiry: idempotencyExpiry, + } +} diff --git a/option/request_option.go b/option/request_option.go new file mode 100644 index 0000000..2facc11 --- /dev/null +++ b/option/request_option.go @@ -0,0 +1,48 @@ +// This file was auto-generated by Fern from our API Definition. + +package option + +import ( + core "github.com/trycourier/courier-go/v3/core" + http "net/http" +) + +// RequestOption adapts the behavior of an indivdual request. +type RequestOption = core.RequestOption + +// WithBaseURL sets the base URL, overriding the default +// environment, if any. +func WithBaseURL(baseURL string) *core.BaseURLOption { + return &core.BaseURLOption{ + BaseURL: baseURL, + } +} + +// WithHTTPClient uses the given HTTPClient to issue the request. +func WithHTTPClient(httpClient core.HTTPClient) *core.HTTPClientOption { + return &core.HTTPClientOption{ + HTTPClient: httpClient, + } +} + +// WithHTTPHeader adds the given http.Header to the request. +func WithHTTPHeader(httpHeader http.Header) *core.HTTPHeaderOption { + return &core.HTTPHeaderOption{ + // Clone the headers so they can't be modified after the option call. + HTTPHeader: httpHeader.Clone(), + } +} + +// WithMaxAttempts configures the maximum number of retry attempts. +func WithMaxAttempts(attempts uint) *core.MaxAttemptsOption { + return &core.MaxAttemptsOption{ + MaxAttempts: attempts, + } +} + +// WithAuthorizationToken sets the 'Authorization: Bearer ' request header. +func WithAuthorizationToken(authorizationToken string) *core.AuthorizationTokenOption { + return &core.AuthorizationTokenOption{ + AuthorizationToken: authorizationToken, + } +} diff --git a/profiles.go b/profiles.go index 10e95c9..51f8c22 100644 --- a/profiles.go +++ b/profiles.go @@ -9,16 +9,16 @@ import ( ) type MergeProfileRequest struct { - Profile map[string]interface{} `json:"profile,omitempty"` + Profile map[string]interface{} `json:"profile,omitempty" url:"profile,omitempty"` } type GetListSubscriptionsRequest struct { // A unique identifier that allows for fetching the next set of message statuses. - Cursor *string `json:"-"` + Cursor *string `json:"-" url:"cursor,omitempty"` } type ReplaceProfileRequest struct { - Profile map[string]interface{} `json:"profile,omitempty"` + Profile map[string]interface{} `json:"profile,omitempty" url:"profile,omitempty"` } type DeleteListSubscriptionResponse struct { @@ -32,12 +32,16 @@ func (d *DeleteListSubscriptionResponse) Status() string { } func (d *DeleteListSubscriptionResponse) UnmarshalJSON(data []byte) error { - type unmarshaler DeleteListSubscriptionResponse - var value unmarshaler - if err := json.Unmarshal(data, &value); err != nil { + type embed DeleteListSubscriptionResponse + var unmarshaler = struct { + embed + }{ + embed: embed(*d), + } + if err := json.Unmarshal(data, &unmarshaler); err != nil { return err } - *d = DeleteListSubscriptionResponse(value) + *d = DeleteListSubscriptionResponse(unmarshaler.embed) d.status = "SUCCESS" d._rawJSON = json.RawMessage(data) return nil @@ -68,9 +72,9 @@ func (d *DeleteListSubscriptionResponse) String() string { } type GetListSubscriptionsResponse struct { - Paging *Paging `json:"paging,omitempty"` + Paging *Paging `json:"paging,omitempty" url:"paging,omitempty"` // An array of lists - Results []*GetListSubscriptionsList `json:"results,omitempty"` + Results []*GetListSubscriptionsList `json:"results,omitempty" url:"results,omitempty"` _rawJSON json.RawMessage } @@ -109,12 +113,16 @@ func (m *MergeProfileResponse) Status() string { } func (m *MergeProfileResponse) UnmarshalJSON(data []byte) error { - type unmarshaler MergeProfileResponse - var value unmarshaler - if err := json.Unmarshal(data, &value); err != nil { + type embed MergeProfileResponse + var unmarshaler = struct { + embed + }{ + embed: embed(*m), + } + if err := json.Unmarshal(data, &unmarshaler); err != nil { return err } - *m = MergeProfileResponse(value) + *m = MergeProfileResponse(unmarshaler.embed) m.status = "SUCCESS" m._rawJSON = json.RawMessage(data) return nil @@ -145,8 +153,8 @@ func (m *MergeProfileResponse) String() string { } type ProfileGetResponse struct { - Profile map[string]interface{} `json:"profile,omitempty"` - Preferences *RecipientPreferences `json:"preferences,omitempty"` + Profile map[string]interface{} `json:"profile,omitempty" url:"profile,omitempty"` + Preferences *RecipientPreferences `json:"preferences,omitempty" url:"preferences,omitempty"` _rawJSON json.RawMessage } @@ -185,12 +193,16 @@ func (r *ReplaceProfileResponse) Status() string { } func (r *ReplaceProfileResponse) UnmarshalJSON(data []byte) error { - type unmarshaler ReplaceProfileResponse - var value unmarshaler - if err := json.Unmarshal(data, &value); err != nil { + type embed ReplaceProfileResponse + var unmarshaler = struct { + embed + }{ + embed: embed(*r), + } + if err := json.Unmarshal(data, &unmarshaler); err != nil { return err } - *r = ReplaceProfileResponse(value) + *r = ReplaceProfileResponse(unmarshaler.embed) r.status = "SUCCESS" r._rawJSON = json.RawMessage(data) return nil @@ -221,7 +233,7 @@ func (r *ReplaceProfileResponse) String() string { } type SubscribeToListsRequest struct { - Lists []*SubscribeToListsRequestListObject `json:"lists,omitempty"` + Lists []*SubscribeToListsRequestListObject `json:"lists,omitempty" url:"lists,omitempty"` _rawJSON json.RawMessage } @@ -260,12 +272,16 @@ func (s *SubscribeToListsResponse) Status() string { } func (s *SubscribeToListsResponse) UnmarshalJSON(data []byte) error { - type unmarshaler SubscribeToListsResponse - var value unmarshaler - if err := json.Unmarshal(data, &value); err != nil { + type embed SubscribeToListsResponse + var unmarshaler = struct { + embed + }{ + embed: embed(*s), + } + if err := json.Unmarshal(data, &unmarshaler); err != nil { return err } - *s = SubscribeToListsResponse(value) + *s = SubscribeToListsResponse(unmarshaler.embed) s.status = "SUCCESS" s._rawJSON = json.RawMessage(data) return nil diff --git a/profiles/client.go b/profiles/client.go index 6463fc6..bd1b047 100644 --- a/profiles/client.go +++ b/profiles/client.go @@ -10,9 +10,9 @@ import ( fmt "fmt" v3 "github.com/trycourier/courier-go/v3" core "github.com/trycourier/courier-go/v3/core" + option "github.com/trycourier/courier-go/v3/option" io "io" http "net/http" - url "net/url" ) type Client struct { @@ -21,28 +21,40 @@ type Client struct { header http.Header } -func NewClient(opts ...core.ClientOption) *Client { - options := core.NewClientOptions() - for _, opt := range opts { - opt(options) - } +func NewClient(opts ...option.RequestOption) *Client { + options := core.NewRequestOptions(opts...) return &Client{ baseURL: options.BaseURL, - caller: core.NewCaller(options.HTTPClient), - header: options.ToHeader(), + caller: core.NewCaller( + &core.CallerParams{ + Client: options.HTTPClient, + MaxAttempts: options.MaxAttempts, + }, + ), + header: options.ToHeader(), } } // Returns the specified user profile. -// -// A unique identifier representing the user associated with the requested profile. -func (c *Client) Get(ctx context.Context, userId string) (*v3.ProfileGetResponse, error) { +func (c *Client) Get( + ctx context.Context, + // A unique identifier representing the user associated with the requested profile. + userId string, + opts ...option.RequestOption, +) (*v3.ProfileGetResponse, error) { + options := core.NewRequestOptions(opts...) + baseURL := "https://api.courier.com" if c.baseURL != "" { baseURL = c.baseURL } + if options.BaseURL != "" { + baseURL = options.BaseURL + } endpointURL := fmt.Sprintf(baseURL+"/"+"profiles/%v", userId) + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + errorDecoder := func(statusCode int, body io.Reader) error { raw, err := io.ReadAll(body) if err != nil { @@ -68,7 +80,9 @@ func (c *Client) Get(ctx context.Context, userId string) (*v3.ProfileGetResponse &core.CallParams{ URL: endpointURL, Method: http.MethodGet, - Headers: c.header, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, Response: &response, ErrorDecoder: errorDecoder, }, @@ -79,15 +93,26 @@ func (c *Client) Get(ctx context.Context, userId string) (*v3.ProfileGetResponse } // Merge the supplied values with an existing profile or create a new profile if one doesn't already exist. -// -// A unique identifier representing the user associated with the requested profile. -func (c *Client) Create(ctx context.Context, userId string, request *v3.MergeProfileRequest) (*v3.MergeProfileResponse, error) { +func (c *Client) Create( + ctx context.Context, + // A unique identifier representing the user associated with the requested profile. + userId string, + request *v3.MergeProfileRequest, + opts ...option.IdempotentRequestOption, +) (*v3.MergeProfileResponse, error) { + options := core.NewIdempotentRequestOptions(opts...) + baseURL := "https://api.courier.com" if c.baseURL != "" { baseURL = c.baseURL } + if options.BaseURL != "" { + baseURL = options.BaseURL + } endpointURL := fmt.Sprintf(baseURL+"/"+"profiles/%v", userId) + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + errorDecoder := func(statusCode int, body io.Reader) error { raw, err := io.ReadAll(body) if err != nil { @@ -113,7 +138,9 @@ func (c *Client) Create(ctx context.Context, userId string, request *v3.MergePro &core.CallParams{ URL: endpointURL, Method: http.MethodPost, - Headers: c.header, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, Request: request, Response: &response, ErrorDecoder: errorDecoder, @@ -128,15 +155,26 @@ func (c *Client) Create(ctx context.Context, userId string, request *v3.MergePro // Any key-value pairs that exist in the profile but fail to be included in the `PUT` request will be // removed from the profile. Remember, a `PUT` update is a full replacement of the data. For partial updates, // use the [Patch](https://www.courier.com/docs/reference/profiles/patch/) request. -// -// A unique identifier representing the user associated with the requested profile. -func (c *Client) Replace(ctx context.Context, userId string, request *v3.ReplaceProfileRequest) (*v3.ReplaceProfileResponse, error) { +func (c *Client) Replace( + ctx context.Context, + // A unique identifier representing the user associated with the requested profile. + userId string, + request *v3.ReplaceProfileRequest, + opts ...option.RequestOption, +) (*v3.ReplaceProfileResponse, error) { + options := core.NewRequestOptions(opts...) + baseURL := "https://api.courier.com" if c.baseURL != "" { baseURL = c.baseURL } + if options.BaseURL != "" { + baseURL = options.BaseURL + } endpointURL := fmt.Sprintf(baseURL+"/"+"profiles/%v", userId) + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + errorDecoder := func(statusCode int, body io.Reader) error { raw, err := io.ReadAll(body) if err != nil { @@ -162,7 +200,9 @@ func (c *Client) Replace(ctx context.Context, userId string, request *v3.Replace &core.CallParams{ URL: endpointURL, Method: http.MethodPut, - Headers: c.header, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, Request: request, Response: &response, ErrorDecoder: errorDecoder, @@ -174,15 +214,25 @@ func (c *Client) Replace(ctx context.Context, userId string, request *v3.Replace } // Deletes the specified user profile. -// -// A unique identifier representing the user associated with the requested profile. -func (c *Client) Delete(ctx context.Context, userId string) error { +func (c *Client) Delete( + ctx context.Context, + // A unique identifier representing the user associated with the requested profile. + userId string, + opts ...option.RequestOption, +) error { + options := core.NewRequestOptions(opts...) + baseURL := "https://api.courier.com" if c.baseURL != "" { baseURL = c.baseURL } + if options.BaseURL != "" { + baseURL = options.BaseURL + } endpointURL := fmt.Sprintf(baseURL+"/"+"profiles/%v", userId) + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + errorDecoder := func(statusCode int, body io.Reader) error { raw, err := io.ReadAll(body) if err != nil { @@ -207,7 +257,9 @@ func (c *Client) Delete(ctx context.Context, userId string) error { &core.CallParams{ URL: endpointURL, Method: http.MethodDelete, - Headers: c.header, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, ErrorDecoder: errorDecoder, }, ); err != nil { @@ -217,23 +269,34 @@ func (c *Client) Delete(ctx context.Context, userId string) error { } // Returns the subscribed lists for a specified user. -// -// A unique identifier representing the user associated with the requested profile. -func (c *Client) GetListSubscriptions(ctx context.Context, userId string, request *v3.GetListSubscriptionsRequest) (*v3.GetListSubscriptionsResponse, error) { +func (c *Client) GetListSubscriptions( + ctx context.Context, + // A unique identifier representing the user associated with the requested profile. + userId string, + request *v3.GetListSubscriptionsRequest, + opts ...option.RequestOption, +) (*v3.GetListSubscriptionsResponse, error) { + options := core.NewRequestOptions(opts...) + baseURL := "https://api.courier.com" if c.baseURL != "" { baseURL = c.baseURL } + if options.BaseURL != "" { + baseURL = options.BaseURL + } endpointURL := fmt.Sprintf(baseURL+"/"+"profiles/%v/lists", userId) - queryParams := make(url.Values) - if request.Cursor != nil { - queryParams.Add("cursor", fmt.Sprintf("%v", *request.Cursor)) + queryParams, err := core.QueryValues(request) + if err != nil { + return nil, err } if len(queryParams) > 0 { endpointURL += "?" + queryParams.Encode() } + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + errorDecoder := func(statusCode int, body io.Reader) error { raw, err := io.ReadAll(body) if err != nil { @@ -259,7 +322,9 @@ func (c *Client) GetListSubscriptions(ctx context.Context, userId string, reques &core.CallParams{ URL: endpointURL, Method: http.MethodGet, - Headers: c.header, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, Response: &response, ErrorDecoder: errorDecoder, }, @@ -270,15 +335,26 @@ func (c *Client) GetListSubscriptions(ctx context.Context, userId string, reques } // Subscribes the given user to one or more lists. If the list does not exist, it will be created. -// -// A unique identifier representing the user associated with the requested profile. -func (c *Client) SubscribeToLists(ctx context.Context, userId string, request *v3.SubscribeToListsRequest) (*v3.SubscribeToListsResponse, error) { +func (c *Client) SubscribeToLists( + ctx context.Context, + // A unique identifier representing the user associated with the requested profile. + userId string, + request *v3.SubscribeToListsRequest, + opts ...option.IdempotentRequestOption, +) (*v3.SubscribeToListsResponse, error) { + options := core.NewIdempotentRequestOptions(opts...) + baseURL := "https://api.courier.com" if c.baseURL != "" { baseURL = c.baseURL } + if options.BaseURL != "" { + baseURL = options.BaseURL + } endpointURL := fmt.Sprintf(baseURL+"/"+"profiles/%v/lists", userId) + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + errorDecoder := func(statusCode int, body io.Reader) error { raw, err := io.ReadAll(body) if err != nil { @@ -304,7 +380,9 @@ func (c *Client) SubscribeToLists(ctx context.Context, userId string, request *v &core.CallParams{ URL: endpointURL, Method: http.MethodPost, - Headers: c.header, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, Request: request, Response: &response, ErrorDecoder: errorDecoder, @@ -316,15 +394,25 @@ func (c *Client) SubscribeToLists(ctx context.Context, userId string, request *v } // Removes all list subscriptions for given user. -// -// A unique identifier representing the user associated with the requested profile. -func (c *Client) DeleteListSubscription(ctx context.Context, userId string) (*v3.DeleteListSubscriptionResponse, error) { +func (c *Client) DeleteListSubscription( + ctx context.Context, + // A unique identifier representing the user associated with the requested profile. + userId string, + opts ...option.RequestOption, +) (*v3.DeleteListSubscriptionResponse, error) { + options := core.NewRequestOptions(opts...) + baseURL := "https://api.courier.com" if c.baseURL != "" { baseURL = c.baseURL } + if options.BaseURL != "" { + baseURL = options.BaseURL + } endpointURL := fmt.Sprintf(baseURL+"/"+"profiles/%v/lists", userId) + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + errorDecoder := func(statusCode int, body io.Reader) error { raw, err := io.ReadAll(body) if err != nil { @@ -350,7 +438,9 @@ func (c *Client) DeleteListSubscription(ctx context.Context, userId string) (*v3 &core.CallParams{ URL: endpointURL, Method: http.MethodDelete, - Headers: c.header, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, Response: &response, ErrorDecoder: errorDecoder, }, diff --git a/templates.go b/templates.go index 2cb564f..6e5f9f5 100644 --- a/templates.go +++ b/templates.go @@ -10,13 +10,13 @@ import ( type ListTemplatesRequest struct { // A unique identifier that allows for fetching the next set of templates - Cursor *string `json:"-"` + Cursor *string `json:"-" url:"cursor,omitempty"` } type ListTemplatesResponse struct { - Paging *Paging `json:"paging,omitempty"` + Paging *Paging `json:"paging,omitempty" url:"paging,omitempty"` // An array of Notification Templates - Results []*NotificationTemplates `json:"results,omitempty"` + Results []*NotificationTemplates `json:"results,omitempty" url:"results,omitempty"` _rawJSON json.RawMessage } diff --git a/templates/client.go b/templates/client.go index d88200d..e7fa0cc 100644 --- a/templates/client.go +++ b/templates/client.go @@ -4,11 +4,10 @@ package templates import ( context "context" - fmt "fmt" v3 "github.com/trycourier/courier-go/v3" core "github.com/trycourier/courier-go/v3/core" + option "github.com/trycourier/courier-go/v3/option" http "net/http" - url "net/url" ) type Client struct { @@ -17,42 +16,57 @@ type Client struct { header http.Header } -func NewClient(opts ...core.ClientOption) *Client { - options := core.NewClientOptions() - for _, opt := range opts { - opt(options) - } +func NewClient(opts ...option.RequestOption) *Client { + options := core.NewRequestOptions(opts...) return &Client{ baseURL: options.BaseURL, - caller: core.NewCaller(options.HTTPClient), - header: options.ToHeader(), + caller: core.NewCaller( + &core.CallerParams{ + Client: options.HTTPClient, + MaxAttempts: options.MaxAttempts, + }, + ), + header: options.ToHeader(), } } // Returns a list of notification templates -func (c *Client) List(ctx context.Context, request *v3.ListTemplatesRequest) (*v3.ListTemplatesResponse, error) { +func (c *Client) List( + ctx context.Context, + request *v3.ListTemplatesRequest, + opts ...option.RequestOption, +) (*v3.ListTemplatesResponse, error) { + options := core.NewRequestOptions(opts...) + baseURL := "https://api.courier.com" if c.baseURL != "" { baseURL = c.baseURL } + if options.BaseURL != "" { + baseURL = options.BaseURL + } endpointURL := baseURL + "/" + "notifications" - queryParams := make(url.Values) - if request.Cursor != nil { - queryParams.Add("cursor", fmt.Sprintf("%v", *request.Cursor)) + queryParams, err := core.QueryValues(request) + if err != nil { + return nil, err } if len(queryParams) > 0 { endpointURL += "?" + queryParams.Encode() } + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + var response *v3.ListTemplatesResponse if err := c.caller.Call( ctx, &core.CallParams{ - URL: endpointURL, - Method: http.MethodGet, - Headers: c.header, - Response: &response, + URL: endpointURL, + Method: http.MethodGet, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, + Response: &response, }, ); err != nil { return nil, err diff --git a/tenants.go b/tenants.go index 574cbe9..69a8952 100644 --- a/tenants.go +++ b/tenants.go @@ -10,37 +10,37 @@ import ( type TenantCreateOrReplaceParams struct { // Name of the tenant. - Name string `json:"name"` + Name string `json:"name" url:"name"` // Tenant's parent id (if any). - ParentTenantId *string `json:"parent_tenant_id,omitempty"` + ParentTenantId *string `json:"parent_tenant_id,omitempty" url:"parent_tenant_id,omitempty"` // Defines the preferences used for the tenant when the user hasn't specified their own. - DefaultPreferences *DefaultPreferences `json:"default_preferences,omitempty"` + DefaultPreferences *DefaultPreferences `json:"default_preferences,omitempty" url:"default_preferences,omitempty"` // Arbitrary properties accessible to a template. - Properties []TemplateProperty `json:"properties,omitempty"` + Properties []TemplateProperty `json:"properties,omitempty" url:"properties,omitempty"` // A user profile object merged with user profile on send. - UserProfile map[string]interface{} `json:"user_profile,omitempty"` + UserProfile map[string]interface{} `json:"user_profile,omitempty" url:"user_profile,omitempty"` // Brand to be used for the account when one is not specified by the send call. - BrandId *string `json:"brand_id,omitempty"` + BrandId *string `json:"brand_id,omitempty" url:"brand_id,omitempty"` } type ListUsersForTenantParams struct { // The number of accounts to return // (defaults to 20, maximum value of 100) - Limit *int `json:"-"` + Limit *int `json:"-" url:"limit,omitempty"` // Continue the pagination with the next cursor - Cursor *string `json:"-"` + Cursor *string `json:"-" url:"cursor,omitempty"` } type ListTenantParams struct { // The number of accousnts to return // (defaults to 20, maximum value of 100) - Limit *int `json:"-"` + Limit *int `json:"-" url:"limit,omitempty"` // Continue the pagination with the next cursor - Cursor *string `json:"-"` + Cursor *string `json:"-" url:"cursor,omitempty"` } type DefaultPreferences struct { - Items []*SubscriptionTopic `json:"items,omitempty"` + Items []*SubscriptionTopic `json:"items,omitempty" url:"items,omitempty"` _rawJSON json.RawMessage } @@ -69,17 +69,17 @@ func (d *DefaultPreferences) String() string { } type ListUsersForTenantResponse struct { - Items []*UserTenantAssociation `json:"items,omitempty"` + Items []*UserTenantAssociation `json:"items,omitempty" url:"items,omitempty"` // Set to true when there are more pages that can be retrieved. - HasMore bool `json:"has_more"` + HasMore bool `json:"has_more" url:"has_more"` // A url that may be used to generate these results. - Url string `json:"url"` + Url string `json:"url" url:"url"` // A url that may be used to generate fetch the next set of results. // Defined only when `has_more` is set to true - NextUrl *string `json:"next_url,omitempty"` + NextUrl *string `json:"next_url,omitempty" url:"next_url,omitempty"` // A pointer to the next page of results. Defined // only when `has_more` is set to true - Cursor *string `json:"cursor,omitempty"` + Cursor *string `json:"cursor,omitempty" url:"cursor,omitempty"` // Always set to `list`. Represents the type of this object. type_ string @@ -91,12 +91,16 @@ func (l *ListUsersForTenantResponse) Type() string { } func (l *ListUsersForTenantResponse) UnmarshalJSON(data []byte) error { - type unmarshaler ListUsersForTenantResponse - var value unmarshaler - if err := json.Unmarshal(data, &value); err != nil { + type embed ListUsersForTenantResponse + var unmarshaler = struct { + embed + }{ + embed: embed(*l), + } + if err := json.Unmarshal(data, &unmarshaler); err != nil { return err } - *l = ListUsersForTenantResponse(value) + *l = ListUsersForTenantResponse(unmarshaler.embed) l.type_ = "list" l._rawJSON = json.RawMessage(data) return nil @@ -130,19 +134,19 @@ type TemplateProperty = interface{} type Tenant struct { // Id of the tenant. - Id string `json:"id"` + Id string `json:"id" url:"id"` // Name of the tenant. - Name string `json:"name"` + Name string `json:"name" url:"name"` // Tenant's parent id (if any). - ParentTenantId *string `json:"parent_tenant_id,omitempty"` + ParentTenantId *string `json:"parent_tenant_id,omitempty" url:"parent_tenant_id,omitempty"` // Defines the preferences used for the account when the user hasn't specified their own. - DefaultPreferences *DefaultPreferences `json:"default_preferences,omitempty"` + DefaultPreferences *DefaultPreferences `json:"default_preferences,omitempty" url:"default_preferences,omitempty"` // Arbitrary properties accessible to a template. - Properties *TemplateProperty `json:"properties,omitempty"` + Properties *TemplateProperty `json:"properties,omitempty" url:"properties,omitempty"` // A user profile object merged with user profile on send. - UserProfile map[string]interface{} `json:"user_profile,omitempty"` + UserProfile map[string]interface{} `json:"user_profile,omitempty" url:"user_profile,omitempty"` // Brand to be used for the account when one is not specified by the send call. - BrandId *string `json:"brand_id,omitempty"` + BrandId *string `json:"brand_id,omitempty" url:"brand_id,omitempty"` _rawJSON json.RawMessage } @@ -172,16 +176,16 @@ func (t *Tenant) String() string { type TenantListResponse struct { // A pointer to the next page of results. Defined only when has_more is set to true. - Cursor *string `json:"cursor,omitempty"` + Cursor *string `json:"cursor,omitempty" url:"cursor,omitempty"` // Set to true when there are more pages that can be retrieved. - HasMore bool `json:"has_more"` + HasMore bool `json:"has_more" url:"has_more"` // An array of Tenants - Items []*Tenant `json:"items,omitempty"` + Items []*Tenant `json:"items,omitempty" url:"items,omitempty"` // A url that may be used to generate fetch the next set of results. // Defined only when has_more is set to true - NextUrl *string `json:"next_url,omitempty"` + NextUrl *string `json:"next_url,omitempty" url:"next_url,omitempty"` // A url that may be used to generate these results. - Url string `json:"url"` + Url string `json:"url" url:"url"` // Always set to "list". Represents the type of this object. type_ string @@ -193,12 +197,16 @@ func (t *TenantListResponse) Type() string { } func (t *TenantListResponse) UnmarshalJSON(data []byte) error { - type unmarshaler TenantListResponse - var value unmarshaler - if err := json.Unmarshal(data, &value); err != nil { + type embed TenantListResponse + var unmarshaler = struct { + embed + }{ + embed: embed(*t), + } + if err := json.Unmarshal(data, &unmarshaler); err != nil { return err } - *t = TenantListResponse(value) + *t = TenantListResponse(unmarshaler.embed) t.type_ = "list" t._rawJSON = json.RawMessage(data) return nil diff --git a/tenants/client.go b/tenants/client.go index d23a592..f389138 100644 --- a/tenants/client.go +++ b/tenants/client.go @@ -10,9 +10,9 @@ import ( fmt "fmt" v3 "github.com/trycourier/courier-go/v3" core "github.com/trycourier/courier-go/v3/core" + option "github.com/trycourier/courier-go/v3/option" io "io" http "net/http" - url "net/url" ) type Client struct { @@ -21,26 +21,40 @@ type Client struct { header http.Header } -func NewClient(opts ...core.ClientOption) *Client { - options := core.NewClientOptions() - for _, opt := range opts { - opt(options) - } +func NewClient(opts ...option.RequestOption) *Client { + options := core.NewRequestOptions(opts...) return &Client{ baseURL: options.BaseURL, - caller: core.NewCaller(options.HTTPClient), - header: options.ToHeader(), + caller: core.NewCaller( + &core.CallerParams{ + Client: options.HTTPClient, + MaxAttempts: options.MaxAttempts, + }, + ), + header: options.ToHeader(), } } -// A unique identifier representing the tenant to be returned. -func (c *Client) CreateOrReplace(ctx context.Context, tenantId string, request *v3.TenantCreateOrReplaceParams) (*v3.Tenant, error) { +func (c *Client) CreateOrReplace( + ctx context.Context, + // A unique identifier representing the tenant to be returned. + tenantId string, + request *v3.TenantCreateOrReplaceParams, + opts ...option.RequestOption, +) (*v3.Tenant, error) { + options := core.NewRequestOptions(opts...) + baseURL := "https://api.courier.com" if c.baseURL != "" { baseURL = c.baseURL } + if options.BaseURL != "" { + baseURL = options.BaseURL + } endpointURL := fmt.Sprintf(baseURL+"/"+"tenants/%v", tenantId) + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + errorDecoder := func(statusCode int, body io.Reader) error { raw, err := io.ReadAll(body) if err != nil { @@ -66,7 +80,9 @@ func (c *Client) CreateOrReplace(ctx context.Context, tenantId string, request * &core.CallParams{ URL: endpointURL, Method: http.MethodPut, - Headers: c.header, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, Request: request, Response: &response, ErrorDecoder: errorDecoder, @@ -77,14 +93,25 @@ func (c *Client) CreateOrReplace(ctx context.Context, tenantId string, request * return response, nil } -// A unique identifier representing the tenant to be returned. -func (c *Client) Get(ctx context.Context, tenantId string) (*v3.Tenant, error) { +func (c *Client) Get( + ctx context.Context, + // A unique identifier representing the tenant to be returned. + tenantId string, + opts ...option.RequestOption, +) (*v3.Tenant, error) { + options := core.NewRequestOptions(opts...) + baseURL := "https://api.courier.com" if c.baseURL != "" { baseURL = c.baseURL } + if options.BaseURL != "" { + baseURL = options.BaseURL + } endpointURL := fmt.Sprintf(baseURL+"/"+"tenants/%v", tenantId) + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + errorDecoder := func(statusCode int, body io.Reader) error { raw, err := io.ReadAll(body) if err != nil { @@ -110,7 +137,9 @@ func (c *Client) Get(ctx context.Context, tenantId string) (*v3.Tenant, error) { &core.CallParams{ URL: endpointURL, Method: http.MethodGet, - Headers: c.header, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, Response: &response, ErrorDecoder: errorDecoder, }, @@ -120,32 +149,42 @@ func (c *Client) Get(ctx context.Context, tenantId string) (*v3.Tenant, error) { return response, nil } -func (c *Client) List(ctx context.Context, request *v3.ListTenantParams) (*v3.TenantListResponse, error) { +func (c *Client) List( + ctx context.Context, + request *v3.ListTenantParams, + opts ...option.RequestOption, +) (*v3.TenantListResponse, error) { + options := core.NewRequestOptions(opts...) + baseURL := "https://api.courier.com" if c.baseURL != "" { baseURL = c.baseURL } + if options.BaseURL != "" { + baseURL = options.BaseURL + } endpointURL := baseURL + "/" + "tenants" - queryParams := make(url.Values) - if request.Limit != nil { - queryParams.Add("limit", fmt.Sprintf("%v", *request.Limit)) - } - if request.Cursor != nil { - queryParams.Add("cursor", fmt.Sprintf("%v", *request.Cursor)) + queryParams, err := core.QueryValues(request) + if err != nil { + return nil, err } if len(queryParams) > 0 { endpointURL += "?" + queryParams.Encode() } + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + var response *v3.TenantListResponse if err := c.caller.Call( ctx, &core.CallParams{ - URL: endpointURL, - Method: http.MethodGet, - Headers: c.header, - Response: &response, + URL: endpointURL, + Method: http.MethodGet, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, + Response: &response, }, ); err != nil { return nil, err @@ -153,20 +192,33 @@ func (c *Client) List(ctx context.Context, request *v3.ListTenantParams) (*v3.Te return response, nil } -// Id of the tenant to be deleted. -func (c *Client) Delete(ctx context.Context, tenantId string) error { +func (c *Client) Delete( + ctx context.Context, + // Id of the tenant to be deleted. + tenantId string, + opts ...option.RequestOption, +) error { + options := core.NewRequestOptions(opts...) + baseURL := "https://api.courier.com" if c.baseURL != "" { baseURL = c.baseURL } + if options.BaseURL != "" { + baseURL = options.BaseURL + } endpointURL := fmt.Sprintf(baseURL+"/"+"tenants/%v", tenantId) + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + if err := c.caller.Call( ctx, &core.CallParams{ - URL: endpointURL, - Method: http.MethodDelete, - Headers: c.header, + URL: endpointURL, + Method: http.MethodDelete, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, }, ); err != nil { return err @@ -174,25 +226,34 @@ func (c *Client) Delete(ctx context.Context, tenantId string) error { return nil } -// Id of the tenant for user membership. -func (c *Client) GetUsersByTenant(ctx context.Context, tenantId string, request *v3.ListUsersForTenantParams) (*v3.ListUsersForTenantResponse, error) { +func (c *Client) GetUsersByTenant( + ctx context.Context, + // Id of the tenant for user membership. + tenantId string, + request *v3.ListUsersForTenantParams, + opts ...option.RequestOption, +) (*v3.ListUsersForTenantResponse, error) { + options := core.NewRequestOptions(opts...) + baseURL := "https://api.courier.com" if c.baseURL != "" { baseURL = c.baseURL } + if options.BaseURL != "" { + baseURL = options.BaseURL + } endpointURL := fmt.Sprintf(baseURL+"/"+"tenants/%v/users", tenantId) - queryParams := make(url.Values) - if request.Limit != nil { - queryParams.Add("limit", fmt.Sprintf("%v", *request.Limit)) - } - if request.Cursor != nil { - queryParams.Add("cursor", fmt.Sprintf("%v", *request.Cursor)) + queryParams, err := core.QueryValues(request) + if err != nil { + return nil, err } if len(queryParams) > 0 { endpointURL += "?" + queryParams.Encode() } + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + errorDecoder := func(statusCode int, body io.Reader) error { raw, err := io.ReadAll(body) if err != nil { @@ -218,7 +279,9 @@ func (c *Client) GetUsersByTenant(ctx context.Context, tenantId string, request &core.CallParams{ URL: endpointURL, Method: http.MethodGet, - Headers: c.header, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, Response: &response, ErrorDecoder: errorDecoder, }, diff --git a/translations/client.go b/translations/client.go index 624d7f4..4cd0bf5 100644 --- a/translations/client.go +++ b/translations/client.go @@ -10,6 +10,7 @@ import ( fmt "fmt" v3 "github.com/trycourier/courier-go/v3" core "github.com/trycourier/courier-go/v3/core" + option "github.com/trycourier/courier-go/v3/option" io "io" http "net/http" ) @@ -20,29 +21,42 @@ type Client struct { header http.Header } -func NewClient(opts ...core.ClientOption) *Client { - options := core.NewClientOptions() - for _, opt := range opts { - opt(options) - } +func NewClient(opts ...option.RequestOption) *Client { + options := core.NewRequestOptions(opts...) return &Client{ baseURL: options.BaseURL, - caller: core.NewCaller(options.HTTPClient), - header: options.ToHeader(), + caller: core.NewCaller( + &core.CallerParams{ + Client: options.HTTPClient, + MaxAttempts: options.MaxAttempts, + }, + ), + header: options.ToHeader(), } } // Get translations by locale -// -// The domain you want to retrieve translations for. Only `default` is supported at the moment -// The locale you want to retrieve the translations for -func (c *Client) Get(ctx context.Context, domain string, locale string) (string, error) { +func (c *Client) Get( + ctx context.Context, + // The domain you want to retrieve translations for. Only `default` is supported at the moment + domain string, + // The locale you want to retrieve the translations for + locale string, + opts ...option.RequestOption, +) (string, error) { + options := core.NewRequestOptions(opts...) + baseURL := "https://api.courier.com" if c.baseURL != "" { baseURL = c.baseURL } + if options.BaseURL != "" { + baseURL = options.BaseURL + } endpointURL := fmt.Sprintf(baseURL+"/"+"translations/%v/%v", domain, locale) + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + errorDecoder := func(statusCode int, body io.Reader) error { raw, err := io.ReadAll(body) if err != nil { @@ -68,7 +82,9 @@ func (c *Client) Get(ctx context.Context, domain string, locale string) (string, &core.CallParams{ URL: endpointURL, Method: http.MethodGet, - Headers: c.header, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, Response: &response, ErrorDecoder: errorDecoder, }, @@ -79,16 +95,28 @@ func (c *Client) Get(ctx context.Context, domain string, locale string) (string, } // Update a translation -// -// The domain you want to retrieve translations for. Only `default` is supported at the moment -// The locale you want to retrieve the translations for -func (c *Client) Update(ctx context.Context, domain string, locale string, request string) error { +func (c *Client) Update( + ctx context.Context, + // The domain you want to retrieve translations for. Only `default` is supported at the moment + domain string, + // The locale you want to retrieve the translations for + locale string, + request string, + opts ...option.RequestOption, +) error { + options := core.NewRequestOptions(opts...) + baseURL := "https://api.courier.com" if c.baseURL != "" { baseURL = c.baseURL } + if options.BaseURL != "" { + baseURL = options.BaseURL + } endpointURL := fmt.Sprintf(baseURL+"/"+"translations/%v/%v", domain, locale) + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + errorDecoder := func(statusCode int, body io.Reader) error { raw, err := io.ReadAll(body) if err != nil { @@ -113,7 +141,9 @@ func (c *Client) Update(ctx context.Context, domain string, locale string, reque &core.CallParams{ URL: endpointURL, Method: http.MethodPut, - Headers: c.header, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, Request: request, ErrorDecoder: errorDecoder, }, diff --git a/types.go b/types.go index d8627f4..4f921af 100644 --- a/types.go +++ b/types.go @@ -10,7 +10,7 @@ import ( type SendMessageRequest struct { // Defines the message to be delivered - Message *Message `json:"message,omitempty"` + Message *Message `json:"message,omitempty" url:"message,omitempty"` } type SendMessageResponse struct { @@ -19,7 +19,7 @@ type SendMessageResponse struct { // For send requests that have a single recipient, the `requestId` is assigned to the derived message as its message_id. Therefore the `requestId` can be supplied to the Message's API for single recipient messages. // // For send requests that have multiple recipients (accounts, audiences, lists, etc.), Courier assigns a unique id to each derived message as its `message_id`. Therefore the `requestId` cannot be supplied to the Message's API for single-recipient messages. - RequestId string `json:"requestId"` + RequestId string `json:"requestId" url:"requestId"` _rawJSON json.RawMessage } @@ -48,11 +48,11 @@ func (s *SendMessageResponse) String() string { } type AudienceMember struct { - AddedAt string `json:"added_at"` - AudienceId string `json:"audience_id"` - AudienceVersion int `json:"audience_version"` - MemberId string `json:"member_id"` - Reason string `json:"reason"` + AddedAt string `json:"added_at" url:"added_at"` + AudienceId string `json:"audience_id" url:"audience_id"` + AudienceVersion int `json:"audience_version" url:"audience_version"` + MemberId string `json:"member_id" url:"member_id"` + Reason string `json:"reason" url:"reason"` _rawJSON json.RawMessage } @@ -81,7 +81,7 @@ func (a *AudienceMember) String() string { } type AudienceMemberGetResponse struct { - AudienceMember *AudienceMember `json:"audienceMember,omitempty"` + AudienceMember *AudienceMember `json:"audienceMember,omitempty" url:"audienceMember,omitempty"` _rawJSON json.RawMessage } @@ -111,7 +111,7 @@ func (a *AudienceMemberGetResponse) String() string { type BaseFilterConfig struct { // The operator to use for filtering - Operator *Operator `json:"operator,omitempty"` + Operator *Operator `json:"operator,omitempty" url:"operator,omitempty"` _rawJSON json.RawMessage } @@ -276,8 +276,8 @@ func (l LogicalOperator) Ptr() *LogicalOperator { // The operator to use for filtering type NestedFilterConfig struct { // The operator to use for filtering - Operator *Operator `json:"operator,omitempty"` - Rules []*FilterConfig `json:"rules,omitempty"` + Operator *Operator `json:"operator,omitempty" url:"operator,omitempty"` + Rules []*FilterConfig `json:"rules,omitempty" url:"rules,omitempty"` _rawJSON json.RawMessage } @@ -365,11 +365,11 @@ func (o *Operator) Accept(visitor OperatorVisitor) error { // A single filter to use for filtering type SingleFilterConfig struct { // The operator to use for filtering - Operator *Operator `json:"operator,omitempty"` + Operator *Operator `json:"operator,omitempty" url:"operator,omitempty"` // The value to use for filtering - Value string `json:"value"` + Value string `json:"value" url:"value"` // The attribe name from profile whose value will be operated against the filter value - Path string `json:"path"` + Path string `json:"path" url:"path"` _rawJSON json.RawMessage } @@ -398,8 +398,8 @@ func (s *SingleFilterConfig) String() string { } type Actor struct { - Id *string `json:"id,omitempty"` - Email *string `json:"email,omitempty"` + Id *string `json:"id,omitempty" url:"id,omitempty"` + Email *string `json:"email,omitempty" url:"email,omitempty"` _rawJSON json.RawMessage } @@ -428,7 +428,7 @@ func (a *Actor) String() string { } type GetAuditEventParams struct { - AuditEventId string `json:"auditEventId"` + AuditEventId string `json:"auditEventId" url:"auditEventId"` _rawJSON json.RawMessage } @@ -457,7 +457,7 @@ func (g *GetAuditEventParams) String() string { } type ListAuditEventsParams struct { - Cursor *string `json:"cursor,omitempty"` + Cursor *string `json:"cursor,omitempty" url:"cursor,omitempty"` _rawJSON json.RawMessage } @@ -486,8 +486,8 @@ func (l *ListAuditEventsParams) String() string { } type Target struct { - Id *string `json:"id,omitempty"` - Email *string `json:"email,omitempty"` + Id *string `json:"id,omitempty" url:"id,omitempty"` + Email *string `json:"email,omitempty" url:"email,omitempty"` _rawJSON json.RawMessage } @@ -516,8 +516,8 @@ func (t *Target) String() string { } type Automation struct { - CancelationToken *string `json:"cancelation_token,omitempty"` - Steps []*AutomationStepOption `json:"steps,omitempty"` + CancelationToken *string `json:"cancelation_token,omitempty" url:"cancelation_token,omitempty"` + Steps []*AutomationStepOption `json:"steps,omitempty" url:"steps,omitempty"` _rawJSON json.RawMessage } @@ -546,9 +546,9 @@ func (a *Automation) String() string { } type AutomationCancelStep struct { - If *string `json:"if,omitempty"` - Ref *string `json:"ref,omitempty"` - CancelationToken *string `json:"cancelation_token,omitempty"` + If *string `json:"if,omitempty" url:"if,omitempty"` + Ref *string `json:"ref,omitempty" url:"ref,omitempty"` + CancelationToken *string `json:"cancelation_token,omitempty" url:"cancelation_token,omitempty"` action string _rawJSON json.RawMessage @@ -559,12 +559,16 @@ func (a *AutomationCancelStep) Action() string { } func (a *AutomationCancelStep) UnmarshalJSON(data []byte) error { - type unmarshaler AutomationCancelStep - var value unmarshaler - if err := json.Unmarshal(data, &value); err != nil { + type embed AutomationCancelStep + var unmarshaler = struct { + embed + }{ + embed: embed(*a), + } + if err := json.Unmarshal(data, &unmarshaler); err != nil { return err } - *a = AutomationCancelStep(value) + *a = AutomationCancelStep(unmarshaler.embed) a.action = "cancel" a._rawJSON = json.RawMessage(data) return nil @@ -595,9 +599,9 @@ func (a *AutomationCancelStep) String() string { } type AutomationDelayStep struct { - If *string `json:"if,omitempty"` - Ref *string `json:"ref,omitempty"` - Until *string `json:"until,omitempty"` + If *string `json:"if,omitempty" url:"if,omitempty"` + Ref *string `json:"ref,omitempty" url:"ref,omitempty"` + Until *string `json:"until,omitempty" url:"until,omitempty"` action string _rawJSON json.RawMessage @@ -608,12 +612,16 @@ func (a *AutomationDelayStep) Action() string { } func (a *AutomationDelayStep) UnmarshalJSON(data []byte) error { - type unmarshaler AutomationDelayStep - var value unmarshaler - if err := json.Unmarshal(data, &value); err != nil { + type embed AutomationDelayStep + var unmarshaler = struct { + embed + }{ + embed: embed(*a), + } + if err := json.Unmarshal(data, &unmarshaler); err != nil { return err } - *a = AutomationDelayStep(value) + *a = AutomationDelayStep(unmarshaler.embed) a.action = "delay" a._rawJSON = json.RawMessage(data) return nil @@ -644,9 +652,9 @@ func (a *AutomationDelayStep) String() string { } type AutomationInvokeStep struct { - If *string `json:"if,omitempty"` - Ref *string `json:"ref,omitempty"` - Template string `json:"template"` + If *string `json:"if,omitempty" url:"if,omitempty"` + Ref *string `json:"ref,omitempty" url:"ref,omitempty"` + Template string `json:"template" url:"template"` action string _rawJSON json.RawMessage @@ -657,12 +665,16 @@ func (a *AutomationInvokeStep) Action() string { } func (a *AutomationInvokeStep) UnmarshalJSON(data []byte) error { - type unmarshaler AutomationInvokeStep - var value unmarshaler - if err := json.Unmarshal(data, &value); err != nil { + type embed AutomationInvokeStep + var unmarshaler = struct { + embed + }{ + embed: embed(*a), + } + if err := json.Unmarshal(data, &unmarshaler); err != nil { return err } - *a = AutomationInvokeStep(value) + *a = AutomationInvokeStep(unmarshaler.embed) a.action = "invoke" a._rawJSON = json.RawMessage(data) return nil @@ -693,12 +705,12 @@ func (a *AutomationInvokeStep) String() string { } type AutomationInvokeTemplateParams struct { - Brand *string `json:"brand,omitempty"` - Data map[string]interface{} `json:"data,omitempty"` - Profile *Profile `json:"profile,omitempty"` - Recipient *string `json:"recipient,omitempty"` - Template *string `json:"template,omitempty"` - TemplateId string `json:"templateId"` + Brand *string `json:"brand,omitempty" url:"brand,omitempty"` + Data map[string]interface{} `json:"data,omitempty" url:"data,omitempty"` + Profile *Profile `json:"profile,omitempty" url:"profile,omitempty"` + Recipient *string `json:"recipient,omitempty" url:"recipient,omitempty"` + Template *string `json:"template,omitempty" url:"template,omitempty"` + TemplateId string `json:"templateId" url:"templateId"` _rawJSON json.RawMessage } @@ -727,11 +739,11 @@ func (a *AutomationInvokeTemplateParams) String() string { } type AutomationRunContext struct { - Brand *string `json:"brand,omitempty"` - Data interface{} `json:"data,omitempty"` - Profile *Profile `json:"profile,omitempty"` - Template *string `json:"template,omitempty"` - Recipient *string `json:"recipient,omitempty"` + Brand *string `json:"brand,omitempty" url:"brand,omitempty"` + Data interface{} `json:"data,omitempty" url:"data,omitempty"` + Profile *Profile `json:"profile,omitempty" url:"profile,omitempty"` + Template *string `json:"template,omitempty" url:"template,omitempty"` + Recipient *string `json:"recipient,omitempty" url:"recipient,omitempty"` _rawJSON json.RawMessage } @@ -760,13 +772,13 @@ func (a *AutomationRunContext) String() string { } type AutomationSendListStep struct { - If *string `json:"if,omitempty"` - Ref *string `json:"ref,omitempty"` - Brand *string `json:"brand,omitempty"` - Data map[string]interface{} `json:"data,omitempty"` - List string `json:"list"` - Override map[string]interface{} `json:"override,omitempty"` - Template *string `json:"template,omitempty"` + If *string `json:"if,omitempty" url:"if,omitempty"` + Ref *string `json:"ref,omitempty" url:"ref,omitempty"` + Brand *string `json:"brand,omitempty" url:"brand,omitempty"` + Data map[string]interface{} `json:"data,omitempty" url:"data,omitempty"` + List string `json:"list" url:"list"` + Override map[string]interface{} `json:"override,omitempty" url:"override,omitempty"` + Template *string `json:"template,omitempty" url:"template,omitempty"` action string _rawJSON json.RawMessage @@ -777,12 +789,16 @@ func (a *AutomationSendListStep) Action() string { } func (a *AutomationSendListStep) UnmarshalJSON(data []byte) error { - type unmarshaler AutomationSendListStep - var value unmarshaler - if err := json.Unmarshal(data, &value); err != nil { + type embed AutomationSendListStep + var unmarshaler = struct { + embed + }{ + embed: embed(*a), + } + if err := json.Unmarshal(data, &unmarshaler); err != nil { return err } - *a = AutomationSendListStep(value) + *a = AutomationSendListStep(unmarshaler.embed) a.action = "send-list" a._rawJSON = json.RawMessage(data) return nil @@ -813,14 +829,14 @@ func (a *AutomationSendListStep) String() string { } type AutomationSendStep struct { - If *string `json:"if,omitempty"` - Ref *string `json:"ref,omitempty"` - Brand *string `json:"brand,omitempty"` - Data map[string]interface{} `json:"data,omitempty"` - Override map[string]interface{} `json:"override,omitempty"` - Profile interface{} `json:"profile,omitempty"` - Recipient *string `json:"recipient,omitempty"` - Template *string `json:"template,omitempty"` + If *string `json:"if,omitempty" url:"if,omitempty"` + Ref *string `json:"ref,omitempty" url:"ref,omitempty"` + Brand *string `json:"brand,omitempty" url:"brand,omitempty"` + Data map[string]interface{} `json:"data,omitempty" url:"data,omitempty"` + Override map[string]interface{} `json:"override,omitempty" url:"override,omitempty"` + Profile interface{} `json:"profile,omitempty" url:"profile,omitempty"` + Recipient *string `json:"recipient,omitempty" url:"recipient,omitempty"` + Template *string `json:"template,omitempty" url:"template,omitempty"` action string _rawJSON json.RawMessage @@ -831,12 +847,16 @@ func (a *AutomationSendStep) Action() string { } func (a *AutomationSendStep) UnmarshalJSON(data []byte) error { - type unmarshaler AutomationSendStep - var value unmarshaler - if err := json.Unmarshal(data, &value); err != nil { + type embed AutomationSendStep + var unmarshaler = struct { + embed + }{ + embed: embed(*a), + } + if err := json.Unmarshal(data, &unmarshaler); err != nil { return err } - *a = AutomationSendStep(value) + *a = AutomationSendStep(unmarshaler.embed) a.action = "send" a._rawJSON = json.RawMessage(data) return nil @@ -867,8 +887,8 @@ func (a *AutomationSendStep) String() string { } type AutomationStep struct { - If *string `json:"if,omitempty"` - Ref *string `json:"ref,omitempty"` + If *string `json:"if,omitempty" url:"if,omitempty"` + Ref *string `json:"ref,omitempty" url:"ref,omitempty"` _rawJSON json.RawMessage } @@ -1068,9 +1088,9 @@ func (a *AutomationStepOption) Accept(visitor AutomationStepOptionVisitor) error } type AutomationUpdateProfileStep struct { - RecipientId string `json:"recipient_id"` - Profile Profile `json:"profile,omitempty"` - Merge MergeAlgorithm `json:"merge,omitempty"` + RecipientId string `json:"recipient_id" url:"recipient_id"` + Profile Profile `json:"profile,omitempty" url:"profile,omitempty"` + Merge MergeAlgorithm `json:"merge,omitempty" url:"merge,omitempty"` action string _rawJSON json.RawMessage @@ -1081,12 +1101,16 @@ func (a *AutomationUpdateProfileStep) Action() string { } func (a *AutomationUpdateProfileStep) UnmarshalJSON(data []byte) error { - type unmarshaler AutomationUpdateProfileStep - var value unmarshaler - if err := json.Unmarshal(data, &value); err != nil { + type embed AutomationUpdateProfileStep + var unmarshaler = struct { + embed + }{ + embed: embed(*a), + } + if err := json.Unmarshal(data, &unmarshaler); err != nil { return err } - *a = AutomationUpdateProfileStep(value) + *a = AutomationUpdateProfileStep(unmarshaler.embed) a.action = "update-profile" a._rawJSON = json.RawMessage(data) return nil @@ -1117,9 +1141,9 @@ func (a *AutomationUpdateProfileStep) String() string { } type AutomationV2SendStep struct { - If *string `json:"if,omitempty"` - Ref *string `json:"ref,omitempty"` - Message *Message `json:"message,omitempty"` + If *string `json:"if,omitempty" url:"if,omitempty"` + Ref *string `json:"ref,omitempty" url:"ref,omitempty"` + Message *Message `json:"message,omitempty" url:"message,omitempty"` action string _rawJSON json.RawMessage @@ -1130,12 +1154,16 @@ func (a *AutomationV2SendStep) Action() string { } func (a *AutomationV2SendStep) UnmarshalJSON(data []byte) error { - type unmarshaler AutomationV2SendStep - var value unmarshaler - if err := json.Unmarshal(data, &value); err != nil { + type embed AutomationV2SendStep + var unmarshaler = struct { + embed + }{ + embed: embed(*a), + } + if err := json.Unmarshal(data, &unmarshaler); err != nil { return err } - *a = AutomationV2SendStep(value) + *a = AutomationV2SendStep(unmarshaler.embed) a.action = "send" a._rawJSON = json.RawMessage(data) return nil @@ -1196,9 +1224,9 @@ func (m MergeAlgorithm) Ptr() *MergeAlgorithm { type Profile = interface{} type BrandColors struct { - Primary *string `json:"primary,omitempty"` - Secondary *string `json:"secondary,omitempty"` - Tertiary *string `json:"tertiary,omitempty"` + Primary *string `json:"primary,omitempty" url:"primary,omitempty"` + Secondary *string `json:"secondary,omitempty" url:"secondary,omitempty"` + Tertiary *string `json:"tertiary,omitempty" url:"tertiary,omitempty"` _rawJSON json.RawMessage } @@ -1227,8 +1255,8 @@ func (b *BrandColors) String() string { } type BrandGetAllResponse struct { - Paging *Paging `json:"paging,omitempty"` - Results []*Brand `json:"results,omitempty"` + Paging *Paging `json:"paging,omitempty" url:"paging,omitempty"` + Results []*Brand `json:"results,omitempty" url:"results,omitempty"` _rawJSON json.RawMessage } @@ -1257,8 +1285,8 @@ func (b *BrandGetAllResponse) String() string { } type BrandSnippet struct { - Name string `json:"name"` - Value string `json:"value"` + Name string `json:"name" url:"name"` + Value string `json:"value" url:"value"` format string _rawJSON json.RawMessage @@ -1269,12 +1297,16 @@ func (b *BrandSnippet) Format() string { } func (b *BrandSnippet) UnmarshalJSON(data []byte) error { - type unmarshaler BrandSnippet - var value unmarshaler - if err := json.Unmarshal(data, &value); err != nil { + type embed BrandSnippet + var unmarshaler = struct { + embed + }{ + embed: embed(*b), + } + if err := json.Unmarshal(data, &unmarshaler); err != nil { return err } - *b = BrandSnippet(value) + *b = BrandSnippet(unmarshaler.embed) b.format = "handlebars" b._rawJSON = json.RawMessage(data) return nil @@ -1305,7 +1337,7 @@ func (b *BrandSnippet) String() string { } type BulkGetJobParams struct { - JobId string `json:"jobId"` + JobId string `json:"jobId" url:"jobId"` _rawJSON json.RawMessage } @@ -1334,8 +1366,8 @@ func (b *BulkGetJobParams) String() string { } type BulkGetJobUsersParams struct { - JobId string `json:"jobId"` - Cursor *string `json:"cursor,omitempty"` + JobId string `json:"jobId" url:"jobId"` + Cursor *string `json:"cursor,omitempty" url:"cursor,omitempty"` _rawJSON json.RawMessage } @@ -1364,8 +1396,8 @@ func (b *BulkGetJobUsersParams) String() string { } type BulkIngestError struct { - User interface{} `json:"user,omitempty"` - Error interface{} `json:"error,omitempty"` + User interface{} `json:"user,omitempty" url:"user,omitempty"` + Error interface{} `json:"error,omitempty" url:"error,omitempty"` _rawJSON json.RawMessage } @@ -1394,8 +1426,8 @@ func (b *BulkIngestError) String() string { } type BulkIngestUsersResponse struct { - Total int `json:"total"` - Errors []*BulkIngestError `json:"errors,omitempty"` + Total int `json:"total" url:"total"` + Errors []*BulkIngestError `json:"errors,omitempty" url:"errors,omitempty"` _rawJSON json.RawMessage } @@ -1477,13 +1509,13 @@ func (b BulkJobUserStatus) Ptr() *BulkJobUserStatus { } type BulkMessageUserResponse struct { - Preferences *RecipientPreferences `json:"preferences,omitempty"` - Profile interface{} `json:"profile,omitempty"` - Recipient *string `json:"recipient,omitempty"` - Data interface{} `json:"data,omitempty"` - To *UserRecipient `json:"to,omitempty"` - Status BulkJobUserStatus `json:"status,omitempty"` - MessageId *string `json:"messageId,omitempty"` + Preferences *RecipientPreferences `json:"preferences,omitempty" url:"preferences,omitempty"` + Profile interface{} `json:"profile,omitempty" url:"profile,omitempty"` + Recipient *string `json:"recipient,omitempty" url:"recipient,omitempty"` + Data interface{} `json:"data,omitempty" url:"data,omitempty"` + To *UserRecipient `json:"to,omitempty" url:"to,omitempty"` + Status BulkJobUserStatus `json:"status,omitempty" url:"status,omitempty"` + MessageId *string `json:"messageId,omitempty" url:"messageId,omitempty"` _rawJSON json.RawMessage } @@ -1516,27 +1548,27 @@ func (b *BulkMessageUserResponse) String() string { type InboundBulkContentMessage struct { // An arbitrary object that includes any data you want to pass to the message. // The data will populate the corresponding template or elements variables. - Data *MessageData `json:"data,omitempty"` - BrandId *string `json:"brand_id,omitempty"` + Data *MessageData `json:"data,omitempty" url:"data,omitempty"` + BrandId *string `json:"brand_id,omitempty" url:"brand_id,omitempty"` // "Define run-time configuration for one or more channels. If you don't specify channels, the default configuration for each channel will be used. Valid ChannelId's are: email, sms, push, inbox, direct_message, banner, and webhook." - Channels *MessageChannels `json:"channels,omitempty"` + Channels *MessageChannels `json:"channels,omitempty" url:"channels,omitempty"` // Context to load with this recipient. Will override any context set on message.context. - Context *MessageContext `json:"context,omitempty"` + Context *MessageContext `json:"context,omitempty" url:"context,omitempty"` // Metadata such as utm tracking attached with the notification through this channel. - Metadata *MessageMetadata `json:"metadata,omitempty"` + Metadata *MessageMetadata `json:"metadata,omitempty" url:"metadata,omitempty"` // An object whose keys are valid provider identifiers which map to an object. - Providers *MessageProviders `json:"providers,omitempty"` - Routing *Routing `json:"routing,omitempty"` + Providers *MessageProviders `json:"providers,omitempty" url:"providers,omitempty"` + Routing *Routing `json:"routing,omitempty" url:"routing,omitempty"` // Time in ms to attempt the channel before failing over to the next available channel. - Timeout *Timeout `json:"timeout,omitempty"` + Timeout *Timeout `json:"timeout,omitempty" url:"timeout,omitempty"` // Defines the time to wait before delivering the message. - Delay *Delay `json:"delay,omitempty"` + Delay *Delay `json:"delay,omitempty" url:"delay,omitempty"` // "Expiry allows you to set an absolute or relative time in which a message expires. // Note: This is only valid for the Courier Inbox channel as of 12-08-2022." - Expiry *Expiry `json:"expiry,omitempty"` + Expiry *Expiry `json:"expiry,omitempty" url:"expiry,omitempty"` // Describes the content of the message in a way that will work for email, push, // chat, or any channel. Either this or template must be specified. - Content *Content `json:"content,omitempty"` + Content *Content `json:"content,omitempty" url:"content,omitempty"` _rawJSON json.RawMessage } @@ -1565,11 +1597,11 @@ func (i *InboundBulkContentMessage) String() string { } type InboundBulkMessageUser struct { - Preferences *RecipientPreferences `json:"preferences,omitempty"` - Profile interface{} `json:"profile,omitempty"` - Recipient *string `json:"recipient,omitempty"` - Data interface{} `json:"data,omitempty"` - To *UserRecipient `json:"to,omitempty"` + Preferences *RecipientPreferences `json:"preferences,omitempty" url:"preferences,omitempty"` + Profile interface{} `json:"profile,omitempty" url:"profile,omitempty"` + Recipient *string `json:"recipient,omitempty" url:"recipient,omitempty"` + Data interface{} `json:"data,omitempty" url:"data,omitempty"` + To *UserRecipient `json:"to,omitempty" url:"to,omitempty"` _rawJSON json.RawMessage } @@ -1600,16 +1632,16 @@ func (i *InboundBulkMessageUser) String() string { type InboundBulkMessageV1 struct { // A unique identifier that represents the brand that should be used // for rendering the notification. - Brand *string `json:"brand,omitempty"` + Brand *string `json:"brand,omitempty" url:"brand,omitempty"` // JSON that includes any data you want to pass to a message template. // The data will populate the corresponding template variables. - Data map[string]interface{} `json:"data,omitempty"` - Event *string `json:"event,omitempty"` - Locale map[string]interface{} `json:"locale,omitempty"` + Data map[string]interface{} `json:"data,omitempty" url:"data,omitempty"` + Event *string `json:"event,omitempty" url:"event,omitempty"` + Locale map[string]interface{} `json:"locale,omitempty" url:"locale,omitempty"` // JSON that is merged into the request sent by Courier to the provider // to override properties or to gain access to features in the provider // API that are not natively supported by Courier. - Override interface{} `json:"override,omitempty"` + Override interface{} `json:"override,omitempty" url:"override,omitempty"` _rawJSON json.RawMessage } @@ -1702,27 +1734,27 @@ func (i *InboundBulkMessageV2) Accept(visitor InboundBulkMessageV2Visitor) error type InboundBulkTemplateMessage struct { // An arbitrary object that includes any data you want to pass to the message. // The data will populate the corresponding template or elements variables. - Data *MessageData `json:"data,omitempty"` - BrandId *string `json:"brand_id,omitempty"` + Data *MessageData `json:"data,omitempty" url:"data,omitempty"` + BrandId *string `json:"brand_id,omitempty" url:"brand_id,omitempty"` // "Define run-time configuration for one or more channels. If you don't specify channels, the default configuration for each channel will be used. Valid ChannelId's are: email, sms, push, inbox, direct_message, banner, and webhook." - Channels *MessageChannels `json:"channels,omitempty"` + Channels *MessageChannels `json:"channels,omitempty" url:"channels,omitempty"` // Context to load with this recipient. Will override any context set on message.context. - Context *MessageContext `json:"context,omitempty"` + Context *MessageContext `json:"context,omitempty" url:"context,omitempty"` // Metadata such as utm tracking attached with the notification through this channel. - Metadata *MessageMetadata `json:"metadata,omitempty"` + Metadata *MessageMetadata `json:"metadata,omitempty" url:"metadata,omitempty"` // An object whose keys are valid provider identifiers which map to an object. - Providers *MessageProviders `json:"providers,omitempty"` - Routing *Routing `json:"routing,omitempty"` + Providers *MessageProviders `json:"providers,omitempty" url:"providers,omitempty"` + Routing *Routing `json:"routing,omitempty" url:"routing,omitempty"` // Time in ms to attempt the channel before failing over to the next available channel. - Timeout *Timeout `json:"timeout,omitempty"` + Timeout *Timeout `json:"timeout,omitempty" url:"timeout,omitempty"` // Defines the time to wait before delivering the message. - Delay *Delay `json:"delay,omitempty"` + Delay *Delay `json:"delay,omitempty" url:"delay,omitempty"` // "Expiry allows you to set an absolute or relative time in which a message expires. // Note: This is only valid for the Courier Inbox channel as of 12-08-2022." - Expiry *Expiry `json:"expiry,omitempty"` + Expiry *Expiry `json:"expiry,omitempty" url:"expiry,omitempty"` // The id of the notification template to be rendered and sent to the recipient(s). // This field or the content field must be supplied. - Template string `json:"template"` + Template string `json:"template" url:"template"` _rawJSON json.RawMessage } @@ -1751,11 +1783,11 @@ func (i *InboundBulkTemplateMessage) String() string { } type JobDetails struct { - Definition *InboundBulkMessage `json:"definition,omitempty"` - Enqueued int `json:"enqueued"` - Failures int `json:"failures"` - Received int `json:"received"` - Status BulkJobStatus `json:"status,omitempty"` + Definition *InboundBulkMessage `json:"definition,omitempty" url:"definition,omitempty"` + Enqueued int `json:"enqueued" url:"enqueued"` + Failures int `json:"failures" url:"failures"` + Received int `json:"received" url:"received"` + Status BulkJobStatus `json:"status,omitempty" url:"status,omitempty"` _rawJSON json.RawMessage } @@ -1785,7 +1817,7 @@ func (j *JobDetails) String() string { type AlreadyExists struct { // A message describing the error that occurred. - Message string `json:"message"` + Message string `json:"message" url:"message"` type_ string _rawJSON json.RawMessage @@ -1796,12 +1828,16 @@ func (a *AlreadyExists) Type() string { } func (a *AlreadyExists) UnmarshalJSON(data []byte) error { - type unmarshaler AlreadyExists - var value unmarshaler - if err := json.Unmarshal(data, &value); err != nil { + type embed AlreadyExists + var unmarshaler = struct { + embed + }{ + embed: embed(*a), + } + if err := json.Unmarshal(data, &unmarshaler); err != nil { return err } - *a = AlreadyExists(value) + *a = AlreadyExists(unmarshaler.embed) a.type_ = "invalid_request_error" a._rawJSON = json.RawMessage(data) return nil @@ -1833,7 +1869,7 @@ func (a *AlreadyExists) String() string { type BadRequest struct { // A message describing the error that occurred. - Message string `json:"message"` + Message string `json:"message" url:"message"` type_ string _rawJSON json.RawMessage @@ -1844,12 +1880,16 @@ func (b *BadRequest) Type() string { } func (b *BadRequest) UnmarshalJSON(data []byte) error { - type unmarshaler BadRequest - var value unmarshaler - if err := json.Unmarshal(data, &value); err != nil { + type embed BadRequest + var unmarshaler = struct { + embed + }{ + embed: embed(*b), + } + if err := json.Unmarshal(data, &unmarshaler); err != nil { return err } - *b = BadRequest(value) + *b = BadRequest(unmarshaler.embed) b.type_ = "invalid_request_error" b._rawJSON = json.RawMessage(data) return nil @@ -1881,7 +1921,7 @@ func (b *BadRequest) String() string { type BaseError struct { // A message describing the error that occurred. - Message string `json:"message"` + Message string `json:"message" url:"message"` _rawJSON json.RawMessage } @@ -1944,7 +1984,7 @@ func (c ChannelClassification) Ptr() *ChannelClassification { } type ChannelPreference struct { - Channel ChannelClassification `json:"channel,omitempty"` + Channel ChannelClassification `json:"channel,omitempty" url:"channel,omitempty"` _rawJSON json.RawMessage } @@ -1974,7 +2014,7 @@ func (c *ChannelPreference) String() string { type Conflict struct { // A message describing the error that occurred. - Message string `json:"message"` + Message string `json:"message" url:"message"` type_ string _rawJSON json.RawMessage @@ -1985,12 +2025,16 @@ func (c *Conflict) Type() string { } func (c *Conflict) UnmarshalJSON(data []byte) error { - type unmarshaler Conflict - var value unmarshaler - if err := json.Unmarshal(data, &value); err != nil { + type embed Conflict + var unmarshaler = struct { + embed + }{ + embed: embed(*c), + } + if err := json.Unmarshal(data, &unmarshaler); err != nil { return err } - *c = Conflict(value) + *c = Conflict(unmarshaler.embed) c.type_ = "invalid_request_error" c._rawJSON = json.RawMessage(data) return nil @@ -2021,8 +2065,8 @@ func (c *Conflict) String() string { } type Email struct { - Footer interface{} `json:"footer,omitempty"` - Header interface{} `json:"header,omitempty"` + Footer interface{} `json:"footer,omitempty" url:"footer,omitempty"` + Header interface{} `json:"header,omitempty" url:"header,omitempty"` _rawJSON json.RawMessage } @@ -2052,7 +2096,7 @@ func (e *Email) String() string { type MessageNotFound struct { // A message describing the error that occurred. - Message string `json:"message"` + Message string `json:"message" url:"message"` type_ string _rawJSON json.RawMessage @@ -2063,12 +2107,16 @@ func (m *MessageNotFound) Type() string { } func (m *MessageNotFound) UnmarshalJSON(data []byte) error { - type unmarshaler MessageNotFound - var value unmarshaler - if err := json.Unmarshal(data, &value); err != nil { + type embed MessageNotFound + var unmarshaler = struct { + embed + }{ + embed: embed(*m), + } + if err := json.Unmarshal(data, &unmarshaler); err != nil { return err } - *m = MessageNotFound(value) + *m = MessageNotFound(unmarshaler.embed) m.type_ = "invalid_request_error" m._rawJSON = json.RawMessage(data) return nil @@ -2100,7 +2148,7 @@ func (m *MessageNotFound) String() string { type NotFound struct { // A message describing the error that occurred. - Message string `json:"message"` + Message string `json:"message" url:"message"` type_ string _rawJSON json.RawMessage @@ -2111,12 +2159,16 @@ func (n *NotFound) Type() string { } func (n *NotFound) UnmarshalJSON(data []byte) error { - type unmarshaler NotFound - var value unmarshaler - if err := json.Unmarshal(data, &value); err != nil { + type embed NotFound + var unmarshaler = struct { + embed + }{ + embed: embed(*n), + } + if err := json.Unmarshal(data, &unmarshaler); err != nil { return err } - *n = NotFound(value) + *n = NotFound(unmarshaler.embed) n.type_ = "invalid_request_error" n._rawJSON = json.RawMessage(data) return nil @@ -2147,9 +2199,9 @@ func (n *NotFound) String() string { } type NotificationPreferenceDetails struct { - Status PreferenceStatus `json:"status,omitempty"` - Rules []*Rule `json:"rules,omitempty"` - ChannelPreferences []*ChannelPreference `json:"channel_preferences,omitempty"` + Status PreferenceStatus `json:"status,omitempty" url:"status,omitempty"` + Rules []*Rule `json:"rules,omitempty" url:"rules,omitempty"` + ChannelPreferences []*ChannelPreference `json:"channel_preferences,omitempty" url:"channel_preferences,omitempty"` _rawJSON json.RawMessage } @@ -2180,8 +2232,8 @@ func (n *NotificationPreferenceDetails) String() string { type NotificationPreferences = map[string]*NotificationPreferenceDetails type Paging struct { - Cursor *string `json:"cursor,omitempty"` - More bool `json:"more"` + Cursor *string `json:"cursor,omitempty" url:"cursor,omitempty"` + More bool `json:"more" url:"more"` _rawJSON json.RawMessage } @@ -2211,7 +2263,7 @@ func (p *Paging) String() string { type PaymentRequired struct { // A message describing the error that occurred. - Message string `json:"message"` + Message string `json:"message" url:"message"` type_ string _rawJSON json.RawMessage @@ -2222,12 +2274,16 @@ func (p *PaymentRequired) Type() string { } func (p *PaymentRequired) UnmarshalJSON(data []byte) error { - type unmarshaler PaymentRequired - var value unmarshaler - if err := json.Unmarshal(data, &value); err != nil { + type embed PaymentRequired + var unmarshaler = struct { + embed + }{ + embed: embed(*p), + } + if err := json.Unmarshal(data, &unmarshaler); err != nil { return err } - *p = PaymentRequired(value) + *p = PaymentRequired(unmarshaler.embed) p.type_ = "authorization_error" p._rawJSON = json.RawMessage(data) return nil @@ -2283,8 +2339,8 @@ func (p PreferenceStatus) Ptr() *PreferenceStatus { } type Rule struct { - Start *string `json:"start,omitempty"` - Until string `json:"until"` + Start *string `json:"start,omitempty" url:"start,omitempty"` + Until string `json:"until" url:"until"` _rawJSON json.RawMessage } @@ -2314,10 +2370,10 @@ func (r *Rule) String() string { type UserTenantAssociation struct { // User ID for the assocation between tenant and user - UserId string `json:"user_id"` + UserId string `json:"user_id" url:"user_id"` // Tenant ID for the assocation between tenant and user - TenantId string `json:"tenant_id"` - Profile map[string]interface{} `json:"profile,omitempty"` + TenantId string `json:"tenant_id" url:"tenant_id"` + Profile map[string]interface{} `json:"profile,omitempty" url:"profile,omitempty"` type_ string _rawJSON json.RawMessage @@ -2328,12 +2384,16 @@ func (u *UserTenantAssociation) Type() string { } func (u *UserTenantAssociation) UnmarshalJSON(data []byte) error { - type unmarshaler UserTenantAssociation - var value unmarshaler - if err := json.Unmarshal(data, &value); err != nil { + type embed UserTenantAssociation + var unmarshaler = struct { + embed + }{ + embed: embed(*u), + } + if err := json.Unmarshal(data, &unmarshaler); err != nil { return err } - *u = UserTenantAssociation(value) + *u = UserTenantAssociation(unmarshaler.embed) u.type_ = "user" u._rawJSON = json.RawMessage(data) return nil @@ -2364,9 +2424,9 @@ func (u *UserTenantAssociation) String() string { } type ListSubscriptionRecipient struct { - RecipientId string `json:"recipientId"` - Created *string `json:"created,omitempty"` - Preferences *RecipientPreferences `json:"preferences,omitempty"` + RecipientId string `json:"recipientId" url:"recipientId"` + Created *string `json:"created,omitempty" url:"created,omitempty"` + Preferences *RecipientPreferences `json:"preferences,omitempty" url:"preferences,omitempty"` _rawJSON json.RawMessage } @@ -2488,11 +2548,11 @@ func (r Reason) Ptr() *Reason { type RenderOutput struct { // The channel used for rendering the message. - Channel string `json:"channel"` + Channel string `json:"channel" url:"channel"` // The ID of channel used for rendering the message. - ChannelId string `json:"channel_id"` + ChannelId string `json:"channel_id" url:"channel_id"` // Content details of the rendered message. - Content *RenderedMessageContent `json:"content,omitempty"` + Content *RenderedMessageContent `json:"content,omitempty" url:"content,omitempty"` _rawJSON json.RawMessage } @@ -2522,9 +2582,9 @@ func (r *RenderOutput) String() string { type RenderedMessageBlock struct { // The block type of the rendered message block. - Type string `json:"type"` + Type string `json:"type" url:"type"` // The block text of the rendered message block. - Text string `json:"text"` + Text string `json:"text" url:"text"` _rawJSON json.RawMessage } @@ -2554,17 +2614,17 @@ func (r *RenderedMessageBlock) String() string { type RenderedMessageContent struct { // The html content of the rendered message. - Html string `json:"html"` + Html string `json:"html" url:"html"` // The title of the rendered message. - Title string `json:"title"` + Title string `json:"title" url:"title"` // The body of the rendered message. - Body string `json:"body"` + Body string `json:"body" url:"body"` // The subject of the rendered message. - Subject string `json:"subject"` + Subject string `json:"subject" url:"subject"` // The text of the rendered message. - Text string `json:"text"` + Text string `json:"text" url:"text"` // The blocks of the rendered message. - Blocks []*RenderedMessageBlock `json:"blocks,omitempty"` + Blocks []*RenderedMessageBlock `json:"blocks,omitempty" url:"blocks,omitempty"` _rawJSON json.RawMessage } @@ -2636,9 +2696,9 @@ func (b BlockType) Ptr() *BlockType { } type Check struct { - Id string `json:"id"` - Status CheckStatus `json:"status,omitempty"` - Updated int64 `json:"updated"` + Id string `json:"id" url:"id"` + Status CheckStatus `json:"status,omitempty" url:"status,omitempty"` + Updated int64 `json:"updated" url:"updated"` type_ string _rawJSON json.RawMessage @@ -2649,12 +2709,16 @@ func (c *Check) Type() string { } func (c *Check) UnmarshalJSON(data []byte) error { - type unmarshaler Check - var value unmarshaler - if err := json.Unmarshal(data, &value); err != nil { + type embed Check + var unmarshaler = struct { + embed + }{ + embed: embed(*c), + } + if err := json.Unmarshal(data, &unmarshaler); err != nil { return err } - *c = Check(value) + *c = Check(unmarshaler.embed) c.type_ = "custom" c._rawJSON = json.RawMessage(data) return nil @@ -2710,8 +2774,8 @@ func (c CheckStatus) Ptr() *CheckStatus { } type MessageRouting struct { - Method MessageRoutingMethod `json:"method,omitempty"` - Channels []*MessageRoutingChannel `json:"channels,omitempty"` + Method MessageRoutingMethod `json:"method,omitempty" url:"method,omitempty"` + Channels []*MessageRoutingChannel `json:"channels,omitempty" url:"channels,omitempty"` _rawJSON json.RawMessage } @@ -2819,9 +2883,9 @@ func (m MessageRoutingMethod) Ptr() *MessageRoutingMethod { } type Notification struct { - CreatedAt int64 `json:"created_at"` - Id string `json:"id"` - Routing *MessageRouting `json:"routing,omitempty"` + CreatedAt int64 `json:"created_at" url:"created_at"` + Id string `json:"id" url:"id"` + Routing *MessageRouting `json:"routing,omitempty" url:"routing,omitempty"` _rawJSON json.RawMessage } @@ -2850,8 +2914,8 @@ func (n *Notification) String() string { } type NotificationChannelContent struct { - Subject *string `json:"subject,omitempty"` - Title *string `json:"title,omitempty"` + Subject *string `json:"subject,omitempty" url:"subject,omitempty"` + Title *string `json:"title,omitempty" url:"title,omitempty"` _rawJSON json.RawMessage } @@ -2937,8 +3001,8 @@ func (n *NotificationContent) Accept(visitor NotificationContentVisitor) error { } type NotificationContentHierarchy struct { - Parent *string `json:"parent,omitempty"` - Children *string `json:"children,omitempty"` + Parent *string `json:"parent,omitempty" url:"parent,omitempty"` + Children *string `json:"children,omitempty" url:"children,omitempty"` _rawJSON json.RawMessage } @@ -2967,12 +3031,12 @@ func (n *NotificationContentHierarchy) String() string { } type Address struct { - Formatted string `json:"formatted"` - StreetAddress string `json:"street_address"` - Locality string `json:"locality"` - Region string `json:"region"` - PostalCode string `json:"postal_code"` - Country string `json:"country"` + Formatted string `json:"formatted" url:"formatted"` + StreetAddress string `json:"street_address" url:"street_address"` + Locality string `json:"locality" url:"locality"` + Region string `json:"region" url:"region"` + PostalCode string `json:"postal_code" url:"postal_code"` + Country string `json:"country" url:"country"` _rawJSON json.RawMessage } @@ -3001,8 +3065,8 @@ func (a *Address) String() string { } type AirshipProfile struct { - Audience *AirshipProfileAudience `json:"audience,omitempty"` - DeviceTypes []DeviceType `json:"device_types,omitempty"` + Audience *AirshipProfileAudience `json:"audience,omitempty" url:"audience,omitempty"` + DeviceTypes []DeviceType `json:"device_types,omitempty" url:"device_types,omitempty"` _rawJSON json.RawMessage } @@ -3031,7 +3095,7 @@ func (a *AirshipProfile) String() string { } type AirshipProfileAudience struct { - NamedUser string `json:"named_user"` + NamedUser string `json:"named_user" url:"named_user"` _rawJSON json.RawMessage } @@ -3176,14 +3240,14 @@ func (e *Expo) Accept(visitor ExpoVisitor) error { } type GetListSubscriptionsList struct { - Id string `json:"id"` + Id string `json:"id" url:"id"` // List name - Name string `json:"name"` + Name string `json:"name" url:"name"` // The date/time of when the list was created. Represented as a string in ISO format. - Created string `json:"created"` + Created string `json:"created" url:"created"` // The date/time of when the list was updated. Represented as a string in ISO format. - Updated string `json:"updated"` - Preferences *RecipientPreferences `json:"preferences,omitempty"` + Updated string `json:"updated" url:"updated"` + Preferences *RecipientPreferences `json:"preferences,omitempty" url:"preferences,omitempty"` _rawJSON json.RawMessage } @@ -3212,8 +3276,8 @@ func (g *GetListSubscriptionsList) String() string { } type Intercom struct { - From string `json:"from"` - To *IntercomRecipient `json:"to,omitempty"` + From string `json:"from" url:"from"` + To *IntercomRecipient `json:"to,omitempty" url:"to,omitempty"` _rawJSON json.RawMessage } @@ -3242,7 +3306,7 @@ func (i *Intercom) String() string { } type IntercomRecipient struct { - Id string `json:"id"` + Id string `json:"id" url:"id"` _rawJSON json.RawMessage } @@ -3376,8 +3440,8 @@ func (m *MsTeams) Accept(visitor MsTeamsVisitor) error { } type MsTeamsBaseProperties struct { - TenantId string `json:"tenant_id"` - ServiceUrl string `json:"service_url"` + TenantId string `json:"tenant_id" url:"tenant_id"` + ServiceUrl string `json:"service_url" url:"service_url"` _rawJSON json.RawMessage } @@ -3406,7 +3470,7 @@ func (m *MsTeamsBaseProperties) String() string { } type MultipleTokens struct { - Tokens []*Token `json:"tokens,omitempty"` + Tokens []*Token `json:"tokens,omitempty" url:"tokens,omitempty"` _rawJSON json.RawMessage } @@ -3435,7 +3499,7 @@ func (m *MultipleTokens) String() string { } type ProfileGetParameters struct { - RecipientId string `json:"recipientId"` + RecipientId string `json:"recipientId" url:"recipientId"` _rawJSON json.RawMessage } @@ -3464,7 +3528,7 @@ func (p *ProfileGetParameters) String() string { } type SendDirectMessage struct { - UserId string `json:"user_id"` + UserId string `json:"user_id" url:"user_id"` _rawJSON json.RawMessage } @@ -3493,7 +3557,7 @@ func (s *SendDirectMessage) String() string { } type SendToChannel struct { - ChannelId string `json:"channel_id"` + ChannelId string `json:"channel_id" url:"channel_id"` _rawJSON json.RawMessage } @@ -3522,9 +3586,9 @@ func (s *SendToChannel) String() string { } type SendToMsTeamsChannelId struct { - TenantId string `json:"tenant_id"` - ServiceUrl string `json:"service_url"` - ChannelId string `json:"channel_id"` + TenantId string `json:"tenant_id" url:"tenant_id"` + ServiceUrl string `json:"service_url" url:"service_url"` + ChannelId string `json:"channel_id" url:"channel_id"` _rawJSON json.RawMessage } @@ -3553,10 +3617,10 @@ func (s *SendToMsTeamsChannelId) String() string { } type SendToMsTeamsChannelName struct { - TenantId string `json:"tenant_id"` - ServiceUrl string `json:"service_url"` - ChannelName string `json:"channel_name"` - TeamId string `json:"team_id"` + TenantId string `json:"tenant_id" url:"tenant_id"` + ServiceUrl string `json:"service_url" url:"service_url"` + ChannelName string `json:"channel_name" url:"channel_name"` + TeamId string `json:"team_id" url:"team_id"` _rawJSON json.RawMessage } @@ -3585,9 +3649,9 @@ func (s *SendToMsTeamsChannelName) String() string { } type SendToMsTeamsConversationId struct { - TenantId string `json:"tenant_id"` - ServiceUrl string `json:"service_url"` - ConversationId string `json:"conversation_id"` + TenantId string `json:"tenant_id" url:"tenant_id"` + ServiceUrl string `json:"service_url" url:"service_url"` + ConversationId string `json:"conversation_id" url:"conversation_id"` _rawJSON json.RawMessage } @@ -3616,9 +3680,9 @@ func (s *SendToMsTeamsConversationId) String() string { } type SendToMsTeamsEmail struct { - TenantId string `json:"tenant_id"` - ServiceUrl string `json:"service_url"` - Email string `json:"email"` + TenantId string `json:"tenant_id" url:"tenant_id"` + ServiceUrl string `json:"service_url" url:"service_url"` + Email string `json:"email" url:"email"` _rawJSON json.RawMessage } @@ -3647,9 +3711,9 @@ func (s *SendToMsTeamsEmail) String() string { } type SendToMsTeamsUserId struct { - TenantId string `json:"tenant_id"` - ServiceUrl string `json:"service_url"` - UserId string `json:"user_id"` + TenantId string `json:"tenant_id" url:"tenant_id"` + ServiceUrl string `json:"service_url" url:"service_url"` + UserId string `json:"user_id" url:"user_id"` _rawJSON json.RawMessage } @@ -3678,8 +3742,8 @@ func (s *SendToMsTeamsUserId) String() string { } type SendToSlackChannel struct { - AccessToken string `json:"access_token"` - Channel string `json:"channel"` + AccessToken string `json:"access_token" url:"access_token"` + Channel string `json:"channel" url:"channel"` _rawJSON json.RawMessage } @@ -3708,8 +3772,8 @@ func (s *SendToSlackChannel) String() string { } type SendToSlackEmail struct { - AccessToken string `json:"access_token"` - Email string `json:"email"` + AccessToken string `json:"access_token" url:"access_token"` + Email string `json:"email" url:"email"` _rawJSON json.RawMessage } @@ -3738,8 +3802,8 @@ func (s *SendToSlackEmail) String() string { } type SendToSlackUserId struct { - AccessToken string `json:"access_token"` - UserId string `json:"user_id"` + AccessToken string `json:"access_token" url:"access_token"` + UserId string `json:"user_id" url:"user_id"` _rawJSON json.RawMessage } @@ -3841,7 +3905,7 @@ func (s *Slack) Accept(visitor SlackVisitor) error { } type SlackBaseProperties struct { - AccessToken string `json:"access_token"` + AccessToken string `json:"access_token" url:"access_token"` _rawJSON json.RawMessage } @@ -3870,9 +3934,9 @@ func (s *SlackBaseProperties) String() string { } type SnoozeRule struct { - Type SnoozeRuleType `json:"type,omitempty"` - Start string `json:"start"` - Until string `json:"until"` + Type SnoozeRuleType `json:"type,omitempty" url:"type,omitempty"` + Start string `json:"start" url:"start"` + Until string `json:"until" url:"until"` _rawJSON json.RawMessage } @@ -3920,8 +3984,8 @@ func (s SnoozeRuleType) Ptr() *SnoozeRuleType { } type SubscribeToListsRequestListObject struct { - ListId string `json:"listId"` - Preferences *RecipientPreferences `json:"preferences,omitempty"` + ListId string `json:"listId" url:"listId"` + Preferences *RecipientPreferences `json:"preferences,omitempty" url:"preferences,omitempty"` _rawJSON json.RawMessage } @@ -3950,7 +4014,7 @@ func (s *SubscribeToListsRequestListObject) String() string { } type Token struct { - Token string `json:"token"` + Token string `json:"token" url:"token"` _rawJSON json.RawMessage } @@ -3979,38 +4043,38 @@ func (t *Token) String() string { } type UserProfile struct { - Address *Address `json:"address,omitempty"` - Birthdate string `json:"birthdate"` - Email string `json:"email"` - EmailVerified bool `json:"email_verified"` - FamilyName string `json:"family_name"` - Gender string `json:"gender"` - GivenName string `json:"given_name"` - Locale string `json:"locale"` - MiddleName string `json:"middle_name"` - Name string `json:"name"` - Nickname string `json:"nickname"` - PhoneNumber string `json:"phone_number"` - PhoneNumberVerified bool `json:"phone_number_verified"` - Picture string `json:"picture"` - PreferredName string `json:"preferred_name"` - Profile string `json:"profile"` - Sub string `json:"sub"` - UpdatedAt string `json:"updated_at"` - Website string `json:"website"` - Zoneinfo string `json:"zoneinfo"` + Address *Address `json:"address,omitempty" url:"address,omitempty"` + Birthdate string `json:"birthdate" url:"birthdate"` + Email string `json:"email" url:"email"` + EmailVerified bool `json:"email_verified" url:"email_verified"` + FamilyName string `json:"family_name" url:"family_name"` + Gender string `json:"gender" url:"gender"` + GivenName string `json:"given_name" url:"given_name"` + Locale string `json:"locale" url:"locale"` + MiddleName string `json:"middle_name" url:"middle_name"` + Name string `json:"name" url:"name"` + Nickname string `json:"nickname" url:"nickname"` + PhoneNumber string `json:"phone_number" url:"phone_number"` + PhoneNumberVerified bool `json:"phone_number_verified" url:"phone_number_verified"` + Picture string `json:"picture" url:"picture"` + PreferredName string `json:"preferred_name" url:"preferred_name"` + Profile string `json:"profile" url:"profile"` + Sub string `json:"sub" url:"sub"` + UpdatedAt string `json:"updated_at" url:"updated_at"` + Website string `json:"website" url:"website"` + Zoneinfo string `json:"zoneinfo" url:"zoneinfo"` // A free form object. Due to a limitation of the API Explorer, you can only enter string key/values below, but this API accepts more complex object structures. - Custom interface{} `json:"custom,omitempty"` - Airship *AirshipProfile `json:"airship,omitempty"` - Apn string `json:"apn"` - TargetArn string `json:"target_arn"` - Discord *Discord `json:"discord,omitempty"` - Expo *Expo `json:"expo,omitempty"` - FacebookPsid string `json:"facebookPSID"` - FirebaseToken string `json:"firebaseToken"` - Intercom *Intercom `json:"intercom,omitempty"` - Slack *Slack `json:"slack,omitempty"` - MsTeams *MsTeams `json:"ms_teams,omitempty"` + Custom interface{} `json:"custom,omitempty" url:"custom,omitempty"` + Airship *AirshipProfile `json:"airship,omitempty" url:"airship,omitempty"` + Apn string `json:"apn" url:"apn"` + TargetArn string `json:"target_arn" url:"target_arn"` + Discord *Discord `json:"discord,omitempty" url:"discord,omitempty"` + Expo *Expo `json:"expo,omitempty" url:"expo,omitempty"` + FacebookPsid string `json:"facebookPSID" url:"facebookPSID"` + FirebaseToken string `json:"firebaseToken" url:"firebaseToken"` + Intercom *Intercom `json:"intercom,omitempty" url:"intercom,omitempty"` + Slack *Slack `json:"slack,omitempty" url:"slack,omitempty"` + MsTeams *MsTeams `json:"ms_teams,omitempty" url:"ms_teams,omitempty"` _rawJSON json.RawMessage } @@ -4042,7 +4106,7 @@ type Attachment = map[string]interface{} type AudienceFilter struct { // Send to users only if they are member of the account - Value string `json:"value"` + Value string `json:"value" url:"value"` operator string path string @@ -4058,12 +4122,16 @@ func (a *AudienceFilter) Path() string { } func (a *AudienceFilter) UnmarshalJSON(data []byte) error { - type unmarshaler AudienceFilter - var value unmarshaler - if err := json.Unmarshal(data, &value); err != nil { + type embed AudienceFilter + var unmarshaler = struct { + embed + }{ + embed: embed(*a), + } + if err := json.Unmarshal(data, &unmarshaler); err != nil { return err } - *a = AudienceFilter(value) + *a = AudienceFilter(unmarshaler.embed) a.operator = "MEMBER_OF" a.path = "account_id" a._rawJSON = json.RawMessage(data) @@ -4098,9 +4166,9 @@ func (a *AudienceFilter) String() string { type AudienceRecipient struct { // A unique identifier associated with an Audience. A message will be sent to each user in the audience. - AudienceId string `json:"audience_id"` - Data *MessageData `json:"data,omitempty"` - Filters []*AudienceFilter `json:"filters,omitempty"` + AudienceId string `json:"audience_id" url:"audience_id"` + Data *MessageData `json:"data,omitempty" url:"data,omitempty"` + Filters []*AudienceFilter `json:"filters,omitempty" url:"filters,omitempty"` _rawJSON json.RawMessage } @@ -4131,24 +4199,24 @@ func (a *AudienceRecipient) String() string { type BaseMessage struct { // An arbitrary object that includes any data you want to pass to the message. // The data will populate the corresponding template or elements variables. - Data *MessageData `json:"data,omitempty"` - BrandId *string `json:"brand_id,omitempty"` + Data *MessageData `json:"data,omitempty" url:"data,omitempty"` + BrandId *string `json:"brand_id,omitempty" url:"brand_id,omitempty"` // "Define run-time configuration for one or more channels. If you don't specify channels, the default configuration for each channel will be used. Valid ChannelId's are: email, sms, push, inbox, direct_message, banner, and webhook." - Channels *MessageChannels `json:"channels,omitempty"` + Channels *MessageChannels `json:"channels,omitempty" url:"channels,omitempty"` // Context to load with this recipient. Will override any context set on message.context. - Context *MessageContext `json:"context,omitempty"` + Context *MessageContext `json:"context,omitempty" url:"context,omitempty"` // Metadata such as utm tracking attached with the notification through this channel. - Metadata *MessageMetadata `json:"metadata,omitempty"` + Metadata *MessageMetadata `json:"metadata,omitempty" url:"metadata,omitempty"` // An object whose keys are valid provider identifiers which map to an object. - Providers *MessageProviders `json:"providers,omitempty"` - Routing *Routing `json:"routing,omitempty"` + Providers *MessageProviders `json:"providers,omitempty" url:"providers,omitempty"` + Routing *Routing `json:"routing,omitempty" url:"routing,omitempty"` // Time in ms to attempt the channel before failing over to the next available channel. - Timeout *Timeout `json:"timeout,omitempty"` + Timeout *Timeout `json:"timeout,omitempty" url:"timeout,omitempty"` // Defines the time to wait before delivering the message. - Delay *Delay `json:"delay,omitempty"` + Delay *Delay `json:"delay,omitempty" url:"delay,omitempty"` // "Expiry allows you to set an absolute or relative time in which a message expires. // Note: This is only valid for the Courier Inbox channel as of 12-08-2022." - Expiry *Expiry `json:"expiry,omitempty"` + Expiry *Expiry `json:"expiry,omitempty" url:"expiry,omitempty"` _rawJSON json.RawMessage } @@ -4177,7 +4245,7 @@ func (b *BaseMessage) String() string { } type BaseSocialPresence struct { - Url string `json:"url"` + Url string `json:"url" url:"url"` _rawJSON json.RawMessage } @@ -4206,10 +4274,10 @@ func (b *BaseSocialPresence) String() string { } type BrandSettingsEmail struct { - TemplateOverride *BrandTemplateOverride `json:"templateOverride,omitempty"` - Head *EmailHead `json:"head,omitempty"` - Footer *EmailFooter `json:"footer,omitempty"` - Header *EmailHeader `json:"header,omitempty"` + TemplateOverride *BrandTemplateOverride `json:"templateOverride,omitempty" url:"templateOverride,omitempty"` + Head *EmailHead `json:"head,omitempty" url:"head,omitempty"` + Footer *EmailFooter `json:"footer,omitempty" url:"footer,omitempty"` + Header *EmailHeader `json:"header,omitempty" url:"header,omitempty"` _rawJSON json.RawMessage } @@ -4238,14 +4306,14 @@ func (b *BrandSettingsEmail) String() string { } type BrandSettingsInApp struct { - BorderRadius *string `json:"borderRadius,omitempty"` - DisableMessageIcon *bool `json:"disableMessageIcon,omitempty"` - FontFamily *string `json:"fontFamily,omitempty"` - Placement *InAppPlacement `json:"placement,omitempty"` - WidgetBackground *WidgetBackground `json:"widgetBackground,omitempty"` - Colors *BrandColors `json:"colors,omitempty"` - Icons *Icons `json:"icons,omitempty"` - Preferences *Preferences `json:"preferences,omitempty"` + BorderRadius *string `json:"borderRadius,omitempty" url:"borderRadius,omitempty"` + DisableMessageIcon *bool `json:"disableMessageIcon,omitempty" url:"disableMessageIcon,omitempty"` + FontFamily *string `json:"fontFamily,omitempty" url:"fontFamily,omitempty"` + Placement *InAppPlacement `json:"placement,omitempty" url:"placement,omitempty"` + WidgetBackground *WidgetBackground `json:"widgetBackground,omitempty" url:"widgetBackground,omitempty"` + Colors *BrandColors `json:"colors,omitempty" url:"colors,omitempty"` + Icons *Icons `json:"icons,omitempty" url:"icons,omitempty"` + Preferences *Preferences `json:"preferences,omitempty" url:"preferences,omitempty"` _rawJSON json.RawMessage } @@ -4274,12 +4342,12 @@ func (b *BrandSettingsInApp) String() string { } type BrandSettingsSocialPresence struct { - InheritDefault *bool `json:"inheritDefault,omitempty"` - Facebook *BaseSocialPresence `json:"facebook,omitempty"` - Instagram *BaseSocialPresence `json:"instagram,omitempty"` - Linkedin *BaseSocialPresence `json:"linkedin,omitempty"` - Medium *BaseSocialPresence `json:"medium,omitempty"` - Twitter *BaseSocialPresence `json:"twitter,omitempty"` + InheritDefault *bool `json:"inheritDefault,omitempty" url:"inheritDefault,omitempty"` + Facebook *BaseSocialPresence `json:"facebook,omitempty" url:"facebook,omitempty"` + Instagram *BaseSocialPresence `json:"instagram,omitempty" url:"instagram,omitempty"` + Linkedin *BaseSocialPresence `json:"linkedin,omitempty" url:"linkedin,omitempty"` + Medium *BaseSocialPresence `json:"medium,omitempty" url:"medium,omitempty"` + Twitter *BaseSocialPresence `json:"twitter,omitempty" url:"twitter,omitempty"` _rawJSON json.RawMessage } @@ -4308,13 +4376,13 @@ func (b *BrandSettingsSocialPresence) String() string { } type BrandTemplate struct { - BackgroundColor *string `json:"backgroundColor,omitempty"` - BlocksBackgroundColor *string `json:"blocksBackgroundColor,omitempty"` - Enabled bool `json:"enabled"` - Footer *string `json:"footer,omitempty"` - Head *string `json:"head,omitempty"` - Header *string `json:"header,omitempty"` - Width *string `json:"width,omitempty"` + BackgroundColor *string `json:"backgroundColor,omitempty" url:"backgroundColor,omitempty"` + BlocksBackgroundColor *string `json:"blocksBackgroundColor,omitempty" url:"blocksBackgroundColor,omitempty"` + Enabled bool `json:"enabled" url:"enabled"` + Footer *string `json:"footer,omitempty" url:"footer,omitempty"` + Head *string `json:"head,omitempty" url:"head,omitempty"` + Header *string `json:"header,omitempty" url:"header,omitempty"` + Width *string `json:"width,omitempty" url:"width,omitempty"` _rawJSON json.RawMessage } @@ -4343,16 +4411,16 @@ func (b *BrandTemplate) String() string { } type BrandTemplateOverride struct { - BackgroundColor *string `json:"backgroundColor,omitempty"` - BlocksBackgroundColor *string `json:"blocksBackgroundColor,omitempty"` - Enabled bool `json:"enabled"` - Footer *string `json:"footer,omitempty"` - Head *string `json:"head,omitempty"` - Header *string `json:"header,omitempty"` - Width *string `json:"width,omitempty"` - Mjml *BrandTemplate `json:"mjml,omitempty"` - FooterBackgroundColor *string `json:"footerBackgroundColor,omitempty"` - FooterFullWidth *bool `json:"footerFullWidth,omitempty"` + BackgroundColor *string `json:"backgroundColor,omitempty" url:"backgroundColor,omitempty"` + BlocksBackgroundColor *string `json:"blocksBackgroundColor,omitempty" url:"blocksBackgroundColor,omitempty"` + Enabled bool `json:"enabled" url:"enabled"` + Footer *string `json:"footer,omitempty" url:"footer,omitempty"` + Head *string `json:"head,omitempty" url:"head,omitempty"` + Header *string `json:"header,omitempty" url:"header,omitempty"` + Width *string `json:"width,omitempty" url:"width,omitempty"` + Mjml *BrandTemplate `json:"mjml,omitempty" url:"mjml,omitempty"` + FooterBackgroundColor *string `json:"footerBackgroundColor,omitempty" url:"footerBackgroundColor,omitempty"` + FooterFullWidth *bool `json:"footerFullWidth,omitempty" url:"footerFullWidth,omitempty"` _rawJSON json.RawMessage } @@ -4383,22 +4451,22 @@ func (b *BrandTemplateOverride) String() string { type Channel struct { // Id of the brand that should be used for rendering the message. // If not specified, the brand configured as default brand will be used. - BrandId *string `json:"brand_id,omitempty"` + BrandId *string `json:"brand_id,omitempty" url:"brand_id,omitempty"` // A list of providers enabled for this channel. Courier will select // one provider to send through unless routing_method is set to all. - Providers []string `json:"providers,omitempty"` + Providers []string `json:"providers,omitempty" url:"providers,omitempty"` // The method for selecting the providers to send the message with. // Single will send to one of the available providers for this channel, // all will send the message through all channels. Defaults to `single`. - RoutingMethod *RoutingMethod `json:"routing_method,omitempty"` + RoutingMethod *RoutingMethod `json:"routing_method,omitempty" url:"routing_method,omitempty"` // A JavaScript conditional expression to determine if the message should // be sent through the channel. Has access to the data and profile object. // For example, `data.name === profile.name` - If *string `json:"if,omitempty"` - Timeouts *Timeouts `json:"timeouts,omitempty"` + If *string `json:"if,omitempty" url:"if,omitempty"` + Timeouts *Timeouts `json:"timeouts,omitempty" url:"timeouts,omitempty"` // Channel specific overrides. - Override *Override `json:"override,omitempty"` - Metadata *ChannelMetadata `json:"metadata,omitempty"` + Override *Override `json:"override,omitempty" url:"override,omitempty"` + Metadata *ChannelMetadata `json:"metadata,omitempty" url:"metadata,omitempty"` _rawJSON json.RawMessage } @@ -4427,7 +4495,7 @@ func (c *Channel) String() string { } type ChannelMetadata struct { - Utm *Utm `json:"utm,omitempty"` + Utm *Utm `json:"utm,omitempty" url:"utm,omitempty"` _rawJSON json.RawMessage } @@ -4542,29 +4610,29 @@ func (c *Content) Accept(visitor ContentVisitor) error { type ContentMessage struct { // An arbitrary object that includes any data you want to pass to the message. // The data will populate the corresponding template or elements variables. - Data *MessageData `json:"data,omitempty"` - BrandId *string `json:"brand_id,omitempty"` + Data *MessageData `json:"data,omitempty" url:"data,omitempty"` + BrandId *string `json:"brand_id,omitempty" url:"brand_id,omitempty"` // "Define run-time configuration for one or more channels. If you don't specify channels, the default configuration for each channel will be used. Valid ChannelId's are: email, sms, push, inbox, direct_message, banner, and webhook." - Channels *MessageChannels `json:"channels,omitempty"` + Channels *MessageChannels `json:"channels,omitempty" url:"channels,omitempty"` // Context to load with this recipient. Will override any context set on message.context. - Context *MessageContext `json:"context,omitempty"` + Context *MessageContext `json:"context,omitempty" url:"context,omitempty"` // Metadata such as utm tracking attached with the notification through this channel. - Metadata *MessageMetadata `json:"metadata,omitempty"` + Metadata *MessageMetadata `json:"metadata,omitempty" url:"metadata,omitempty"` // An object whose keys are valid provider identifiers which map to an object. - Providers *MessageProviders `json:"providers,omitempty"` - Routing *Routing `json:"routing,omitempty"` + Providers *MessageProviders `json:"providers,omitempty" url:"providers,omitempty"` + Routing *Routing `json:"routing,omitempty" url:"routing,omitempty"` // Time in ms to attempt the channel before failing over to the next available channel. - Timeout *Timeout `json:"timeout,omitempty"` + Timeout *Timeout `json:"timeout,omitempty" url:"timeout,omitempty"` // Defines the time to wait before delivering the message. - Delay *Delay `json:"delay,omitempty"` + Delay *Delay `json:"delay,omitempty" url:"delay,omitempty"` // "Expiry allows you to set an absolute or relative time in which a message expires. // Note: This is only valid for the Courier Inbox channel as of 12-08-2022." - Expiry *Expiry `json:"expiry,omitempty"` + Expiry *Expiry `json:"expiry,omitempty" url:"expiry,omitempty"` // Describes the content of the message in a way that will work for email, push, // chat, or any channel. Either this or template must be specified. - Content *Content `json:"content,omitempty"` + Content *Content `json:"content,omitempty" url:"content,omitempty"` // The recipient or a list of recipients of the message - To *MessageRecipient `json:"to,omitempty"` + To *MessageRecipient `json:"to,omitempty" url:"to,omitempty"` _rawJSON json.RawMessage } @@ -4622,7 +4690,7 @@ func (c Criteria) Ptr() *Criteria { type Delay struct { // The duration of the delay in milliseconds. - Duration int `json:"duration"` + Duration int `json:"duration" url:"duration"` _rawJSON json.RawMessage } @@ -4652,24 +4720,24 @@ func (d *Delay) String() string { // Allows the user to execute an action. Can be a button or a link. type ElementalActionNode struct { - Channels []string `json:"channels,omitempty"` - Ref *string `json:"ref,omitempty"` - If *string `json:"if,omitempty"` - Loop *string `json:"loop,omitempty"` + Channels []string `json:"channels,omitempty" url:"channels,omitempty"` + Ref *string `json:"ref,omitempty" url:"ref,omitempty"` + If *string `json:"if,omitempty" url:"if,omitempty"` + Loop *string `json:"loop,omitempty" url:"loop,omitempty"` // The text content of the action shown to the user. - Content string `json:"content"` + Content string `json:"content" url:"content"` // The target URL of the action. - Href string `json:"href"` + Href string `json:"href" url:"href"` // A unique id used to identify the action when it is executed. - ActionId *string `json:"action_id,omitempty"` + ActionId *string `json:"action_id,omitempty" url:"action_id,omitempty"` // The alignment of the action button. Defaults to "center". - Align *IAlignment `json:"align,omitempty"` + Align *IAlignment `json:"align,omitempty" url:"align,omitempty"` // The background color of the action button. - BackgroundColor *string `json:"background_color,omitempty"` + BackgroundColor *string `json:"background_color,omitempty" url:"background_color,omitempty"` // Defaults to `button`. - Style *IActionButtonStyle `json:"style,omitempty"` + Style *IActionButtonStyle `json:"style,omitempty" url:"style,omitempty"` // Region specific content. See [locales docs](https://www.courier.com/docs/platform/content/elemental/locales/) for more details. - Locales Locales `json:"locales,omitempty"` + Locales Locales `json:"locales,omitempty" url:"locales,omitempty"` _rawJSON json.RawMessage } @@ -4698,10 +4766,10 @@ func (e *ElementalActionNode) String() string { } type ElementalBaseNode struct { - Channels []string `json:"channels,omitempty"` - Ref *string `json:"ref,omitempty"` - If *string `json:"if,omitempty"` - Loop *string `json:"loop,omitempty"` + Channels []string `json:"channels,omitempty" url:"channels,omitempty"` + Ref *string `json:"ref,omitempty" url:"ref,omitempty"` + If *string `json:"if,omitempty" url:"if,omitempty"` + Loop *string `json:"loop,omitempty" url:"loop,omitempty"` _rawJSON json.RawMessage } @@ -4738,19 +4806,19 @@ func (e *ElementalBaseNode) String() string { // display an individual element on a per channel basis. See the // [control flow docs](https://www.courier.com/docs/platform/content/elemental/control-flow/) for more details. type ElementalChannelNode struct { - Channels []string `json:"channels,omitempty"` - Ref *string `json:"ref,omitempty"` - If *string `json:"if,omitempty"` - Loop *string `json:"loop,omitempty"` + Channels []string `json:"channels,omitempty" url:"channels,omitempty"` + Ref *string `json:"ref,omitempty" url:"ref,omitempty"` + If *string `json:"if,omitempty" url:"if,omitempty"` + Loop *string `json:"loop,omitempty" url:"loop,omitempty"` // The channel the contents of this element should be applied to. Can be `email`, // `push`, `direct_message`, `sms` or a provider such as slack - Channel string `json:"channel"` + Channel string `json:"channel" url:"channel"` // An array of elements to apply to the channel. If `raw` has not been // specified, `elements` is `required`. - Elements []*ElementalNode `json:"elements,omitempty"` + Elements []*ElementalNode `json:"elements,omitempty" url:"elements,omitempty"` // Raw data to apply to the channel. If `elements` has not been // specified, `raw` is `required`. - Raw map[string]interface{} `json:"raw,omitempty"` + Raw map[string]interface{} `json:"raw,omitempty" url:"raw,omitempty"` _rawJSON json.RawMessage } @@ -4780,9 +4848,9 @@ func (e *ElementalChannelNode) String() string { type ElementalContent struct { // For example, "2022-01-01" - Version string `json:"version"` - Brand interface{} `json:"brand,omitempty"` - Elements []*ElementalNode `json:"elements,omitempty"` + Version string `json:"version" url:"version"` + Brand interface{} `json:"brand,omitempty" url:"brand,omitempty"` + Elements []*ElementalNode `json:"elements,omitempty" url:"elements,omitempty"` _rawJSON json.RawMessage } @@ -4813,9 +4881,9 @@ func (e *ElementalContent) String() string { // Syntatic Sugar to provide a fast shorthand for Courier Elemental Blocks. type ElementalContentSugar struct { // The title to be displayed by supported channels i.e. push, email (as subject) - Title string `json:"title"` + Title string `json:"title" url:"title"` // The text content displayed in the notification. - Body string `json:"body"` + Body string `json:"body" url:"body"` _rawJSON json.RawMessage } @@ -4845,12 +4913,12 @@ func (e *ElementalContentSugar) String() string { // Renders a dividing line between elements. type ElementalDividerNode struct { - Channels []string `json:"channels,omitempty"` - Ref *string `json:"ref,omitempty"` - If *string `json:"if,omitempty"` - Loop *string `json:"loop,omitempty"` + Channels []string `json:"channels,omitempty" url:"channels,omitempty"` + Ref *string `json:"ref,omitempty" url:"ref,omitempty"` + If *string `json:"if,omitempty" url:"if,omitempty"` + Loop *string `json:"loop,omitempty" url:"loop,omitempty"` // The CSS color to render the line with. For example, `#fff` - Color *string `json:"color,omitempty"` + Color *string `json:"color,omitempty" url:"color,omitempty"` _rawJSON json.RawMessage } @@ -4880,12 +4948,12 @@ func (e *ElementalDividerNode) String() string { // Allows you to group elements together. This can be useful when used in combination with "if" or "loop". See [control flow docs](https://www.courier.com/docs/platform/content/elemental/control-flow/) for more details. type ElementalGroupNode struct { - Channels []string `json:"channels,omitempty"` - Ref *string `json:"ref,omitempty"` - If *string `json:"if,omitempty"` - Loop *string `json:"loop,omitempty"` + Channels []string `json:"channels,omitempty" url:"channels,omitempty"` + Ref *string `json:"ref,omitempty" url:"ref,omitempty"` + If *string `json:"if,omitempty" url:"if,omitempty"` + Loop *string `json:"loop,omitempty" url:"loop,omitempty"` // Sub elements to render. - Elements []*ElementalNode `json:"elements,omitempty"` + Elements []*ElementalNode `json:"elements,omitempty" url:"elements,omitempty"` _rawJSON json.RawMessage } @@ -4915,20 +4983,20 @@ func (e *ElementalGroupNode) String() string { // Used to embed an image into the notification. type ElementalImageNode struct { - Channels []string `json:"channels,omitempty"` - Ref *string `json:"ref,omitempty"` - If *string `json:"if,omitempty"` - Loop *string `json:"loop,omitempty"` + Channels []string `json:"channels,omitempty" url:"channels,omitempty"` + Ref *string `json:"ref,omitempty" url:"ref,omitempty"` + If *string `json:"if,omitempty" url:"if,omitempty"` + Loop *string `json:"loop,omitempty" url:"loop,omitempty"` // The source of the image. - Src string `json:"src"` + Src string `json:"src" url:"src"` // A URL to link to when the image is clicked. - Href *string `json:"href,omitempty"` + Href *string `json:"href,omitempty" url:"href,omitempty"` // The alignment of the image. - Align *IAlignment `json:"align,omitempty"` + Align *IAlignment `json:"align,omitempty" url:"align,omitempty"` // Alternate text for the image. - AltText *string `json:"altText,omitempty"` + AltText *string `json:"altText,omitempty" url:"altText,omitempty"` // CSS width properties to apply to the image. For example, 50px - Width *string `json:"width,omitempty"` + Width *string `json:"width,omitempty" url:"width,omitempty"` _rawJSON json.RawMessage } @@ -4960,12 +5028,12 @@ func (e *ElementalImageNode) String() string { // be used by a particular channel or provider. One important field is the title // field which will be used as the title for channels that support it. type ElementalMetaNode struct { - Channels []string `json:"channels,omitempty"` - Ref *string `json:"ref,omitempty"` - If *string `json:"if,omitempty"` - Loop *string `json:"loop,omitempty"` + Channels []string `json:"channels,omitempty" url:"channels,omitempty"` + Ref *string `json:"ref,omitempty" url:"ref,omitempty"` + If *string `json:"if,omitempty" url:"if,omitempty"` + Loop *string `json:"loop,omitempty" url:"loop,omitempty"` // The title to be displayed by supported channels. For example, the email subject. - Title *string `json:"title,omitempty"` + Title *string `json:"title,omitempty" url:"title,omitempty"` _rawJSON json.RawMessage } @@ -5213,19 +5281,19 @@ func (e *ElementalNode) Accept(visitor ElementalNodeVisitor) error { // Renders a quote block. type ElementalQuoteNode struct { - Channels []string `json:"channels,omitempty"` - Ref *string `json:"ref,omitempty"` - If *string `json:"if,omitempty"` - Loop *string `json:"loop,omitempty"` + Channels []string `json:"channels,omitempty" url:"channels,omitempty"` + Ref *string `json:"ref,omitempty" url:"ref,omitempty"` + If *string `json:"if,omitempty" url:"if,omitempty"` + Loop *string `json:"loop,omitempty" url:"loop,omitempty"` // The text value of the quote. - Content string `json:"content"` + Content string `json:"content" url:"content"` // Alignment of the quote. - Align *IAlignment `json:"align,omitempty"` + Align *IAlignment `json:"align,omitempty" url:"align,omitempty"` // CSS border color property. For example, `#fff` - BorderColor *string `json:"borderColor,omitempty"` - TextStyle TextStyle `json:"text_style,omitempty"` + BorderColor *string `json:"borderColor,omitempty" url:"borderColor,omitempty"` + TextStyle TextStyle `json:"text_style,omitempty" url:"text_style,omitempty"` // Region specific content. See [locales docs](https://www.courier.com/docs/platform/content/elemental/locales/) for more details. - Locales Locales `json:"locales,omitempty"` + Locales Locales `json:"locales,omitempty" url:"locales,omitempty"` _rawJSON json.RawMessage } @@ -5255,30 +5323,30 @@ func (e *ElementalQuoteNode) String() string { // Represents a body of text to be rendered inside of the notification. type ElementalTextNode struct { - Channels []string `json:"channels,omitempty"` - Ref *string `json:"ref,omitempty"` - If *string `json:"if,omitempty"` - Loop *string `json:"loop,omitempty"` + Channels []string `json:"channels,omitempty" url:"channels,omitempty"` + Ref *string `json:"ref,omitempty" url:"ref,omitempty"` + If *string `json:"if,omitempty" url:"if,omitempty"` + Loop *string `json:"loop,omitempty" url:"loop,omitempty"` // The text content displayed in the notification. Either this // field must be specified, or the elements field - Content string `json:"content"` + Content string `json:"content" url:"content"` // Text alignment. - Align TextAlign `json:"align,omitempty"` + Align TextAlign `json:"align,omitempty" url:"align,omitempty"` // Allows the text to be rendered as a heading level. - TextStyle *TextStyle `json:"text_style,omitempty"` + TextStyle *TextStyle `json:"text_style,omitempty" url:"text_style,omitempty"` // Specifies the color of text. Can be any valid css color value - Color *string `json:"color,omitempty"` + Color *string `json:"color,omitempty" url:"color,omitempty"` // Apply bold to the text - Bold *string `json:"bold,omitempty"` + Bold *string `json:"bold,omitempty" url:"bold,omitempty"` // Apply italics to the text - Italic *string `json:"italic,omitempty"` + Italic *string `json:"italic,omitempty" url:"italic,omitempty"` // Apply a strike through the text - Strikethrough *string `json:"strikethrough,omitempty"` + Strikethrough *string `json:"strikethrough,omitempty" url:"strikethrough,omitempty"` // Apply an underline to the text - Underline *string `json:"underline,omitempty"` + Underline *string `json:"underline,omitempty" url:"underline,omitempty"` // Region specific content. See [locales docs](https://www.courier.com/docs/platform/content/elemental/locales/) for more details. - Locales *Locales `json:"locales,omitempty"` - Format *string `json:"format,omitempty"` + Locales *Locales `json:"locales,omitempty" url:"locales,omitempty"` + Format *string `json:"format,omitempty" url:"format,omitempty"` _rawJSON json.RawMessage } @@ -5307,8 +5375,8 @@ func (e *ElementalTextNode) String() string { } type EmailFooter struct { - Content interface{} `json:"content,omitempty"` - InheritDefault *bool `json:"inheritDefault,omitempty"` + Content interface{} `json:"content,omitempty" url:"content,omitempty"` + InheritDefault *bool `json:"inheritDefault,omitempty" url:"inheritDefault,omitempty"` _rawJSON json.RawMessage } @@ -5337,8 +5405,8 @@ func (e *EmailFooter) String() string { } type EmailHead struct { - InheritDefault bool `json:"inheritDefault"` - Content *string `json:"content,omitempty"` + InheritDefault bool `json:"inheritDefault" url:"inheritDefault"` + Content *string `json:"content,omitempty" url:"content,omitempty"` _rawJSON json.RawMessage } @@ -5367,9 +5435,9 @@ func (e *EmailHead) String() string { } type EmailHeader struct { - InheritDefault *bool `json:"inheritDefault,omitempty"` - BarColor *string `json:"barColor,omitempty"` - Logo *Logo `json:"logo,omitempty"` + InheritDefault *bool `json:"inheritDefault,omitempty" url:"inheritDefault,omitempty"` + BarColor *string `json:"barColor,omitempty" url:"barColor,omitempty"` + Logo *Logo `json:"logo,omitempty" url:"logo,omitempty"` _rawJSON json.RawMessage } @@ -5456,9 +5524,9 @@ func (e *ExpiresInType) Accept(visitor ExpiresInTypeVisitor) error { type Expiry struct { // An epoch timestamp or ISO8601 timestamp with timezone `(YYYY-MM-DDThh:mm:ss.sTZD)` that describes the time in which a message expires. - ExpiresAt *string `json:"expires_at,omitempty"` + ExpiresAt *string `json:"expires_at,omitempty" url:"expires_at,omitempty"` // A duration in the form of milliseconds or an ISO8601 Duration format (i.e. P1DT4H). - ExpiresIn *ExpiresInType `json:"expires_in,omitempty"` + ExpiresIn *ExpiresInType `json:"expires_in,omitempty" url:"expires_in,omitempty"` _rawJSON json.RawMessage } @@ -5539,9 +5607,9 @@ func (i IAlignment) Ptr() *IAlignment { type IPreferences = map[string]*Preference type IProfilePreferences struct { - Categories *IPreferences `json:"categories,omitempty"` - Notifications IPreferences `json:"notifications,omitempty"` - TemplateId *string `json:"templateId,omitempty"` + Categories *IPreferences `json:"categories,omitempty" url:"categories,omitempty"` + Notifications IPreferences `json:"notifications,omitempty" url:"notifications,omitempty"` + TemplateId *string `json:"templateId,omitempty" url:"templateId,omitempty"` _rawJSON json.RawMessage } @@ -5570,8 +5638,8 @@ func (i *IProfilePreferences) String() string { } type Icons struct { - Bell *string `json:"bell,omitempty"` - Message *string `json:"message,omitempty"` + Bell *string `json:"bell,omitempty" url:"bell,omitempty"` + Message *string `json:"message,omitempty" url:"message,omitempty"` _rawJSON json.RawMessage } @@ -5628,8 +5696,8 @@ func (i InAppPlacement) Ptr() *InAppPlacement { } type InvalidListPatternRecipient struct { - UserId string `json:"user_id"` - ListId string `json:"list_id"` + UserId string `json:"user_id" url:"user_id"` + ListId string `json:"list_id" url:"list_id"` _rawJSON json.RawMessage } @@ -5658,8 +5726,8 @@ func (i *InvalidListPatternRecipient) String() string { } type InvalidListRecipient struct { - UserId string `json:"user_id"` - ListPattern string `json:"list_pattern"` + UserId string `json:"user_id" url:"user_id"` + ListPattern string `json:"list_pattern" url:"list_pattern"` _rawJSON json.RawMessage } @@ -5688,8 +5756,8 @@ func (i *InvalidListRecipient) String() string { } type InvalidUserRecipient struct { - ListId string `json:"list_id"` - ListPattern string `json:"list_pattern"` + ListId string `json:"list_id" url:"list_id"` + ListPattern string `json:"list_pattern" url:"list_pattern"` _rawJSON json.RawMessage } @@ -5719,7 +5787,7 @@ func (i *InvalidUserRecipient) String() string { type ListFilter struct { // Send to users only if they are member of the account - Value string `json:"value"` + Value string `json:"value" url:"value"` operator string path string @@ -5735,12 +5803,16 @@ func (l *ListFilter) Path() string { } func (l *ListFilter) UnmarshalJSON(data []byte) error { - type unmarshaler ListFilter - var value unmarshaler - if err := json.Unmarshal(data, &value); err != nil { + type embed ListFilter + var unmarshaler = struct { + embed + }{ + embed: embed(*l), + } + if err := json.Unmarshal(data, &unmarshaler); err != nil { return err } - *l = ListFilter(value) + *l = ListFilter(unmarshaler.embed) l.operator = "MEMBER_OF" l.path = "account_id" l._rawJSON = json.RawMessage(data) @@ -5774,8 +5846,8 @@ func (l *ListFilter) String() string { } type ListPatternRecipient struct { - ListPattern *string `json:"list_pattern,omitempty"` - Data *MessageData `json:"data,omitempty"` + ListPattern *string `json:"list_pattern,omitempty" url:"list_pattern,omitempty"` + Data *MessageData `json:"data,omitempty" url:"data,omitempty"` _rawJSON json.RawMessage } @@ -5831,9 +5903,9 @@ func (l *ListPatternRecipientType) String() string { } type ListRecipient struct { - ListId *string `json:"list_id,omitempty"` - Data *MessageData `json:"data,omitempty"` - Filters []*ListFilter `json:"filters,omitempty"` + ListId *string `json:"list_id,omitempty" url:"list_id,omitempty"` + Data *MessageData `json:"data,omitempty" url:"data,omitempty"` + Filters []*ListFilter `json:"filters,omitempty" url:"filters,omitempty"` _rawJSON json.RawMessage } @@ -5889,7 +5961,7 @@ func (l *ListRecipientType) String() string { } type Locale struct { - Content string `json:"content"` + Content string `json:"content" url:"content"` _rawJSON json.RawMessage } @@ -5920,8 +5992,8 @@ func (l *Locale) String() string { type Locales = map[string]*Locale type Logo struct { - Href *string `json:"href,omitempty"` - Image *string `json:"image,omitempty"` + Href *string `json:"href,omitempty" url:"href,omitempty"` + Image *string `json:"image,omitempty" url:"image,omitempty"` _rawJSON json.RawMessage } @@ -6009,16 +6081,16 @@ func (m *Message) Accept(visitor MessageVisitor) error { } type MessageChannelEmailOverride struct { - Attachments []Attachment `json:"attachments,omitempty"` - Bcc *string `json:"bcc,omitempty"` - Brand *Brand `json:"brand,omitempty"` - Cc *string `json:"cc,omitempty"` - From *string `json:"from,omitempty"` - Html *string `json:"html,omitempty"` - ReplyTo *string `json:"reply_to,omitempty"` - Subject *string `json:"subject,omitempty"` - Text *string `json:"text,omitempty"` - Tracking *TrackingOverride `json:"tracking,omitempty"` + Attachments []Attachment `json:"attachments,omitempty" url:"attachments,omitempty"` + Bcc *string `json:"bcc,omitempty" url:"bcc,omitempty"` + Brand *Brand `json:"brand,omitempty" url:"brand,omitempty"` + Cc *string `json:"cc,omitempty" url:"cc,omitempty"` + From *string `json:"from,omitempty" url:"from,omitempty"` + Html *string `json:"html,omitempty" url:"html,omitempty"` + ReplyTo *string `json:"reply_to,omitempty" url:"reply_to,omitempty"` + Subject *string `json:"subject,omitempty" url:"subject,omitempty"` + Text *string `json:"text,omitempty" url:"text,omitempty"` + Tracking *TrackingOverride `json:"tracking,omitempty" url:"tracking,omitempty"` _rawJSON json.RawMessage } @@ -6051,7 +6123,7 @@ type MessageChannels = map[string]*Channel type MessageContext struct { // An id of a tenant, see [tenants api docs](https://www.courier.com/docs/reference/tenants/). // Will load brand, default preferences and any other base context data associated with this tenant. - TenantId *string `json:"tenant_id,omitempty"` + TenantId *string `json:"tenant_id,omitempty" url:"tenant_id,omitempty"` _rawJSON json.RawMessage } @@ -6083,13 +6155,13 @@ type MessageData = map[string]interface{} type MessageMetadata struct { // An arbitrary string to tracks the event that generated this request (e.g. 'signup'). - Event *string `json:"event,omitempty"` + Event *string `json:"event,omitempty" url:"event,omitempty"` // An array of up to 9 tags you wish to associate with this request (and corresponding messages) for later analysis. Individual tags cannot be more than 30 characters in length. - Tags []string `json:"tags,omitempty"` + Tags []string `json:"tags,omitempty" url:"tags,omitempty"` // Identify the campaign that refers traffic to a specific website, and attributes the browser's website session. - Utm *Utm `json:"utm,omitempty"` + Utm *Utm `json:"utm,omitempty" url:"utm,omitempty"` // A unique ID used to correlate this request to processing on your servers. Note: Courier does not verify the uniqueness of this ID. - TraceId *string `json:"trace_id,omitempty"` + TraceId *string `json:"trace_id,omitempty" url:"trace_id,omitempty"` _rawJSON json.RawMessage } @@ -6121,13 +6193,13 @@ type MessageProviders = map[string]*MessageProvidersType type MessageProvidersType struct { // Provider specific overrides. - Override map[string]interface{} `json:"override,omitempty"` + Override map[string]interface{} `json:"override,omitempty" url:"override,omitempty"` // A JavaScript conditional expression to determine if the message should be sent // through the channel. Has access to the data and profile object. For example, // `data.name === profile.name` - If *string `json:"if,omitempty"` - Timeouts *int `json:"timeouts,omitempty"` - Metadata *Metadata `json:"metadata,omitempty"` + If *string `json:"if,omitempty" url:"if,omitempty"` + Timeouts *int `json:"timeouts,omitempty" url:"timeouts,omitempty"` + Metadata *Metadata `json:"metadata,omitempty" url:"metadata,omitempty"` _rawJSON json.RawMessage } @@ -6213,7 +6285,7 @@ func (m *MessageRecipient) Accept(visitor MessageRecipientVisitor) error { } type Metadata struct { - Utm *Utm `json:"utm,omitempty"` + Utm *Utm `json:"utm,omitempty" url:"utm,omitempty"` _rawJSON json.RawMessage } @@ -6242,7 +6314,7 @@ func (m *Metadata) String() string { } type MsTeamsRecipient struct { - MsTeams *MsTeams `json:"ms_teams,omitempty"` + MsTeams *MsTeams `json:"ms_teams,omitempty" url:"ms_teams,omitempty"` _rawJSON json.RawMessage } @@ -6293,10 +6365,10 @@ func (o Override) Ptr() *Override { } type Preference struct { - Status PreferenceStatus `json:"status,omitempty"` - Rules []*Rule `json:"rules,omitempty"` - ChannelPreferences []*ChannelPreference `json:"channel_preferences,omitempty"` - Source *ChannelSource `json:"source,omitempty"` + Status PreferenceStatus `json:"status,omitempty" url:"status,omitempty"` + Rules []*Rule `json:"rules,omitempty" url:"rules,omitempty"` + ChannelPreferences []*ChannelPreference `json:"channel_preferences,omitempty" url:"channel_preferences,omitempty"` + Source *ChannelSource `json:"source,omitempty" url:"source,omitempty"` _rawJSON json.RawMessage } @@ -6325,7 +6397,7 @@ func (p *Preference) String() string { } type Preferences struct { - TemplateIds []string `json:"templateIds,omitempty"` + TemplateIds []string `json:"templateIds,omitempty" url:"templateIds,omitempty"` _rawJSON json.RawMessage } @@ -6478,11 +6550,11 @@ func (r *Recipient) Accept(visitor RecipientVisitor) error { // If no routing key is specified, Courier will use the default routing configuration or // routing defined by the template. type Routing struct { - Method RoutingMethod `json:"method,omitempty"` + Method RoutingMethod `json:"method,omitempty" url:"method,omitempty"` // A list of channels or providers to send the message through. Can also recursively define // sub-routing methods, which can be useful for defining advanced push notification // delivery strategies. - Channels []*RoutingChannel `json:"channels,omitempty"` + Channels []*RoutingChannel `json:"channels,omitempty" url:"channels,omitempty"` _rawJSON json.RawMessage } @@ -6606,11 +6678,11 @@ func (r RoutingMethod) Ptr() *RoutingMethod { } type RoutingStrategyChannel struct { - Channel string `json:"channel"` - Config map[string]interface{} `json:"config,omitempty"` - Method *RoutingMethod `json:"method,omitempty"` - Providers *MessageProviders `json:"providers,omitempty"` - If *string `json:"if,omitempty"` + Channel string `json:"channel" url:"channel"` + Config map[string]interface{} `json:"config,omitempty" url:"config,omitempty"` + Method *RoutingMethod `json:"method,omitempty" url:"method,omitempty"` + Providers *MessageProviders `json:"providers,omitempty" url:"providers,omitempty"` + If *string `json:"if,omitempty" url:"if,omitempty"` _rawJSON json.RawMessage } @@ -6639,10 +6711,10 @@ func (r *RoutingStrategyChannel) String() string { } type RoutingStrategyProvider struct { - Name string `json:"name"` - Config map[string]interface{} `json:"config,omitempty"` - If *string `json:"if,omitempty"` - Metadata *Metadata `json:"metadata,omitempty"` + Name string `json:"name" url:"name"` + Config map[string]interface{} `json:"config,omitempty" url:"config,omitempty"` + If *string `json:"if,omitempty" url:"if,omitempty"` + Metadata *Metadata `json:"metadata,omitempty" url:"metadata,omitempty"` _rawJSON json.RawMessage } @@ -6696,7 +6768,7 @@ func (r RuleType) Ptr() *RuleType { } type SlackRecipient struct { - Slack *Slack `json:"slack,omitempty"` + Slack *Slack `json:"slack,omitempty" url:"slack,omitempty"` _rawJSON json.RawMessage } @@ -6727,29 +6799,29 @@ func (s *SlackRecipient) String() string { type TemplateMessage struct { // An arbitrary object that includes any data you want to pass to the message. // The data will populate the corresponding template or elements variables. - Data *MessageData `json:"data,omitempty"` - BrandId *string `json:"brand_id,omitempty"` + Data *MessageData `json:"data,omitempty" url:"data,omitempty"` + BrandId *string `json:"brand_id,omitempty" url:"brand_id,omitempty"` // "Define run-time configuration for one or more channels. If you don't specify channels, the default configuration for each channel will be used. Valid ChannelId's are: email, sms, push, inbox, direct_message, banner, and webhook." - Channels *MessageChannels `json:"channels,omitempty"` + Channels *MessageChannels `json:"channels,omitempty" url:"channels,omitempty"` // Context to load with this recipient. Will override any context set on message.context. - Context *MessageContext `json:"context,omitempty"` + Context *MessageContext `json:"context,omitempty" url:"context,omitempty"` // Metadata such as utm tracking attached with the notification through this channel. - Metadata *MessageMetadata `json:"metadata,omitempty"` + Metadata *MessageMetadata `json:"metadata,omitempty" url:"metadata,omitempty"` // An object whose keys are valid provider identifiers which map to an object. - Providers *MessageProviders `json:"providers,omitempty"` - Routing *Routing `json:"routing,omitempty"` + Providers *MessageProviders `json:"providers,omitempty" url:"providers,omitempty"` + Routing *Routing `json:"routing,omitempty" url:"routing,omitempty"` // Time in ms to attempt the channel before failing over to the next available channel. - Timeout *Timeout `json:"timeout,omitempty"` + Timeout *Timeout `json:"timeout,omitempty" url:"timeout,omitempty"` // Defines the time to wait before delivering the message. - Delay *Delay `json:"delay,omitempty"` + Delay *Delay `json:"delay,omitempty" url:"delay,omitempty"` // "Expiry allows you to set an absolute or relative time in which a message expires. // Note: This is only valid for the Courier Inbox channel as of 12-08-2022." - Expiry *Expiry `json:"expiry,omitempty"` + Expiry *Expiry `json:"expiry,omitempty" url:"expiry,omitempty"` // The id of the notification template to be rendered and sent to the recipient(s). // This field or the content field must be supplied. - Template string `json:"template"` + Template string `json:"template" url:"template"` // The recipient or a list of recipients of the message - To *MessageRecipient `json:"to,omitempty"` + To *MessageRecipient `json:"to,omitempty" url:"to,omitempty"` _rawJSON json.RawMessage } @@ -6831,11 +6903,11 @@ func (t TextStyle) Ptr() *TextStyle { } type Timeout struct { - Provider map[string]int `json:"provider,omitempty"` - Channel map[string]int `json:"channel,omitempty"` - Message *int `json:"message,omitempty"` - Escalation *int `json:"escalation,omitempty"` - Criteria *Criteria `json:"criteria,omitempty"` + Provider map[string]int `json:"provider,omitempty" url:"provider,omitempty"` + Channel map[string]int `json:"channel,omitempty" url:"channel,omitempty"` + Message *int `json:"message,omitempty" url:"message,omitempty"` + Escalation *int `json:"escalation,omitempty" url:"escalation,omitempty"` + Criteria *Criteria `json:"criteria,omitempty" url:"criteria,omitempty"` _rawJSON json.RawMessage } @@ -6864,8 +6936,8 @@ func (t *Timeout) String() string { } type Timeouts struct { - Provider *int `json:"provider,omitempty"` - Channel *int `json:"channel,omitempty"` + Provider *int `json:"provider,omitempty" url:"provider,omitempty"` + Channel *int `json:"channel,omitempty" url:"channel,omitempty"` _rawJSON json.RawMessage } @@ -6894,7 +6966,7 @@ func (t *Timeouts) String() string { } type TrackingOverride struct { - Open bool `json:"open"` + Open bool `json:"open" url:"open"` _rawJSON json.RawMessage } @@ -6923,11 +6995,11 @@ func (t *TrackingOverride) String() string { } type Utm struct { - Source *string `json:"source,omitempty"` - Medium *string `json:"medium,omitempty"` - Campaign *string `json:"campaign,omitempty"` - Term *string `json:"term,omitempty"` - Content *string `json:"content,omitempty"` + Source *string `json:"source,omitempty" url:"source,omitempty"` + Medium *string `json:"medium,omitempty" url:"medium,omitempty"` + Campaign *string `json:"campaign,omitempty" url:"campaign,omitempty"` + Term *string `json:"term,omitempty" url:"term,omitempty"` + Content *string `json:"content,omitempty" url:"content,omitempty"` _rawJSON json.RawMessage } @@ -6957,19 +7029,19 @@ func (u *Utm) String() string { type UserRecipient struct { // Use `tenant_id` instad. - AccountId *string `json:"account_id,omitempty"` + AccountId *string `json:"account_id,omitempty" url:"account_id,omitempty"` // Context information such as tenant_id to send the notification with. - Context *MessageContext `json:"context,omitempty"` - Data *MessageData `json:"data,omitempty"` - Email *string `json:"email,omitempty"` + Context *MessageContext `json:"context,omitempty" url:"context,omitempty"` + Data *MessageData `json:"data,omitempty" url:"data,omitempty"` + Email *string `json:"email,omitempty" url:"email,omitempty"` // The user's preferred ISO 639-1 language code. - Locale *string `json:"locale,omitempty"` - UserId *string `json:"user_id,omitempty"` - PhoneNumber *string `json:"phone_number,omitempty"` - Preferences *IProfilePreferences `json:"preferences,omitempty"` + Locale *string `json:"locale,omitempty" url:"locale,omitempty"` + UserId *string `json:"user_id,omitempty" url:"user_id,omitempty"` + PhoneNumber *string `json:"phone_number,omitempty" url:"phone_number,omitempty"` + Preferences *IProfilePreferences `json:"preferences,omitempty" url:"preferences,omitempty"` // An id of a tenant, [see tenants api docs](https://www.courier.com/docs/reference/tenants). // Will load brand, default preferences and any other base context data associated with this tenant. - TenantId *string `json:"tenant_id,omitempty"` + TenantId *string `json:"tenant_id,omitempty" url:"tenant_id,omitempty"` _rawJSON json.RawMessage } @@ -7025,8 +7097,8 @@ func (u *UserRecipientType) String() string { } type WidgetBackground struct { - TopColor *string `json:"topColor,omitempty"` - BottomColor *string `json:"bottomColor,omitempty"` + TopColor *string `json:"topColor,omitempty" url:"topColor,omitempty"` + BottomColor *string `json:"bottomColor,omitempty" url:"bottomColor,omitempty"` _rawJSON json.RawMessage } @@ -7058,17 +7130,17 @@ type ChannelIdentifier = string type NotificationTemplates struct { // A UTC timestamp at which notification was created. This is stored as a millisecond representation of the Unix epoch (the time passed since January 1, 1970). - CreatedAt int `json:"created_at"` + CreatedAt int `json:"created_at" url:"created_at"` // A unique identifier associated with the notification. - Id string `json:"id"` + Id string `json:"id" url:"id"` // Routing strategy used by this notification. - Routing *RoutingStrategy `json:"routing,omitempty"` + Routing *RoutingStrategy `json:"routing,omitempty" url:"routing,omitempty"` // A list of tags attached to the notification. - Tags []*Tag `json:"tags,omitempty"` + Tags []*Tag `json:"tags,omitempty" url:"tags,omitempty"` // The title of the notification. - Title string `json:"title"` + Title string `json:"title" url:"title"` // A UTC timestamp at which notification was updated. This is stored as a millisecond representation of the Unix epoch (the time passed since January 1, 1970). - UpdatedAt int `json:"updated_at"` + UpdatedAt int `json:"updated_at" url:"updated_at"` _rawJSON json.RawMessage } @@ -7098,9 +7170,9 @@ func (n *NotificationTemplates) String() string { type RoutingStrategy struct { // The method for selecting channels to send the message with. Value can be either 'single' or 'all'. If not provided will default to 'single' - Method RoutingStrategyMethod `json:"method,omitempty"` + Method RoutingStrategyMethod `json:"method,omitempty" url:"method,omitempty"` // An array of valid channel identifiers (like email, push, sms, etc.) and additional routing nodes. - Channels []ChannelIdentifier `json:"channels,omitempty"` + Channels []ChannelIdentifier `json:"channels,omitempty" url:"channels,omitempty"` _rawJSON json.RawMessage } @@ -7151,7 +7223,7 @@ func (r RoutingStrategyMethod) Ptr() *RoutingStrategyMethod { } type Tag struct { - Data []*TagData `json:"data,omitempty"` + Data []*TagData `json:"data,omitempty" url:"data,omitempty"` _rawJSON json.RawMessage } @@ -7181,9 +7253,9 @@ func (t *Tag) String() string { type TagData struct { // A unique identifier of the tag. - Id string `json:"id"` + Id string `json:"id" url:"id"` // Name of the tag. - Name string `json:"name"` + Name string `json:"name" url:"name"` _rawJSON json.RawMessage } @@ -7213,8 +7285,8 @@ func (t *TagData) String() string { type SubscriptionTopic struct { // Topic ID - Id string `json:"id"` - Status SubscriptionTopicStatus `json:"status,omitempty"` + Id string `json:"id" url:"id"` + Status SubscriptionTopicStatus `json:"status,omitempty" url:"status,omitempty"` _rawJSON json.RawMessage } diff --git a/users/client/client.go b/users/client/client.go index dd8b30a..6e7d919 100644 --- a/users/client/client.go +++ b/users/client/client.go @@ -4,6 +4,7 @@ package client import ( core "github.com/trycourier/courier-go/v3/core" + option "github.com/trycourier/courier-go/v3/option" preferences "github.com/trycourier/courier-go/v3/users/preferences" tenants "github.com/trycourier/courier-go/v3/users/tenants" tokens "github.com/trycourier/courier-go/v3/users/tokens" @@ -20,14 +21,16 @@ type Client struct { Tokens *tokens.Client } -func NewClient(opts ...core.ClientOption) *Client { - options := core.NewClientOptions() - for _, opt := range opts { - opt(options) - } +func NewClient(opts ...option.RequestOption) *Client { + options := core.NewRequestOptions(opts...) return &Client{ - baseURL: options.BaseURL, - caller: core.NewCaller(options.HTTPClient), + baseURL: options.BaseURL, + caller: core.NewCaller( + &core.CallerParams{ + Client: options.HTTPClient, + MaxAttempts: options.MaxAttempts, + }, + ), header: options.ToHeader(), Preferences: preferences.NewClient(opts...), Tenants: tenants.NewClient(opts...), diff --git a/users/preferences.go b/users/preferences.go index 4ae2c25..08b9f8b 100644 --- a/users/preferences.go +++ b/users/preferences.go @@ -10,7 +10,7 @@ import ( ) type UserPreferencesGetResponse struct { - Topic *TopicPreference `json:"topic,omitempty"` + Topic *TopicPreference `json:"topic,omitempty" url:"topic,omitempty"` _rawJSON json.RawMessage } @@ -39,9 +39,9 @@ func (u *UserPreferencesGetResponse) String() string { } type UserPreferencesListResponse struct { - Paging *v3.Paging `json:"paging,omitempty"` + Paging *v3.Paging `json:"paging,omitempty" url:"paging,omitempty"` // The Preferences associated with the user_id. - Items []*TopicPreference `json:"items,omitempty"` + Items []*TopicPreference `json:"items,omitempty" url:"items,omitempty"` _rawJSON json.RawMessage } @@ -70,7 +70,7 @@ func (u *UserPreferencesListResponse) String() string { } type UserPreferencesUpdateResponse struct { - Message string `json:"message"` + Message string `json:"message" url:"message"` _rawJSON json.RawMessage } @@ -99,9 +99,9 @@ func (u *UserPreferencesUpdateResponse) String() string { } type UserPreferencesUpdateParams struct { - Status v3.PreferenceStatus `json:"status,omitempty"` + Status v3.PreferenceStatus `json:"status,omitempty" url:"status,omitempty"` // The Channels a user has chosen to receive notifications through for this topic - CustomRouting []v3.ChannelClassification `json:"custom_routing,omitempty"` - DefaultStatus v3.PreferenceStatus `json:"default_status,omitempty"` - HasCustomRouting *bool `json:"has_custom_routing,omitempty"` + CustomRouting []v3.ChannelClassification `json:"custom_routing,omitempty" url:"custom_routing,omitempty"` + DefaultStatus v3.PreferenceStatus `json:"default_status,omitempty" url:"default_status,omitempty"` + HasCustomRouting *bool `json:"has_custom_routing,omitempty" url:"has_custom_routing,omitempty"` } diff --git a/users/preferences/client.go b/users/preferences/client.go index fc9d063..fbc4268 100644 --- a/users/preferences/client.go +++ b/users/preferences/client.go @@ -10,6 +10,7 @@ import ( fmt "fmt" v3 "github.com/trycourier/courier-go/v3" core "github.com/trycourier/courier-go/v3/core" + option "github.com/trycourier/courier-go/v3/option" users "github.com/trycourier/courier-go/v3/users" io "io" http "net/http" @@ -21,28 +22,40 @@ type Client struct { header http.Header } -func NewClient(opts ...core.ClientOption) *Client { - options := core.NewClientOptions() - for _, opt := range opts { - opt(options) - } +func NewClient(opts ...option.RequestOption) *Client { + options := core.NewRequestOptions(opts...) return &Client{ baseURL: options.BaseURL, - caller: core.NewCaller(options.HTTPClient), - header: options.ToHeader(), + caller: core.NewCaller( + &core.CallerParams{ + Client: options.HTTPClient, + MaxAttempts: options.MaxAttempts, + }, + ), + header: options.ToHeader(), } } // Fetch all user preferences. -// -// A unique identifier associated with the user whose preferences you wish to retrieve. -func (c *Client) List(ctx context.Context, userId string) (*users.UserPreferencesListResponse, error) { +func (c *Client) List( + ctx context.Context, + // A unique identifier associated with the user whose preferences you wish to retrieve. + userId string, + opts ...option.RequestOption, +) (*users.UserPreferencesListResponse, error) { + options := core.NewRequestOptions(opts...) + baseURL := "https://api.courier.com" if c.baseURL != "" { baseURL = c.baseURL } + if options.BaseURL != "" { + baseURL = options.BaseURL + } endpointURL := fmt.Sprintf(baseURL+"/"+"users/%v/preferences", userId) + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + errorDecoder := func(statusCode int, body io.Reader) error { raw, err := io.ReadAll(body) if err != nil { @@ -68,7 +81,9 @@ func (c *Client) List(ctx context.Context, userId string) (*users.UserPreference &core.CallParams{ URL: endpointURL, Method: http.MethodGet, - Headers: c.header, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, Response: &response, ErrorDecoder: errorDecoder, }, @@ -79,16 +94,27 @@ func (c *Client) List(ctx context.Context, userId string) (*users.UserPreference } // Fetch user preferences for a specific subscription topic. -// -// A unique identifier associated with the user whose preferences you wish to retrieve. -// A unique identifier associated with a subscription topic. -func (c *Client) Get(ctx context.Context, userId string, topicId string) (*users.UserPreferencesGetResponse, error) { +func (c *Client) Get( + ctx context.Context, + // A unique identifier associated with the user whose preferences you wish to retrieve. + userId string, + // A unique identifier associated with a subscription topic. + topicId string, + opts ...option.RequestOption, +) (*users.UserPreferencesGetResponse, error) { + options := core.NewRequestOptions(opts...) + baseURL := "https://api.courier.com" if c.baseURL != "" { baseURL = c.baseURL } + if options.BaseURL != "" { + baseURL = options.BaseURL + } endpointURL := fmt.Sprintf(baseURL+"/"+"users/%v/preferences/%v", userId, topicId) + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + errorDecoder := func(statusCode int, body io.Reader) error { raw, err := io.ReadAll(body) if err != nil { @@ -114,7 +140,9 @@ func (c *Client) Get(ctx context.Context, userId string, topicId string) (*users &core.CallParams{ URL: endpointURL, Method: http.MethodGet, - Headers: c.header, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, Response: &response, ErrorDecoder: errorDecoder, }, @@ -125,16 +153,28 @@ func (c *Client) Get(ctx context.Context, userId string, topicId string) (*users } // Update or Create user preferences for a specific subscription topic. -// -// A unique identifier associated with the user whose preferences you wish to retrieve. -// A unique identifier associated with a subscription topic. -func (c *Client) Update(ctx context.Context, userId string, topicId string, request *users.UserPreferencesUpdateParams) (*users.UserPreferencesUpdateResponse, error) { +func (c *Client) Update( + ctx context.Context, + // A unique identifier associated with the user whose preferences you wish to retrieve. + userId string, + // A unique identifier associated with a subscription topic. + topicId string, + request *users.UserPreferencesUpdateParams, + opts ...option.RequestOption, +) (*users.UserPreferencesUpdateResponse, error) { + options := core.NewRequestOptions(opts...) + baseURL := "https://api.courier.com" if c.baseURL != "" { baseURL = c.baseURL } + if options.BaseURL != "" { + baseURL = options.BaseURL + } endpointURL := fmt.Sprintf(baseURL+"/"+"users/%v/preferences/%v", userId, topicId) + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + errorDecoder := func(statusCode int, body io.Reader) error { raw, err := io.ReadAll(body) if err != nil { @@ -160,7 +200,9 @@ func (c *Client) Update(ctx context.Context, userId string, topicId string, requ &core.CallParams{ URL: endpointURL, Method: http.MethodPut, - Headers: c.header, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, Request: request, Response: &response, ErrorDecoder: errorDecoder, diff --git a/users/tenants.go b/users/tenants.go index ef5f4e8..08bb51c 100644 --- a/users/tenants.go +++ b/users/tenants.go @@ -10,31 +10,31 @@ import ( ) type AddUserToSingleTenantsParams struct { - Profile *AddUserToSingleTenantsParamsProfile `json:"profile,omitempty"` + Profile *AddUserToSingleTenantsParamsProfile `json:"profile,omitempty" url:"profile,omitempty"` } type AddUserToMultipleTenantsParams struct { - Tenants []*v3.UserTenantAssociation `json:"tenants,omitempty"` + Tenants []*v3.UserTenantAssociation `json:"tenants,omitempty" url:"tenants,omitempty"` } type ListTenantsForUserParams struct { // The number of accounts to return // (defaults to 20, maximum value of 100) - Limit *int `json:"-"` + Limit *int `json:"-" url:"limit,omitempty"` // Continue the pagination with the next cursor - Cursor *string `json:"-"` + Cursor *string `json:"-" url:"cursor,omitempty"` } type AddUserToSingleTenantsParamsProfile struct { - Title string `json:"title"` + Title string `json:"title" url:"title"` // Email Address - Email string `json:"email"` + Email string `json:"email" url:"email"` // A valid phone number - PhoneNumber string `json:"phone_number"` + PhoneNumber string `json:"phone_number" url:"phone_number"` // The user's preferred ISO 639-1 language code. - Locale string `json:"locale"` + Locale string `json:"locale" url:"locale"` // Additional provider specific fields may be specified. - AdditionalFields map[string]interface{} `json:"additional_fields,omitempty"` + AdditionalFields map[string]interface{} `json:"additional_fields,omitempty" url:"additional_fields,omitempty"` _rawJSON json.RawMessage } @@ -63,17 +63,17 @@ func (a *AddUserToSingleTenantsParamsProfile) String() string { } type ListTenantsForUserResponse struct { - Items []*v3.UserTenantAssociation `json:"items,omitempty"` + Items []*v3.UserTenantAssociation `json:"items,omitempty" url:"items,omitempty"` // Set to true when there are more pages that can be retrieved. - HasMore bool `json:"has_more"` + HasMore bool `json:"has_more" url:"has_more"` // A url that may be used to generate these results. - Url string `json:"url"` + Url string `json:"url" url:"url"` // A url that may be used to generate fetch the next set of results. // Defined only when `has_more` is set to true - NextUrl *string `json:"next_url,omitempty"` + NextUrl *string `json:"next_url,omitempty" url:"next_url,omitempty"` // A pointer to the next page of results. Defined // only when `has_more` is set to true - Cursor *string `json:"cursor,omitempty"` + Cursor *string `json:"cursor,omitempty" url:"cursor,omitempty"` // Always set to `list`. Represents the type of this object. type_ string @@ -85,12 +85,16 @@ func (l *ListTenantsForUserResponse) Type() string { } func (l *ListTenantsForUserResponse) UnmarshalJSON(data []byte) error { - type unmarshaler ListTenantsForUserResponse - var value unmarshaler - if err := json.Unmarshal(data, &value); err != nil { + type embed ListTenantsForUserResponse + var unmarshaler = struct { + embed + }{ + embed: embed(*l), + } + if err := json.Unmarshal(data, &unmarshaler); err != nil { return err } - *l = ListTenantsForUserResponse(value) + *l = ListTenantsForUserResponse(unmarshaler.embed) l.type_ = "list" l._rawJSON = json.RawMessage(data) return nil diff --git a/users/tenants/client.go b/users/tenants/client.go index 2fce5e8..1f87385 100644 --- a/users/tenants/client.go +++ b/users/tenants/client.go @@ -6,9 +6,9 @@ import ( context "context" fmt "fmt" core "github.com/trycourier/courier-go/v3/core" + option "github.com/trycourier/courier-go/v3/option" users "github.com/trycourier/courier-go/v3/users" http "net/http" - url "net/url" ) type Client struct { @@ -17,15 +17,17 @@ type Client struct { header http.Header } -func NewClient(opts ...core.ClientOption) *Client { - options := core.NewClientOptions() - for _, opt := range opts { - opt(options) - } +func NewClient(opts ...option.RequestOption) *Client { + options := core.NewRequestOptions(opts...) return &Client{ baseURL: options.BaseURL, - caller: core.NewCaller(options.HTTPClient), - header: options.ToHeader(), + caller: core.NewCaller( + &core.CallerParams{ + Client: options.HTTPClient, + MaxAttempts: options.MaxAttempts, + }, + ), + header: options.ToHeader(), } } @@ -34,22 +36,35 @@ func NewClient(opts ...core.ClientOption) *Client { // A custom profile can also be supplied for each tenant. // This profile will be merged with the user's main // profile when sending to the user with that tenant. -// -// The user's ID. This can be any uniquely identifiable string. -func (c *Client) AddMultple(ctx context.Context, userId string, request *users.AddUserToMultipleTenantsParams) error { +func (c *Client) AddMultple( + ctx context.Context, + // The user's ID. This can be any uniquely identifiable string. + userId string, + request *users.AddUserToMultipleTenantsParams, + opts ...option.RequestOption, +) error { + options := core.NewRequestOptions(opts...) + baseURL := "https://api.courier.com" if c.baseURL != "" { baseURL = c.baseURL } + if options.BaseURL != "" { + baseURL = options.BaseURL + } endpointURL := fmt.Sprintf(baseURL+"/"+"users/%v/tenants", userId) + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + if err := c.caller.Call( ctx, &core.CallParams{ - URL: endpointURL, - Method: http.MethodPut, - Headers: c.header, - Request: request, + URL: endpointURL, + Method: http.MethodPut, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, + Request: request, }, ); err != nil { return err @@ -62,23 +77,37 @@ func (c *Client) AddMultple(ctx context.Context, userId string, request *users.A // A custom profile can also be supplied with the tenant. // This profile will be merged with the user's main profile // when sending to the user with that tenant. -// -// Id of the user to be added to the supplied tenant. -// Id of the tenant the user should be added to. -func (c *Client) Add(ctx context.Context, userId string, tenantId string, request *users.AddUserToSingleTenantsParams) error { +func (c *Client) Add( + ctx context.Context, + // Id of the user to be added to the supplied tenant. + userId string, + // Id of the tenant the user should be added to. + tenantId string, + request *users.AddUserToSingleTenantsParams, + opts ...option.RequestOption, +) error { + options := core.NewRequestOptions(opts...) + baseURL := "https://api.courier.com" if c.baseURL != "" { baseURL = c.baseURL } + if options.BaseURL != "" { + baseURL = options.BaseURL + } endpointURL := fmt.Sprintf(baseURL+"/"+"users/%v/tenants/%v", userId, tenantId) + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + if err := c.caller.Call( ctx, &core.CallParams{ - URL: endpointURL, - Method: http.MethodPut, - Headers: c.header, - Request: request, + URL: endpointURL, + Method: http.MethodPut, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, + Request: request, }, ); err != nil { return err @@ -87,21 +116,33 @@ func (c *Client) Add(ctx context.Context, userId string, tenantId string, reques } // Removes a user from any tenants they may have been associated with. -// -// Id of the user to be removed from the supplied tenant. -func (c *Client) RemoveAll(ctx context.Context, userId string) error { +func (c *Client) RemoveAll( + ctx context.Context, + // Id of the user to be removed from the supplied tenant. + userId string, + opts ...option.RequestOption, +) error { + options := core.NewRequestOptions(opts...) + baseURL := "https://api.courier.com" if c.baseURL != "" { baseURL = c.baseURL } + if options.BaseURL != "" { + baseURL = options.BaseURL + } endpointURL := fmt.Sprintf(baseURL+"/"+"users/%v/tenants", userId) + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + if err := c.caller.Call( ctx, &core.CallParams{ - URL: endpointURL, - Method: http.MethodDelete, - Headers: c.header, + URL: endpointURL, + Method: http.MethodDelete, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, }, ); err != nil { return err @@ -110,22 +151,35 @@ func (c *Client) RemoveAll(ctx context.Context, userId string) error { } // Removes a user from the supplied tenant. -// -// Id of the user to be removed from the supplied tenant. -// Id of the tenant the user should be removed from. -func (c *Client) Remove(ctx context.Context, userId string, tenantId string) error { +func (c *Client) Remove( + ctx context.Context, + // Id of the user to be removed from the supplied tenant. + userId string, + // Id of the tenant the user should be removed from. + tenantId string, + opts ...option.RequestOption, +) error { + options := core.NewRequestOptions(opts...) + baseURL := "https://api.courier.com" if c.baseURL != "" { baseURL = c.baseURL } + if options.BaseURL != "" { + baseURL = options.BaseURL + } endpointURL := fmt.Sprintf(baseURL+"/"+"users/%v/tenants/%v", userId, tenantId) + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + if err := c.caller.Call( ctx, &core.CallParams{ - URL: endpointURL, - Method: http.MethodDelete, - Headers: c.header, + URL: endpointURL, + Method: http.MethodDelete, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, }, ); err != nil { return err @@ -134,34 +188,44 @@ func (c *Client) Remove(ctx context.Context, userId string, tenantId string) err } // Returns a paginated list of user tenant associations. -// -// Id of the user to retrieve all associated tenants for. -func (c *Client) List(ctx context.Context, userId string, request *users.ListTenantsForUserParams) (*users.ListTenantsForUserResponse, error) { +func (c *Client) List( + ctx context.Context, + // Id of the user to retrieve all associated tenants for. + userId string, + request *users.ListTenantsForUserParams, + opts ...option.RequestOption, +) (*users.ListTenantsForUserResponse, error) { + options := core.NewRequestOptions(opts...) + baseURL := "https://api.courier.com" if c.baseURL != "" { baseURL = c.baseURL } + if options.BaseURL != "" { + baseURL = options.BaseURL + } endpointURL := fmt.Sprintf(baseURL+"/"+"users/%v/tenants", userId) - queryParams := make(url.Values) - if request.Limit != nil { - queryParams.Add("limit", fmt.Sprintf("%v", *request.Limit)) - } - if request.Cursor != nil { - queryParams.Add("cursor", fmt.Sprintf("%v", *request.Cursor)) + queryParams, err := core.QueryValues(request) + if err != nil { + return nil, err } if len(queryParams) > 0 { endpointURL += "?" + queryParams.Encode() } + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + var response *users.ListTenantsForUserResponse if err := c.caller.Call( ctx, &core.CallParams{ - URL: endpointURL, - Method: http.MethodGet, - Headers: c.header, - Response: &response, + URL: endpointURL, + Method: http.MethodGet, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, + Response: &response, }, ); err != nil { return nil, err diff --git a/users/tokens.go b/users/tokens.go index 5b70ed0..94aa709 100644 --- a/users/tokens.go +++ b/users/tokens.go @@ -13,19 +13,19 @@ type GetAllTokensResponse = []*UserToken type GetUserTokenResponse struct { // Full body of the token. Must match token in URL. - Token *string `json:"token,omitempty"` - ProviderKey ProviderKey `json:"provider_key,omitempty"` + Token *string `json:"token,omitempty" url:"token,omitempty"` + ProviderKey ProviderKey `json:"provider_key,omitempty" url:"provider_key,omitempty"` // ISO 8601 formatted date the token expires. Defaults to 2 months. Set to false to disable expiration. - ExpiryDate *ExpiryDate `json:"expiry_date,omitempty"` + ExpiryDate *ExpiryDate `json:"expiry_date,omitempty" url:"expiry_date,omitempty"` // Properties sent to the provider along with the token - Properties interface{} `json:"properties,omitempty"` + Properties interface{} `json:"properties,omitempty" url:"properties,omitempty"` // Information about the device the token is associated with. - Device *Device `json:"device,omitempty"` + Device *Device `json:"device,omitempty" url:"device,omitempty"` // Information about the device the token is associated with. - Tracking *Tracking `json:"tracking,omitempty"` - Status *TokenStatus `json:"status,omitempty"` + Tracking *Tracking `json:"tracking,omitempty" url:"tracking,omitempty"` + Status *TokenStatus `json:"status,omitempty" url:"status,omitempty"` // The reason for the token status. - StatusReason *string `json:"status_reason,omitempty"` + StatusReason *string `json:"status_reason,omitempty" url:"status_reason,omitempty"` _rawJSON json.RawMessage } @@ -54,7 +54,7 @@ func (g *GetUserTokenResponse) String() string { } type PatchUserTokenOpts struct { - Patch []*PatchOperation `json:"patch,omitempty"` + Patch []*PatchOperation `json:"patch,omitempty" url:"patch,omitempty"` _rawJSON json.RawMessage } @@ -84,16 +84,16 @@ func (p *PatchUserTokenOpts) String() string { type UserToken struct { // Full body of the token. Must match token in URL. - Token *string `json:"token,omitempty"` - ProviderKey ProviderKey `json:"provider_key,omitempty"` + Token *string `json:"token,omitempty" url:"token,omitempty"` + ProviderKey ProviderKey `json:"provider_key,omitempty" url:"provider_key,omitempty"` // ISO 8601 formatted date the token expires. Defaults to 2 months. Set to false to disable expiration. - ExpiryDate *ExpiryDate `json:"expiry_date,omitempty"` + ExpiryDate *ExpiryDate `json:"expiry_date,omitempty" url:"expiry_date,omitempty"` // Properties sent to the provider along with the token - Properties interface{} `json:"properties,omitempty"` + Properties interface{} `json:"properties,omitempty" url:"properties,omitempty"` // Information about the device the token is associated with. - Device *Device `json:"device,omitempty"` + Device *Device `json:"device,omitempty" url:"device,omitempty"` // Information about the device the token is associated with. - Tracking *Tracking `json:"tracking,omitempty"` + Tracking *Tracking `json:"tracking,omitempty" url:"tracking,omitempty"` _rawJSON json.RawMessage } diff --git a/users/tokens/client.go b/users/tokens/client.go index f8626d3..4f372cf 100644 --- a/users/tokens/client.go +++ b/users/tokens/client.go @@ -10,6 +10,7 @@ import ( fmt "fmt" v3 "github.com/trycourier/courier-go/v3" core "github.com/trycourier/courier-go/v3/core" + option "github.com/trycourier/courier-go/v3/option" users "github.com/trycourier/courier-go/v3/users" io "io" http "net/http" @@ -21,28 +22,40 @@ type Client struct { header http.Header } -func NewClient(opts ...core.ClientOption) *Client { - options := core.NewClientOptions() - for _, opt := range opts { - opt(options) - } +func NewClient(opts ...option.RequestOption) *Client { + options := core.NewRequestOptions(opts...) return &Client{ baseURL: options.BaseURL, - caller: core.NewCaller(options.HTTPClient), - header: options.ToHeader(), + caller: core.NewCaller( + &core.CallerParams{ + Client: options.HTTPClient, + MaxAttempts: options.MaxAttempts, + }, + ), + header: options.ToHeader(), } } // Adds multiple tokens to a user and overwrites matching existing tokens. -// -// The user's ID. This can be any uniquely identifiable string. -func (c *Client) AddMultiple(ctx context.Context, userId string) error { +func (c *Client) AddMultiple( + ctx context.Context, + // The user's ID. This can be any uniquely identifiable string. + userId string, + opts ...option.RequestOption, +) error { + options := core.NewRequestOptions(opts...) + baseURL := "https://api.courier.com" if c.baseURL != "" { baseURL = c.baseURL } + if options.BaseURL != "" { + baseURL = options.BaseURL + } endpointURL := fmt.Sprintf(baseURL+"/"+"users/%v/tokens", userId) + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + errorDecoder := func(statusCode int, body io.Reader) error { raw, err := io.ReadAll(body) if err != nil { @@ -67,7 +80,9 @@ func (c *Client) AddMultiple(ctx context.Context, userId string) error { &core.CallParams{ URL: endpointURL, Method: http.MethodPut, - Headers: c.header, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, ErrorDecoder: errorDecoder, }, ); err != nil { @@ -77,16 +92,28 @@ func (c *Client) AddMultiple(ctx context.Context, userId string) error { } // Adds a single token to a user and overwrites a matching existing token. -// -// The user's ID. This can be any uniquely identifiable string. -// The full token string. -func (c *Client) Add(ctx context.Context, userId string, token string, request *users.UserToken) error { +func (c *Client) Add( + ctx context.Context, + // The user's ID. This can be any uniquely identifiable string. + userId string, + // The full token string. + token string, + request *users.UserToken, + opts ...option.RequestOption, +) error { + options := core.NewRequestOptions(opts...) + baseURL := "https://api.courier.com" if c.baseURL != "" { baseURL = c.baseURL } + if options.BaseURL != "" { + baseURL = options.BaseURL + } endpointURL := fmt.Sprintf(baseURL+"/"+"users/%v/tokens/%v", userId, token) + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + errorDecoder := func(statusCode int, body io.Reader) error { raw, err := io.ReadAll(body) if err != nil { @@ -111,7 +138,9 @@ func (c *Client) Add(ctx context.Context, userId string, token string, request * &core.CallParams{ URL: endpointURL, Method: http.MethodPut, - Headers: c.header, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, Request: request, ErrorDecoder: errorDecoder, }, @@ -122,16 +151,28 @@ func (c *Client) Add(ctx context.Context, userId string, token string, request * } // Apply a JSON Patch (RFC 6902) to the specified token. -// -// The user's ID. This can be any uniquely identifiable string. -// The full token string. -func (c *Client) Update(ctx context.Context, userId string, token string, request *users.PatchUserTokenOpts) error { +func (c *Client) Update( + ctx context.Context, + // The user's ID. This can be any uniquely identifiable string. + userId string, + // The full token string. + token string, + request *users.PatchUserTokenOpts, + opts ...option.RequestOption, +) error { + options := core.NewRequestOptions(opts...) + baseURL := "https://api.courier.com" if c.baseURL != "" { baseURL = c.baseURL } + if options.BaseURL != "" { + baseURL = options.BaseURL + } endpointURL := fmt.Sprintf(baseURL+"/"+"users/%v/tokens/%v", userId, token) + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + errorDecoder := func(statusCode int, body io.Reader) error { raw, err := io.ReadAll(body) if err != nil { @@ -156,7 +197,9 @@ func (c *Client) Update(ctx context.Context, userId string, token string, reques &core.CallParams{ URL: endpointURL, Method: http.MethodPatch, - Headers: c.header, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, Request: request, ErrorDecoder: errorDecoder, }, @@ -167,16 +210,27 @@ func (c *Client) Update(ctx context.Context, userId string, token string, reques } // Get single token available for a `:token` -// -// The user's ID. This can be any uniquely identifiable string. -// The full token string. -func (c *Client) Get(ctx context.Context, userId string, token string) (*users.GetUserTokenResponse, error) { +func (c *Client) Get( + ctx context.Context, + // The user's ID. This can be any uniquely identifiable string. + userId string, + // The full token string. + token string, + opts ...option.RequestOption, +) (*users.GetUserTokenResponse, error) { + options := core.NewRequestOptions(opts...) + baseURL := "https://api.courier.com" if c.baseURL != "" { baseURL = c.baseURL } + if options.BaseURL != "" { + baseURL = options.BaseURL + } endpointURL := fmt.Sprintf(baseURL+"/"+"users/%v/tokens/%v", userId, token) + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + errorDecoder := func(statusCode int, body io.Reader) error { raw, err := io.ReadAll(body) if err != nil { @@ -202,7 +256,9 @@ func (c *Client) Get(ctx context.Context, userId string, token string) (*users.G &core.CallParams{ URL: endpointURL, Method: http.MethodGet, - Headers: c.header, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, Response: &response, ErrorDecoder: errorDecoder, }, @@ -213,15 +269,25 @@ func (c *Client) Get(ctx context.Context, userId string, token string) (*users.G } // Gets all tokens available for a :user_id -// -// The user's ID. This can be any uniquely identifiable string. -func (c *Client) List(ctx context.Context, userId string) (users.GetAllTokensResponse, error) { +func (c *Client) List( + ctx context.Context, + // The user's ID. This can be any uniquely identifiable string. + userId string, + opts ...option.RequestOption, +) (users.GetAllTokensResponse, error) { + options := core.NewRequestOptions(opts...) + baseURL := "https://api.courier.com" if c.baseURL != "" { baseURL = c.baseURL } + if options.BaseURL != "" { + baseURL = options.BaseURL + } endpointURL := fmt.Sprintf(baseURL+"/"+"users/%v/tokens", userId) + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + errorDecoder := func(statusCode int, body io.Reader) error { raw, err := io.ReadAll(body) if err != nil { @@ -247,7 +313,9 @@ func (c *Client) List(ctx context.Context, userId string) (users.GetAllTokensRes &core.CallParams{ URL: endpointURL, Method: http.MethodGet, - Headers: c.header, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, Response: &response, ErrorDecoder: errorDecoder, }, diff --git a/users/types.go b/users/types.go index 5c57669..7ae79ad 100644 --- a/users/types.go +++ b/users/types.go @@ -11,12 +11,12 @@ import ( type TopicPreference struct { // The Channels a user has chosen to receive notifications through for this topic - CustomRouting []v3.ChannelClassification `json:"custom_routing,omitempty"` - DefaultStatus v3.PreferenceStatus `json:"default_status,omitempty"` - HasCustomRouting *bool `json:"has_custom_routing,omitempty"` - Status v3.PreferenceStatus `json:"status,omitempty"` - TopicId string `json:"topic_id"` - TopicName string `json:"topic_name"` + CustomRouting []v3.ChannelClassification `json:"custom_routing,omitempty" url:"custom_routing,omitempty"` + DefaultStatus v3.PreferenceStatus `json:"default_status,omitempty" url:"default_status,omitempty"` + HasCustomRouting *bool `json:"has_custom_routing,omitempty" url:"has_custom_routing,omitempty"` + Status v3.PreferenceStatus `json:"status,omitempty" url:"status,omitempty"` + TopicId string `json:"topic_id" url:"topic_id"` + TopicName string `json:"topic_name" url:"topic_name"` _rawJSON json.RawMessage } @@ -45,8 +45,8 @@ func (t *TopicPreference) String() string { } type DeleteUserTokenOpts struct { - UserId string `json:"user_id"` - Token string `json:"token"` + UserId string `json:"user_id" url:"user_id"` + Token string `json:"token" url:"token"` _rawJSON json.RawMessage } @@ -76,17 +76,17 @@ func (d *DeleteUserTokenOpts) String() string { type Device struct { // Id of the application the token is used for - AppId *string `json:"app_id,omitempty"` + AppId *string `json:"app_id,omitempty" url:"app_id,omitempty"` // Id of the advertising identifier - AdId *string `json:"ad_id,omitempty"` + AdId *string `json:"ad_id,omitempty" url:"ad_id,omitempty"` // Id of the device the token is associated with - DeviceId *string `json:"device_id,omitempty"` + DeviceId *string `json:"device_id,omitempty" url:"device_id,omitempty"` // The device platform i.e. android, ios, web - Platform *string `json:"platform,omitempty"` + Platform *string `json:"platform,omitempty" url:"platform,omitempty"` // The device manufacturer - Manufacturer *string `json:"manufacturer,omitempty"` + Manufacturer *string `json:"manufacturer,omitempty" url:"manufacturer,omitempty"` // The device model - Model *string `json:"model,omitempty"` + Model *string `json:"model,omitempty" url:"model,omitempty"` _rawJSON json.RawMessage } @@ -172,8 +172,8 @@ func (e *ExpiryDate) Accept(visitor ExpiryDateVisitor) error { } type GetUserTokenOpts struct { - UserId string `json:"user_id"` - Token string `json:"token"` + UserId string `json:"user_id" url:"user_id"` + Token string `json:"token" url:"token"` _rawJSON json.RawMessage } @@ -202,7 +202,7 @@ func (g *GetUserTokenOpts) String() string { } type GetUserTokensOpts struct { - UserId string `json:"user_id"` + UserId string `json:"user_id" url:"user_id"` _rawJSON json.RawMessage } @@ -266,11 +266,11 @@ func (p PatchOp) Ptr() *PatchOp { type PatchOperation struct { // The operation to perform. - Op string `json:"op"` + Op string `json:"op" url:"op"` // The JSON path specifying the part of the profile to operate on. - Path string `json:"path"` + Path string `json:"path" url:"path"` // The value for the operation. - Value *string `json:"value,omitempty"` + Value *string `json:"value,omitempty" url:"value,omitempty"` _rawJSON json.RawMessage } @@ -327,8 +327,8 @@ func (p ProviderKey) Ptr() *ProviderKey { } type PutUserTokenOpts struct { - UserId string `json:"user_id"` - Token *UserToken `json:"token,omitempty"` + UserId string `json:"user_id" url:"user_id"` + Token *UserToken `json:"token,omitempty" url:"token,omitempty"` _rawJSON json.RawMessage } @@ -357,8 +357,8 @@ func (p *PutUserTokenOpts) String() string { } type PutUserTokensOpts struct { - UserId string `json:"user_id"` - Tokens []*UserToken `json:"tokens,omitempty"` + UserId string `json:"user_id" url:"user_id"` + Tokens []*UserToken `json:"tokens,omitempty" url:"tokens,omitempty"` _rawJSON json.RawMessage } @@ -416,13 +416,13 @@ func (t TokenStatus) Ptr() *TokenStatus { type Tracking struct { // The operating system version - OsVersion *string `json:"os_version,omitempty"` + OsVersion *string `json:"os_version,omitempty" url:"os_version,omitempty"` // The IP address of the device - Ip *string `json:"ip,omitempty"` + Ip *string `json:"ip,omitempty" url:"ip,omitempty"` // The latitude of the device - Lat *string `json:"lat,omitempty"` + Lat *string `json:"lat,omitempty" url:"lat,omitempty"` // The longitude of the device - Long *string `json:"long,omitempty"` + Long *string `json:"long,omitempty" url:"long,omitempty"` _rawJSON json.RawMessage }