From cbf6e3122833339740b821c19a98ccf6c5bf59c7 Mon Sep 17 00:00:00 2001 From: Lukas Bindreiter Date: Mon, 18 Nov 2024 14:59:10 +0100 Subject: [PATCH] Handcrafted SDK instead of auto-generated one (#2) --- LICENSE | 21 + README.md | 110 +- client.go | 1590 +++------------------ examples/contact-crud/main.go | 55 + examples/send-event/main.go | 30 + examples/send-transactional-email/main.go | 30 + generate.go | 3 - go.mod | 3 - go.sum | 11 - models.go | 109 ++ oapi_codegen_config.yaml | 6 - openapi.json | 935 ------------ 12 files changed, 535 insertions(+), 2368 deletions(-) create mode 100644 LICENSE create mode 100644 examples/contact-crud/main.go create mode 100644 examples/send-event/main.go create mode 100644 examples/send-transactional-email/main.go delete mode 100644 generate.go create mode 100644 models.go delete mode 100644 oapi_codegen_config.yaml delete mode 100644 openapi.json diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..ca45ccd --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Tilebox, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 80c4b67..88b6840 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,115 @@ ## Introduction -A Go SDK for interacting with [Loops's] API - generated from their [OpenAPI Spec](https://app.loops.so/openapi.json) using [oapi-codegen](https://github.com/oapi-codegen/oapi-codegen). +A Go SDK for interacting with [Loops's](https://loops.so) API. + +## Usage + +Below are a few examples of how to use the SDK to send API requests. +For some full, working examples, see the [examples](examples) directory. + +**Create a client**: + +```go +package main + +import ( + "context" + "github.com/tilebox/loops-go" + "log/slog" +) + +func main() { + ctx := context.Background() + client, err := loops.NewClient(loops.WithApiKey("YOUR_LOOPS_API_KEY")) + if err != nil { + slog.Error("failed to create client", slog.Any("error", err.Error())) + return + } + + // now use the client to make requests +} +``` + +### Contacts + +**Find a contact** +```go +contactToFind := &loops.ContactIdentifier{ + Email: loops.String("neil.armstrong@moon.space"), +} + +contact, err := client.FindContact(ctx, contactToFind) +if err != nil { + slog.Error("failed to find contact", slog.Any("error", err.Error())) + return +} +``` + +**Create a contact** +```go +newContact := &loops.Contact{ + Email: "neil.armstrong@moon.space", + FirstName: loops.String("Neil"), + LastName: loops.String("Armstrong"), + UserGroup: loops.String("Astronauts"), + Subscribed: true, +} + +contactID, err := client.CreateContact(ctx, newContact) +if err != nil { + slog.Error("failed to create contact", slog.Any("error", err.Error())) + return +} +``` + +**Delete a contact** +```go +contactToDelete := &loops.ContactIdentifier{ + Email: loops.String("neil.armstrong@moon.space"), +} + +err = client.DeleteContact(ctx, contactToDelete) +if err != nil { + slog.Error("failed to delete contact", slog.Any("error", err.Error())) + return +} +``` + +### Events + +**Send an event** +```go +err = client.SendEvent(ctx, &loops.Event{ + Email: loops.String("neil.armstrong@moon.space"), + EventName: "joinedMission", + EventProperties: &map[string]interface{}{ + "mission": "Apollo 11", + }, +}) +if err != nil { + slog.Error("failed to send event", slog.Any("error", err.Error())) + return +} +``` + +### Transactional emails + +**Send a transactional email** + +```go +err = client.SendTransactionalEmail(ctx, &loops.TransactionalEmail{ + TransactionalId: "cm...", + Email: "recipient@example.com", + DataVariables: &map[string]interface{}{ + "name": "Recipient Name", + }, +}) +if err != nil { + slog.Error("failed to send transactional email", slog.Any("error", err.Error())) + return +} +``` ## Installation diff --git a/client.go b/client.go index 90156cc..44e63de 100644 --- a/client.go +++ b/client.go @@ -1,1558 +1,330 @@ -// Package loops provides primitives to interact with the openapi HTTP API. -// -// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.4.1 DO NOT EDIT. package loops import ( "bytes" "context" "encoding/json" + "errors" "fmt" "io" "net/http" "net/url" - "strings" - - "github.com/oapi-codegen/runtime" -) - -const ( - ApiKeyScopes = "apiKey.Scopes" ) -// Contact defines model for Contact. -type Contact struct { - Email *string `json:"email,omitempty"` - FirstName *string `json:"firstName,omitempty"` - Id *string `json:"id,omitempty"` - LastName *string `json:"lastName,omitempty"` - - // MailingLists An object of mailing list IDs and boolean subscription statuses. - MailingLists *map[string]interface{} `json:"mailingLists,omitempty"` - Source *string `json:"source,omitempty"` - Subscribed *bool `json:"subscribed,omitempty"` - UserGroup *string `json:"userGroup,omitempty"` - UserId *string `json:"userId,omitempty"` -} - -// ContactDeleteRequest defines model for ContactDeleteRequest. -type ContactDeleteRequest struct { - Email string `json:"email"` - UserId string `json:"userId"` -} - -// ContactDeleteResponse defines model for ContactDeleteResponse. -type ContactDeleteResponse struct { - Message string `json:"message"` - Success bool `json:"success"` -} - -// ContactFailureResponse defines model for ContactFailureResponse. -type ContactFailureResponse struct { - Message string `json:"message"` - Success bool `json:"success"` -} - -// ContactRequest defines model for ContactRequest. -type ContactRequest struct { - Email string `json:"email"` - FirstName *string `json:"firstName,omitempty"` - LastName *string `json:"lastName,omitempty"` - - // MailingLists An object of mailing list IDs and boolean subscription statuses. - MailingLists *map[string]interface{} `json:"mailingLists,omitempty"` - Subscribed *bool `json:"subscribed,omitempty"` - UserGroup *string `json:"userGroup,omitempty"` - UserId *string `json:"userId,omitempty"` -} - -// ContactSuccessResponse defines model for ContactSuccessResponse. -type ContactSuccessResponse struct { - Id string `json:"id"` - Success bool `json:"success"` -} - -// CustomField defines model for CustomField. -type CustomField struct { - Key string `json:"key"` - Label string `json:"label"` - Type string `json:"type"` -} - -// EventFailureResponse defines model for EventFailureResponse. -type EventFailureResponse struct { - Message string `json:"message"` - Success bool `json:"success"` -} - -// EventRequest defines model for EventRequest. -type EventRequest struct { - Email *string `json:"email,omitempty"` - EventName string `json:"eventName"` - - // EventProperties An object containing event property data for the event, available in emails sent by the event. - EventProperties *map[string]interface{} `json:"eventProperties,omitempty"` - - // MailingLists An object of mailing list IDs and boolean subscription statuses. - MailingLists *map[string]interface{} `json:"mailingLists,omitempty"` - UserId *string `json:"userId,omitempty"` -} - -// EventSuccessResponse defines model for EventSuccessResponse. -type EventSuccessResponse struct { - Success bool `json:"success"` -} - -// MailingList defines model for MailingList. -type MailingList struct { - Id string `json:"id"` - IsPublic bool `json:"isPublic"` - Name string `json:"name"` -} - -// TransactionalFailure2Response defines model for TransactionalFailure2Response. -type TransactionalFailure2Response struct { - Error struct { - Message *string `json:"message,omitempty"` - Path *string `json:"path,omitempty"` - } `json:"error"` - Success bool `json:"success"` -} - -// TransactionalFailure3Response defines model for TransactionalFailure3Response. -type TransactionalFailure3Response struct { - Message string `json:"message"` - Success bool `json:"success"` -} - -// TransactionalFailureResponse defines model for TransactionalFailureResponse. -type TransactionalFailureResponse struct { - Message string `json:"message"` - Path string `json:"path"` - Success bool `json:"success"` -} - -// TransactionalRequest defines model for TransactionalRequest. -type TransactionalRequest struct { - // AddToAudience If `true`, a contact will be created in your audience using the `email` value (if a matching contact doesn't already exist). - AddToAudience *bool `json:"addToAudience,omitempty"` - - // Attachments A list containing file objects to be sent along with an email message. - Attachments *[]struct { - // ContentType The MIME type of the file. - ContentType string `json:"contentType"` - - // Data The base64-encoded content of the file. - Data string `json:"data"` - - // Filename The name of the file, shown in email clients. - Filename string `json:"filename"` - } `json:"attachments,omitempty"` - - // DataVariables An object containing contact data as defined by the data variables added to the transactional email template. - DataVariables *map[string]interface{} `json:"dataVariables,omitempty"` - Email string `json:"email"` - - // TransactionalId The ID of the transactional email to send. - TransactionalId string `json:"transactionalId"` -} - -// TransactionalSuccessResponse defines model for TransactionalSuccessResponse. -type TransactionalSuccessResponse struct { - Success bool `json:"success"` -} - -// GetContactsFindParams defines parameters for GetContactsFind. -type GetContactsFindParams struct { - // Email Email address (URI-encoded) - Email *string `form:"email,omitempty" json:"email,omitempty"` - UserId *string `form:"userId,omitempty" json:"userId,omitempty"` -} - -// PostContactsCreateJSONRequestBody defines body for PostContactsCreate for application/json ContentType. -type PostContactsCreateJSONRequestBody = ContactRequest - -// PostContactsDeleteJSONRequestBody defines body for PostContactsDelete for application/json ContentType. -type PostContactsDeleteJSONRequestBody = ContactDeleteRequest - -// PutContactsUpdateJSONRequestBody defines body for PutContactsUpdate for application/json ContentType. -type PutContactsUpdateJSONRequestBody = ContactRequest +const defaultApiURL = "https://app.loops.so/api/v1/" -// PostEventsSendJSONRequestBody defines body for PostEventsSend for application/json ContentType. -type PostEventsSendJSONRequestBody = EventRequest +var ErrContactNotFound = errors.New("contact not found") -// PostTransactionalJSONRequestBody defines body for PostTransactional for application/json ContentType. -type PostTransactionalJSONRequestBody = TransactionalRequest - -// RequestEditorFn is the function signature for the RequestEditor callback function -type RequestEditorFn func(ctx context.Context, req *http.Request) error - -// Doer performs HTTP requests. -// -// The standard http.Client implements this interface. -type HttpRequestDoer interface { +type HttpClient interface { Do(req *http.Request) (*http.Response, error) } -// Client which conforms to the OpenAPI3 specification for this service. -type Client struct { - // The endpoint of the server conforming to this interface, with scheme, - // https://api.deepmap.com for example. This can contain a path relative - // to the server, such as https://api.deepmap.com/dev-test, and all the - // paths in the swagger spec will be appended to the server. - Server string - - // Doer for performing requests, typically a *http.Client with any - // customized settings, such as certificate chains. - Client HttpRequestDoer +type RequestInterceptor func(ctx context.Context, req *http.Request) error - // A list of callbacks for modifying requests which are generated before sending over - // the network. - RequestEditors []RequestEditorFn +type Client struct { + apiURL *url.URL + httpClient HttpClient + requestInterceptors []RequestInterceptor } -// ClientOption allows setting custom parameters during construction -type ClientOption func(*Client) error - -// Creates a new Client, with reasonable defaults -func NewClient(server string, opts ...ClientOption) (*Client, error) { - // create a client with sane default values - client := Client{ - Server: server, +// NewClient creates a new Loops client. +func NewClient(opts ...ClientOption) (*Client, error) { + config := clientConfig{ + apiURL: defaultApiURL, + httpClient: http.DefaultClient, } - // mutate client and add all optional params for _, o := range opts { - if err := o(&client); err != nil { - return nil, err - } - } - // ensure the server URL always has a trailing slash - if !strings.HasSuffix(client.Server, "/") { - client.Server += "/" + o(&config) } - // create httpClient, if not already present - if client.Client == nil { - client.Client = &http.Client{} + apiURL, err := url.Parse(config.apiURL) + if err != nil { + return nil, fmt.Errorf("invalid api url: %w", err) } - return &client, nil -} -// WithHTTPClient allows overriding the default Doer, which is -// automatically created using http.Client. This is useful for tests. -func WithHTTPClient(doer HttpRequestDoer) ClientOption { - return func(c *Client) error { - c.Client = doer - return nil - } -} + requestInterceptors := config.requestInterceptors -// WithRequestEditorFn allows setting up a callback function, which will be -// called right before sending the request. This can be used to mutate the request. -func WithRequestEditorFn(fn RequestEditorFn) ClientOption { - return func(c *Client) error { - c.RequestEditors = append(c.RequestEditors, fn) - return nil + if config.apiKey != "" { + requestInterceptors = append(requestInterceptors, func(ctx context.Context, req *http.Request) error { + bearerToken := fmt.Sprintf("Bearer %s", config.apiKey) + req.Header.Set("Authorization", bearerToken) + return nil + }) } -} - -// The interface specification for the client above. -type ClientInterface interface { - // GetApiKey request - GetApiKey(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) - - // PostContactsCreateWithBody request with any body - PostContactsCreateWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) - - PostContactsCreate(ctx context.Context, body PostContactsCreateJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) - - // GetContactsCustomFields request - GetContactsCustomFields(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) - - // PostContactsDeleteWithBody request with any body - PostContactsDeleteWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) - - PostContactsDelete(ctx context.Context, body PostContactsDeleteJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) - - // GetContactsFind request - GetContactsFind(ctx context.Context, params *GetContactsFindParams, reqEditors ...RequestEditorFn) (*http.Response, error) - // PutContactsUpdateWithBody request with any body - PutContactsUpdateWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) - - PutContactsUpdate(ctx context.Context, body PutContactsUpdateJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) - - // PostEventsSendWithBody request with any body - PostEventsSendWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) - - PostEventsSend(ctx context.Context, body PostEventsSendJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) - - // GetLists request - GetLists(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) - - // PostTransactionalWithBody request with any body - PostTransactionalWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + requestInterceptors = append(requestInterceptors, func(ctx context.Context, req *http.Request) error { + req.Header.Set("Content-Type", "application/json") + return nil + }) - PostTransactional(ctx context.Context, body PostTransactionalJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + return &Client{ + apiURL: apiURL, + httpClient: config.httpClient, + requestInterceptors: requestInterceptors, + }, nil } -func (c *Client) GetApiKey(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewGetApiKeyRequest(c.Server) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) +type clientConfig struct { + apiURL string + apiKey string + httpClient HttpClient + requestInterceptors []RequestInterceptor } -func (c *Client) PostContactsCreateWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewPostContactsCreateRequestWithBody(c.Server, contentType, body) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) -} +// ClientOption allows setting custom parameters during construction +type ClientOption func(*clientConfig) -func (c *Client) PostContactsCreate(ctx context.Context, body PostContactsCreateJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewPostContactsCreateRequest(c.Server, body) - if err != nil { - return nil, err +// WithApiURL allows overriding the default API URL +func WithApiURL(apiURL string) ClientOption { + return func(c *clientConfig) { + c.apiURL = apiURL } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) } -func (c *Client) GetContactsCustomFields(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewGetContactsCustomFieldsRequest(c.Server) - if err != nil { - return nil, err +// WithApiKey sets the loops API key to use +func WithApiKey(apiKey string) ClientOption { + return func(c *clientConfig) { + c.apiKey = apiKey } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) } -func (c *Client) PostContactsDeleteWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewPostContactsDeleteRequestWithBody(c.Server, contentType, body) - if err != nil { - return nil, err +// WithHttpClient allows overriding the default http client, in case you want to use a custom one (e.g. retryablehttp) +// or for testing purposes (e.g. mocking) +func WithHttpClient(httpClient HttpClient) ClientOption { + return func(c *clientConfig) { + c.httpClient = httpClient } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) } -func (c *Client) PostContactsDelete(ctx context.Context, body PostContactsDeleteJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewPostContactsDeleteRequest(c.Server, body) - if err != nil { - return nil, err +// WithRequestInterceptors allows adding custom request interceptors, modifying API requests before they are sent +func WithRequestInterceptors(requestInterceptors ...RequestInterceptor) ClientOption { + return func(c *clientConfig) { + c.requestInterceptors = append(c.requestInterceptors, requestInterceptors...) } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) } -func (c *Client) GetContactsFind(ctx context.Context, params *GetContactsFindParams, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewGetContactsFindRequest(c.Server, params) +// CreateContact creates a new contact with an email address and any other contact properties. +// See: https://loops.so/docs/api-reference/create-contact +func (c *Client) CreateContact(ctx context.Context, contact *Contact) (string, error) { + req, err := newRequestWithBody(c, ctx, http.MethodPost, "/contacts/create", contact) if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err + return "", err } - return c.Client.Do(req) -} -func (c *Client) PutContactsUpdateWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewPutContactsUpdateRequestWithBody(c.Server, contentType, body) + response, err := sendRequest[*IDResponse](c, req) if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err + return "", err } - return c.Client.Do(req) + return response.ID, err } -func (c *Client) PutContactsUpdate(ctx context.Context, body PutContactsUpdateJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewPutContactsUpdateRequest(c.Server, body) +// UpdateContact updates or creates a contact. +// See: https://loops.so/docs/api-reference/update-contact +func (c *Client) UpdateContact(ctx context.Context, contact *Contact) (string, error) { + req, err := newRequestWithBody(c, ctx, http.MethodPut, "/contacts/update", contact) if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err + return "", err } - return c.Client.Do(req) -} -func (c *Client) PostEventsSendWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewPostEventsSendRequestWithBody(c.Server, contentType, body) + response, err := sendRequest[*IDResponse](c, req) if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err + return "", err } - return c.Client.Do(req) + return response.ID, err } -func (c *Client) PostEventsSend(ctx context.Context, body PostEventsSendJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewPostEventsSendRequest(c.Server, body) - if err != nil { - return nil, err +// FindContact finds a contact by email or userId. +// See: https://loops.so/docs/api-reference/find-contact +func (c *Client) FindContact(ctx context.Context, contact *ContactIdentifier) (*Contact, error) { + if contact.Email == nil && contact.UserId == nil { + return nil, errors.New("contact identifier must contain either an email or a userId") } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err + if contact.Email != nil && contact.UserId != nil { + return nil, errors.New("contact identifier must contain either an email or a userId, but not both") } - return c.Client.Do(req) -} -func (c *Client) GetLists(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewGetListsRequest(c.Server) - if err != nil { - return nil, err + params := url.Values{} + if contact.Email != nil { + params.Add("email", *contact.Email) } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err + if contact.UserId != nil { + params.Add("userId", *contact.UserId) } - return c.Client.Do(req) -} - -func (c *Client) PostTransactionalWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewPostTransactionalRequestWithBody(c.Server, contentType, body) + req, err := newRequestWithQueryParams(c, ctx, http.MethodGet, "/contacts/find", params) if err != nil { return nil, err } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) -} - -func (c *Client) PostTransactional(ctx context.Context, body PostTransactionalJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewPostTransactionalRequest(c.Server, body) + contacts, err := sendRequest[[]*Contact](c, req) if err != nil { return nil, err } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err + if len(contacts) == 0 { + return nil, ErrContactNotFound } - return c.Client.Do(req) + return contacts[0], nil } -// NewGetApiKeyRequest generates requests for GetApiKey -func NewGetApiKeyRequest(server string) (*http.Request, error) { - var err error - - serverURL, err := url.Parse(server) - if err != nil { - return nil, err - } - - operationPath := fmt.Sprintf("/api-key") - if operationPath[0] == '/' { - operationPath = "." + operationPath - } - - queryURL, err := serverURL.Parse(operationPath) - if err != nil { - return nil, err +// DeleteContact deletes a contact by email or userId. +// See: https://loops.so/docs/api-reference/delete-contact +func (c *Client) DeleteContact(ctx context.Context, contact *ContactIdentifier) error { + if contact.Email == nil && contact.UserId == nil { + return errors.New("contact identifier must contain either an email or a userId") } - - req, err := http.NewRequest("GET", queryURL.String(), nil) - if err != nil { - return nil, err + if contact.Email != nil && contact.UserId != nil { + return errors.New("contact identifier must contain either an email or a userId, but not both") } - return req, nil -} - -// NewPostContactsCreateRequest calls the generic PostContactsCreate builder with application/json body -func NewPostContactsCreateRequest(server string, body PostContactsCreateJSONRequestBody) (*http.Request, error) { - var bodyReader io.Reader - buf, err := json.Marshal(body) + req, err := newRequestWithBody(c, ctx, http.MethodPost, "/contacts/delete", &contact) if err != nil { - return nil, err + return err } - bodyReader = bytes.NewReader(buf) - return NewPostContactsCreateRequestWithBody(server, "application/json", bodyReader) + _, err = sendRequest[*MessageResponse](c, req) + return err } -// NewPostContactsCreateRequestWithBody generates requests for PostContactsCreate with any type of body -func NewPostContactsCreateRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { - var err error - - serverURL, err := url.Parse(server) - if err != nil { - return nil, err - } - - operationPath := fmt.Sprintf("/contacts/create") - if operationPath[0] == '/' { - operationPath = "." + operationPath - } - - queryURL, err := serverURL.Parse(operationPath) - if err != nil { - return nil, err - } - - req, err := http.NewRequest("POST", queryURL.String(), body) +// GetMailingLists retrieves a list of an account’s mailing lists. +// See: https://loops.so/docs/api-reference/get-mailing-lists +func (c *Client) GetMailingLists(ctx context.Context) ([]*MailingList, error) { + req, err := newRequestWithQueryParams(c, ctx, http.MethodGet, "/lists", nil) if err != nil { return nil, err } - req.Header.Add("Content-Type", contentType) - - return req, nil + return sendRequest[[]*MailingList](c, req) } -// NewGetContactsCustomFieldsRequest generates requests for GetContactsCustomFields -func NewGetContactsCustomFieldsRequest(server string) (*http.Request, error) { - var err error - - serverURL, err := url.Parse(server) - if err != nil { - return nil, err - } - - operationPath := fmt.Sprintf("/contacts/customFields") - if operationPath[0] == '/' { - operationPath = "." + operationPath +// SendEvent sends an event to trigger emails in Loops. +// See: https://loops.so/docs/api-reference/send-event +func (c *Client) SendEvent(ctx context.Context, event *Event) error { + if event.Email == nil && event.UserId == nil { + return errors.New("event must contain either an email or a userId") } - - queryURL, err := serverURL.Parse(operationPath) - if err != nil { - return nil, err + if event.Email != nil && event.UserId != nil { + return errors.New("event must contain either an email or a userId, but not both") } - - req, err := http.NewRequest("GET", queryURL.String(), nil) + req, err := newRequestWithBody(c, ctx, http.MethodPost, "/events/send", event) if err != nil { - return nil, err + return err } - - return req, nil + _, err = sendRequest[*MessageResponse](c, req) + return err } -// NewPostContactsDeleteRequest calls the generic PostContactsDelete builder with application/json body -func NewPostContactsDeleteRequest(server string, body PostContactsDeleteJSONRequestBody) (*http.Request, error) { - var bodyReader io.Reader - buf, err := json.Marshal(body) +// SendTransactionalEmail sends a transactional email to a contact. +// See: https://loops.so/docs/api-reference/send-transactional-email +func (c *Client) SendTransactionalEmail(ctx context.Context, transactional *TransactionalEmail) error { + req, err := newRequestWithBody(c, ctx, http.MethodPost, "/transactional", transactional) if err != nil { - return nil, err + return err } - bodyReader = bytes.NewReader(buf) - return NewPostContactsDeleteRequestWithBody(server, "application/json", bodyReader) + _, err = sendRequest[*MessageResponse](c, req) + return err } -// NewPostContactsDeleteRequestWithBody generates requests for PostContactsDelete with any type of body -func NewPostContactsDeleteRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { - var err error - - serverURL, err := url.Parse(server) - if err != nil { - return nil, err - } - - operationPath := fmt.Sprintf("/contacts/delete") - if operationPath[0] == '/' { - operationPath = "." + operationPath - } - - queryURL, err := serverURL.Parse(operationPath) +// GetCustomFields retrieves a list of an account's custom contact properties. +func (c *Client) GetCustomFields(ctx context.Context) ([]*CustomField, error) { + req, err := newRequestWithQueryParams(c, ctx, http.MethodGet, "/contacts/customFields", nil) if err != nil { return nil, err } - - req, err := http.NewRequest("POST", queryURL.String(), body) + customFields, err := sendRequest[[]*CustomField](c, req) if err != nil { return nil, err } - - req.Header.Add("Content-Type", contentType) - - return req, nil + return customFields, nil } -// NewGetContactsFindRequest generates requests for GetContactsFind -func NewGetContactsFindRequest(server string, params *GetContactsFindParams) (*http.Request, error) { - var err error - - serverURL, err := url.Parse(server) - if err != nil { - return nil, err - } - - operationPath := fmt.Sprintf("/contacts/find") - if operationPath[0] == '/' { - operationPath = "." + operationPath - } - - queryURL, err := serverURL.Parse(operationPath) - if err != nil { - return nil, err - } - - if params != nil { - queryValues := queryURL.Query() - - if params.Email != nil { - - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "email", runtime.ParamLocationQuery, *params.Email); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) - } - } - } - - } - - if params.UserId != nil { - - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "userId", runtime.ParamLocationQuery, *params.UserId); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) - } - } - } - - } - - queryURL.RawQuery = queryValues.Encode() - } - - req, err := http.NewRequest("GET", queryURL.String(), nil) +// TestApiKey tests that an API key is valid. +// See: https://loops.so/docs/api-reference/api-key +func (c *Client) TestApiKey(ctx context.Context) (*ApiKeyInfo, error) { + req, err := newRequestWithQueryParams(c, ctx, http.MethodGet, "/api-key", nil) if err != nil { return nil, err } - return req, nil -} - -// NewPutContactsUpdateRequest calls the generic PutContactsUpdate builder with application/json body -func NewPutContactsUpdateRequest(server string, body PutContactsUpdateJSONRequestBody) (*http.Request, error) { - var bodyReader io.Reader - buf, err := json.Marshal(body) - if err != nil { - return nil, err - } - bodyReader = bytes.NewReader(buf) - return NewPutContactsUpdateRequestWithBody(server, "application/json", bodyReader) + return sendRequest[*ApiKeyInfo](c, req) } -// NewPutContactsUpdateRequestWithBody generates requests for PutContactsUpdate with any type of body -func NewPutContactsUpdateRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { - var err error - - serverURL, err := url.Parse(server) +func newRequestWithQueryParams(c *Client, ctx context.Context, method, path string, queryParams url.Values) (*http.Request, error) { + req, err := newRequestWithBody[Contact](c, ctx, method, path, nil) if err != nil { return nil, err } - - operationPath := fmt.Sprintf("/contacts/update") - if operationPath[0] == '/' { - operationPath = "." + operationPath - } - - queryURL, err := serverURL.Parse(operationPath) - if err != nil { - return nil, err - } - - req, err := http.NewRequest("PUT", queryURL.String(), body) - if err != nil { - return nil, err + if queryParams != nil { + req.URL.RawQuery = queryParams.Encode() } - req.Header.Add("Content-Type", contentType) - return req, nil } -// NewPostEventsSendRequest calls the generic PostEventsSend builder with application/json body -func NewPostEventsSendRequest(server string, body PostEventsSendJSONRequestBody) (*http.Request, error) { - var bodyReader io.Reader - buf, err := json.Marshal(body) - if err != nil { - return nil, err +func newRequestWithBody[T any](c *Client, ctx context.Context, method, path string, message *T) (*http.Request, error) { + if path[0] == '/' { + path = "." + path } - bodyReader = bytes.NewReader(buf) - return NewPostEventsSendRequestWithBody(server, "application/json", bodyReader) -} - -// NewPostEventsSendRequestWithBody generates requests for PostEventsSend with any type of body -func NewPostEventsSendRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { - var err error - serverURL, err := url.Parse(server) + queryURL, err := c.apiURL.Parse(path) if err != nil { return nil, err } - operationPath := fmt.Sprintf("/events/send") - if operationPath[0] == '/' { - operationPath = "." + operationPath + var body io.Reader + if message != nil { + buf, err := json.Marshal(message) + if err != nil { + return nil, fmt.Errorf("failed to marshal message: %w", err) + } + body = bytes.NewReader(buf) } - queryURL, err := serverURL.Parse(operationPath) + req, err := http.NewRequestWithContext(ctx, method, queryURL.String(), body) if err != nil { return nil, err } - req, err := http.NewRequest("POST", queryURL.String(), body) - if err != nil { - return nil, err + for _, interceptor := range c.requestInterceptors { + if err := interceptor(ctx, req); err != nil { + return nil, err + } } - - req.Header.Add("Content-Type", contentType) - return req, nil } -// NewGetListsRequest generates requests for GetLists -func NewGetListsRequest(server string) (*http.Request, error) { - var err error - - serverURL, err := url.Parse(server) +func sendRequest[T any](c *Client, req *http.Request) (T, error) { + var none T + resp, err := c.httpClient.Do(req) if err != nil { - return nil, err + return none, fmt.Errorf("failed to send request %s: %w", req.URL.String(), err) } + defer func() { _ = resp.Body.Close() }() - operationPath := fmt.Sprintf("/lists") - if operationPath[0] == '/' { - operationPath = "." + operationPath - } - - queryURL, err := serverURL.Parse(operationPath) + body, err := io.ReadAll(resp.Body) if err != nil { - return nil, err + return none, fmt.Errorf("failed to read response body: %w", err) } - req, err := http.NewRequest("GET", queryURL.String(), nil) - if err != nil { - return nil, err + if resp.StatusCode < 300 { // success response + var response T + err = json.Unmarshal(body, &response) + if err != nil { + return none, fmt.Errorf("failed to unmarshal response body: %w", err) + } + return response, nil } - return req, nil -} - -// NewPostTransactionalRequest calls the generic PostTransactional builder with application/json body -func NewPostTransactionalRequest(server string, body PostTransactionalJSONRequestBody) (*http.Request, error) { - var bodyReader io.Reader - buf, err := json.Marshal(body) - if err != nil { - return nil, err + // sometimes loops returns an "error": message, so check if that's the case and if so, return the error + errorMsg := &errorResponse{} + err = json.Unmarshal(body, &errorMsg) + if err == nil { + return none, errors.New(errorMsg.Error) } - bodyReader = bytes.NewReader(buf) - return NewPostTransactionalRequestWithBody(server, "application/json", bodyReader) -} - -// NewPostTransactionalRequestWithBody generates requests for PostTransactional with any type of body -func NewPostTransactionalRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { - var err error - serverURL, err := url.Parse(server) + // error, get the message and return it + msg := &MessageResponse{} + err = json.Unmarshal(body, &msg) if err != nil { - return nil, err + return none, fmt.Errorf("failed to unmarshal error message: %w", err) } - - operationPath := fmt.Sprintf("/transactional") - if operationPath[0] == '/' { - operationPath = "." + operationPath + if msg.Message == "" { + return none, errors.New(string(body)) } - - queryURL, err := serverURL.Parse(operationPath) - if err != nil { - return nil, err - } - - req, err := http.NewRequest("POST", queryURL.String(), body) - if err != nil { - return nil, err - } - - req.Header.Add("Content-Type", contentType) - - return req, nil -} - -func (c *Client) applyEditors(ctx context.Context, req *http.Request, additionalEditors []RequestEditorFn) error { - for _, r := range c.RequestEditors { - if err := r(ctx, req); err != nil { - return err - } - } - for _, r := range additionalEditors { - if err := r(ctx, req); err != nil { - return err - } - } - return nil -} - -// ClientWithResponses builds on ClientInterface to offer response payloads -type ClientWithResponses struct { - ClientInterface -} - -// NewClientWithResponses creates a new ClientWithResponses, which wraps -// Client with return type handling -func NewClientWithResponses(server string, opts ...ClientOption) (*ClientWithResponses, error) { - client, err := NewClient(server, opts...) - if err != nil { - return nil, err - } - return &ClientWithResponses{client}, nil -} - -// WithBaseURL overrides the baseURL. -func WithBaseURL(baseURL string) ClientOption { - return func(c *Client) error { - newBaseURL, err := url.Parse(baseURL) - if err != nil { - return err - } - c.Server = newBaseURL.String() - return nil - } -} - -// ClientWithResponsesInterface is the interface specification for the client with responses above. -type ClientWithResponsesInterface interface { - // GetApiKeyWithResponse request - GetApiKeyWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetApiKeyResponse, error) - - // PostContactsCreateWithBodyWithResponse request with any body - PostContactsCreateWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostContactsCreateResponse, error) - - PostContactsCreateWithResponse(ctx context.Context, body PostContactsCreateJSONRequestBody, reqEditors ...RequestEditorFn) (*PostContactsCreateResponse, error) - - // GetContactsCustomFieldsWithResponse request - GetContactsCustomFieldsWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetContactsCustomFieldsResponse, error) - - // PostContactsDeleteWithBodyWithResponse request with any body - PostContactsDeleteWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostContactsDeleteResponse, error) - - PostContactsDeleteWithResponse(ctx context.Context, body PostContactsDeleteJSONRequestBody, reqEditors ...RequestEditorFn) (*PostContactsDeleteResponse, error) - - // GetContactsFindWithResponse request - GetContactsFindWithResponse(ctx context.Context, params *GetContactsFindParams, reqEditors ...RequestEditorFn) (*GetContactsFindResponse, error) - - // PutContactsUpdateWithBodyWithResponse request with any body - PutContactsUpdateWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PutContactsUpdateResponse, error) - - PutContactsUpdateWithResponse(ctx context.Context, body PutContactsUpdateJSONRequestBody, reqEditors ...RequestEditorFn) (*PutContactsUpdateResponse, error) - - // PostEventsSendWithBodyWithResponse request with any body - PostEventsSendWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostEventsSendResponse, error) - - PostEventsSendWithResponse(ctx context.Context, body PostEventsSendJSONRequestBody, reqEditors ...RequestEditorFn) (*PostEventsSendResponse, error) - - // GetListsWithResponse request - GetListsWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetListsResponse, error) - - // PostTransactionalWithBodyWithResponse request with any body - PostTransactionalWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostTransactionalResponse, error) - - PostTransactionalWithResponse(ctx context.Context, body PostTransactionalJSONRequestBody, reqEditors ...RequestEditorFn) (*PostTransactionalResponse, error) -} - -type GetApiKeyResponse struct { - Body []byte - HTTPResponse *http.Response - JSON200 *struct { - Success bool `json:"success"` - - // TeamName The name of the team the API key belongs to. - TeamName string `json:"teamName"` - } - JSON401 *struct { - Error *string `json:"error,omitempty"` - } -} - -// Status returns HTTPResponse.Status -func (r GetApiKeyResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status - } - return http.StatusText(0) -} - -// StatusCode returns HTTPResponse.StatusCode -func (r GetApiKeyResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode - } - return 0 -} - -type PostContactsCreateResponse struct { - Body []byte - HTTPResponse *http.Response - JSON200 *ContactSuccessResponse - JSON400 *ContactFailureResponse - JSON405 *ContactFailureResponse - JSON409 *ContactFailureResponse -} - -// Status returns HTTPResponse.Status -func (r PostContactsCreateResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status - } - return http.StatusText(0) -} - -// StatusCode returns HTTPResponse.StatusCode -func (r PostContactsCreateResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode - } - return 0 -} - -type GetContactsCustomFieldsResponse struct { - Body []byte - HTTPResponse *http.Response - JSON200 *[]CustomField -} - -// Status returns HTTPResponse.Status -func (r GetContactsCustomFieldsResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status - } - return http.StatusText(0) -} - -// StatusCode returns HTTPResponse.StatusCode -func (r GetContactsCustomFieldsResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode - } - return 0 -} - -type PostContactsDeleteResponse struct { - Body []byte - HTTPResponse *http.Response - JSON200 *ContactDeleteResponse - JSON400 *ContactFailureResponse - JSON404 *ContactFailureResponse -} - -// Status returns HTTPResponse.Status -func (r PostContactsDeleteResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status - } - return http.StatusText(0) -} - -// StatusCode returns HTTPResponse.StatusCode -func (r PostContactsDeleteResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode - } - return 0 -} - -type GetContactsFindResponse struct { - Body []byte - HTTPResponse *http.Response - JSON200 *[]Contact - JSON400 *ContactFailureResponse -} - -// Status returns HTTPResponse.Status -func (r GetContactsFindResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status - } - return http.StatusText(0) -} - -// StatusCode returns HTTPResponse.StatusCode -func (r GetContactsFindResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode - } - return 0 -} - -type PutContactsUpdateResponse struct { - Body []byte - HTTPResponse *http.Response - JSON200 *ContactSuccessResponse - JSON400 *ContactFailureResponse - JSON405 *ContactFailureResponse -} - -// Status returns HTTPResponse.Status -func (r PutContactsUpdateResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status - } - return http.StatusText(0) -} - -// StatusCode returns HTTPResponse.StatusCode -func (r PutContactsUpdateResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode - } - return 0 -} - -type PostEventsSendResponse struct { - Body []byte - HTTPResponse *http.Response - JSON200 *EventSuccessResponse - JSON400 *EventFailureResponse -} - -// Status returns HTTPResponse.Status -func (r PostEventsSendResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status - } - return http.StatusText(0) -} - -// StatusCode returns HTTPResponse.StatusCode -func (r PostEventsSendResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode - } - return 0 -} - -type GetListsResponse struct { - Body []byte - HTTPResponse *http.Response - JSON200 *[]MailingList -} - -// Status returns HTTPResponse.Status -func (r GetListsResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status - } - return http.StatusText(0) -} - -// StatusCode returns HTTPResponse.StatusCode -func (r GetListsResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode - } - return 0 -} - -type PostTransactionalResponse struct { - Body []byte - HTTPResponse *http.Response - JSON200 *TransactionalSuccessResponse - JSON400 *struct { - union json.RawMessage - } - JSON404 *TransactionalFailure2Response -} - -// Status returns HTTPResponse.Status -func (r PostTransactionalResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status - } - return http.StatusText(0) -} - -// StatusCode returns HTTPResponse.StatusCode -func (r PostTransactionalResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode - } - return 0 -} - -// GetApiKeyWithResponse request returning *GetApiKeyResponse -func (c *ClientWithResponses) GetApiKeyWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetApiKeyResponse, error) { - rsp, err := c.GetApiKey(ctx, reqEditors...) - if err != nil { - return nil, err - } - return ParseGetApiKeyResponse(rsp) -} - -// PostContactsCreateWithBodyWithResponse request with arbitrary body returning *PostContactsCreateResponse -func (c *ClientWithResponses) PostContactsCreateWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostContactsCreateResponse, error) { - rsp, err := c.PostContactsCreateWithBody(ctx, contentType, body, reqEditors...) - if err != nil { - return nil, err - } - return ParsePostContactsCreateResponse(rsp) -} - -func (c *ClientWithResponses) PostContactsCreateWithResponse(ctx context.Context, body PostContactsCreateJSONRequestBody, reqEditors ...RequestEditorFn) (*PostContactsCreateResponse, error) { - rsp, err := c.PostContactsCreate(ctx, body, reqEditors...) - if err != nil { - return nil, err - } - return ParsePostContactsCreateResponse(rsp) -} - -// GetContactsCustomFieldsWithResponse request returning *GetContactsCustomFieldsResponse -func (c *ClientWithResponses) GetContactsCustomFieldsWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetContactsCustomFieldsResponse, error) { - rsp, err := c.GetContactsCustomFields(ctx, reqEditors...) - if err != nil { - return nil, err - } - return ParseGetContactsCustomFieldsResponse(rsp) -} - -// PostContactsDeleteWithBodyWithResponse request with arbitrary body returning *PostContactsDeleteResponse -func (c *ClientWithResponses) PostContactsDeleteWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostContactsDeleteResponse, error) { - rsp, err := c.PostContactsDeleteWithBody(ctx, contentType, body, reqEditors...) - if err != nil { - return nil, err - } - return ParsePostContactsDeleteResponse(rsp) -} - -func (c *ClientWithResponses) PostContactsDeleteWithResponse(ctx context.Context, body PostContactsDeleteJSONRequestBody, reqEditors ...RequestEditorFn) (*PostContactsDeleteResponse, error) { - rsp, err := c.PostContactsDelete(ctx, body, reqEditors...) - if err != nil { - return nil, err - } - return ParsePostContactsDeleteResponse(rsp) -} - -// GetContactsFindWithResponse request returning *GetContactsFindResponse -func (c *ClientWithResponses) GetContactsFindWithResponse(ctx context.Context, params *GetContactsFindParams, reqEditors ...RequestEditorFn) (*GetContactsFindResponse, error) { - rsp, err := c.GetContactsFind(ctx, params, reqEditors...) - if err != nil { - return nil, err - } - return ParseGetContactsFindResponse(rsp) -} - -// PutContactsUpdateWithBodyWithResponse request with arbitrary body returning *PutContactsUpdateResponse -func (c *ClientWithResponses) PutContactsUpdateWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PutContactsUpdateResponse, error) { - rsp, err := c.PutContactsUpdateWithBody(ctx, contentType, body, reqEditors...) - if err != nil { - return nil, err - } - return ParsePutContactsUpdateResponse(rsp) -} - -func (c *ClientWithResponses) PutContactsUpdateWithResponse(ctx context.Context, body PutContactsUpdateJSONRequestBody, reqEditors ...RequestEditorFn) (*PutContactsUpdateResponse, error) { - rsp, err := c.PutContactsUpdate(ctx, body, reqEditors...) - if err != nil { - return nil, err - } - return ParsePutContactsUpdateResponse(rsp) -} - -// PostEventsSendWithBodyWithResponse request with arbitrary body returning *PostEventsSendResponse -func (c *ClientWithResponses) PostEventsSendWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostEventsSendResponse, error) { - rsp, err := c.PostEventsSendWithBody(ctx, contentType, body, reqEditors...) - if err != nil { - return nil, err - } - return ParsePostEventsSendResponse(rsp) -} - -func (c *ClientWithResponses) PostEventsSendWithResponse(ctx context.Context, body PostEventsSendJSONRequestBody, reqEditors ...RequestEditorFn) (*PostEventsSendResponse, error) { - rsp, err := c.PostEventsSend(ctx, body, reqEditors...) - if err != nil { - return nil, err - } - return ParsePostEventsSendResponse(rsp) -} - -// GetListsWithResponse request returning *GetListsResponse -func (c *ClientWithResponses) GetListsWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetListsResponse, error) { - rsp, err := c.GetLists(ctx, reqEditors...) - if err != nil { - return nil, err - } - return ParseGetListsResponse(rsp) -} - -// PostTransactionalWithBodyWithResponse request with arbitrary body returning *PostTransactionalResponse -func (c *ClientWithResponses) PostTransactionalWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostTransactionalResponse, error) { - rsp, err := c.PostTransactionalWithBody(ctx, contentType, body, reqEditors...) - if err != nil { - return nil, err - } - return ParsePostTransactionalResponse(rsp) -} - -func (c *ClientWithResponses) PostTransactionalWithResponse(ctx context.Context, body PostTransactionalJSONRequestBody, reqEditors ...RequestEditorFn) (*PostTransactionalResponse, error) { - rsp, err := c.PostTransactional(ctx, body, reqEditors...) - if err != nil { - return nil, err - } - return ParsePostTransactionalResponse(rsp) -} - -// ParseGetApiKeyResponse parses an HTTP response from a GetApiKeyWithResponse call -func ParseGetApiKeyResponse(rsp *http.Response) (*GetApiKeyResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() - if err != nil { - return nil, err - } - - response := &GetApiKeyResponse{ - Body: bodyBytes, - HTTPResponse: rsp, - } - - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest struct { - Success bool `json:"success"` - - // TeamName The name of the team the API key belongs to. - TeamName string `json:"teamName"` - } - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON200 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 401: - var dest struct { - Error *string `json:"error,omitempty"` - } - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON401 = &dest - - } - - return response, nil -} - -// ParsePostContactsCreateResponse parses an HTTP response from a PostContactsCreateWithResponse call -func ParsePostContactsCreateResponse(rsp *http.Response) (*PostContactsCreateResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() - if err != nil { - return nil, err - } - - response := &PostContactsCreateResponse{ - Body: bodyBytes, - HTTPResponse: rsp, - } - - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest ContactSuccessResponse - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON200 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: - var dest ContactFailureResponse - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON400 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 405: - var dest ContactFailureResponse - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON405 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 409: - var dest ContactFailureResponse - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON409 = &dest - - } - - return response, nil -} - -// ParseGetContactsCustomFieldsResponse parses an HTTP response from a GetContactsCustomFieldsWithResponse call -func ParseGetContactsCustomFieldsResponse(rsp *http.Response) (*GetContactsCustomFieldsResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() - if err != nil { - return nil, err - } - - response := &GetContactsCustomFieldsResponse{ - Body: bodyBytes, - HTTPResponse: rsp, - } - - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest []CustomField - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON200 = &dest - - } - - return response, nil -} - -// ParsePostContactsDeleteResponse parses an HTTP response from a PostContactsDeleteWithResponse call -func ParsePostContactsDeleteResponse(rsp *http.Response) (*PostContactsDeleteResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() - if err != nil { - return nil, err - } - - response := &PostContactsDeleteResponse{ - Body: bodyBytes, - HTTPResponse: rsp, - } - - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest ContactDeleteResponse - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON200 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: - var dest ContactFailureResponse - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON400 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: - var dest ContactFailureResponse - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON404 = &dest - - } - - return response, nil -} - -// ParseGetContactsFindResponse parses an HTTP response from a GetContactsFindWithResponse call -func ParseGetContactsFindResponse(rsp *http.Response) (*GetContactsFindResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() - if err != nil { - return nil, err - } - - response := &GetContactsFindResponse{ - Body: bodyBytes, - HTTPResponse: rsp, - } - - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest []Contact - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON200 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: - var dest ContactFailureResponse - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON400 = &dest - - } - - return response, nil -} - -// ParsePutContactsUpdateResponse parses an HTTP response from a PutContactsUpdateWithResponse call -func ParsePutContactsUpdateResponse(rsp *http.Response) (*PutContactsUpdateResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() - if err != nil { - return nil, err - } - - response := &PutContactsUpdateResponse{ - Body: bodyBytes, - HTTPResponse: rsp, - } - - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest ContactSuccessResponse - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON200 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: - var dest ContactFailureResponse - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON400 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 405: - var dest ContactFailureResponse - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON405 = &dest - - } - - return response, nil -} - -// ParsePostEventsSendResponse parses an HTTP response from a PostEventsSendWithResponse call -func ParsePostEventsSendResponse(rsp *http.Response) (*PostEventsSendResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() - if err != nil { - return nil, err - } - - response := &PostEventsSendResponse{ - Body: bodyBytes, - HTTPResponse: rsp, - } - - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest EventSuccessResponse - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON200 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: - var dest EventFailureResponse - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON400 = &dest - - } - - return response, nil -} - -// ParseGetListsResponse parses an HTTP response from a GetListsWithResponse call -func ParseGetListsResponse(rsp *http.Response) (*GetListsResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() - if err != nil { - return nil, err - } - - response := &GetListsResponse{ - Body: bodyBytes, - HTTPResponse: rsp, - } - - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest []MailingList - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON200 = &dest - - } - - return response, nil -} - -// ParsePostTransactionalResponse parses an HTTP response from a PostTransactionalWithResponse call -func ParsePostTransactionalResponse(rsp *http.Response) (*PostTransactionalResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() - if err != nil { - return nil, err - } - - response := &PostTransactionalResponse{ - Body: bodyBytes, - HTTPResponse: rsp, - } - - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest TransactionalSuccessResponse - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON200 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: - var dest struct { - union json.RawMessage - } - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON400 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: - var dest TransactionalFailure2Response - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON404 = &dest - - } - - return response, nil + return none, errors.New(msg.Message) } diff --git a/examples/contact-crud/main.go b/examples/contact-crud/main.go new file mode 100644 index 0000000..d64c637 --- /dev/null +++ b/examples/contact-crud/main.go @@ -0,0 +1,55 @@ +package main + +import ( + "context" + "github.com/tilebox/loops-go" + "log/slog" +) + +func main() { + client, err := loops.NewClient(loops.WithApiKey("YOUR_LOOPS_API_KEY")) + if err != nil { + slog.Error("failed to create client", slog.Any("error", err.Error())) + return + } + + ctx := context.Background() + + // create a contact + contactID, err := client.CreateContact(ctx, &loops.Contact{ + Email: "neil.armstrong@moon.space", + FirstName: loops.String("Neil"), + LastName: loops.String("Armstrong"), + Subscribed: true, + }) + if err != nil { + slog.Error("failed to create contact", slog.Any("error", err.Error())) + return + } + slog.Info("Created contact", slog.String("id", contactID)) + + // find a contact + contact, err := client.FindContact(ctx, &loops.ContactIdentifier{ + Email: loops.String("neil.armstrong@moon.space"), + }) + if err != nil { + slog.Error("failed to find contact", slog.Any("error", err.Error())) + return + } + slog.Info("Found contact", slog.String("id", contact.Id), slog.String("email", contact.Email)) + + // update a contact, specify a user group + _, err = client.UpdateContact(ctx, &loops.Contact{ + Email: "neil.armstrong@moon.space", + UserGroup: loops.String("Astronauts"), + }) + + // delete a contact + err = client.DeleteContact(ctx, &loops.ContactIdentifier{ + Email: loops.String("neil.armstrong@moon.space"), + }) + if err != nil { + slog.Error("failed to delete contact", slog.Any("error", err.Error())) + return + } +} diff --git a/examples/send-event/main.go b/examples/send-event/main.go new file mode 100644 index 0000000..1158980 --- /dev/null +++ b/examples/send-event/main.go @@ -0,0 +1,30 @@ +package main + +import ( + "context" + "github.com/tilebox/loops-go" + "log/slog" +) + +func main() { + client, err := loops.NewClient(loops.WithApiKey("YOUR_LOOPS_API_KEY")) + if err != nil { + slog.Error("failed to create client", slog.Any("error", err.Error())) + return + } + + ctx := context.Background() + + err = client.SendEvent(ctx, &loops.Event{ + Email: loops.String("neil.armstrong@moon.space"), + EventName: "joinedMission", + EventProperties: &map[string]interface{}{ + "mission": "Apollo 11", + }, + }) + if err != nil { + slog.Error("failed to send event", slog.Any("error", err.Error())) + return + } + slog.Info("sent event") +} diff --git a/examples/send-transactional-email/main.go b/examples/send-transactional-email/main.go new file mode 100644 index 0000000..16c0eb2 --- /dev/null +++ b/examples/send-transactional-email/main.go @@ -0,0 +1,30 @@ +package main + +import ( + "context" + "github.com/tilebox/loops-go" + "log/slog" +) + +func main() { + client, err := loops.NewClient(loops.WithApiKey("YOUR_LOOPS_API_KEY")) + if err != nil { + slog.Error("failed to create client", slog.Any("error", err.Error())) + return + } + + ctx := context.Background() + + err = client.SendTransactionalEmail(ctx, &loops.TransactionalEmail{ + TransactionalId: "cm3n2vjux00cgeyeflew9ly2w", + Email: "lukas.bindreiter@tilebox.com", + DataVariables: &map[string]interface{}{ + "name": "Mr. Lukas", + }, + }) + if err != nil { + slog.Error("failed to send transactional email", slog.Any("error", err.Error())) + return + } + slog.Info("sent transactional email") +} diff --git a/generate.go b/generate.go deleted file mode 100644 index 1503d44..0000000 --- a/generate.go +++ /dev/null @@ -1,3 +0,0 @@ -package loops - -//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=oapi_codegen_config.yaml openapi.json diff --git a/go.mod b/go.mod index b42713c..082215a 100644 --- a/go.mod +++ b/go.mod @@ -5,17 +5,14 @@ go 1.23.3 require github.com/oapi-codegen/oapi-codegen/v2 v2.4.1 require ( - github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936 // indirect github.com/getkin/kin-openapi v0.127.0 // indirect github.com/go-openapi/jsonpointer v0.21.0 // indirect github.com/go-openapi/swag v0.23.0 // indirect - github.com/google/uuid v1.5.0 // indirect github.com/invopop/yaml v0.3.1 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect - github.com/oapi-codegen/runtime v1.1.1 // indirect github.com/perimeterx/marshmallow v1.1.5 // indirect github.com/speakeasy-api/openapi-overlay v0.9.0 // indirect github.com/vmware-labs/yaml-jsonpath v0.3.2 // indirect diff --git a/go.sum b/go.sum index 998e2ba..fc9e9df 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,3 @@ -github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk= -github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ= -github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk= -github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= @@ -39,15 +35,12 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= -github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/invopop/yaml v0.3.1 h1:f0+ZpmhfBSS4MhG+4HYseMdJhoeeopbSKbq5Rpeelso= github.com/invopop/yaml v0.3.1/go.mod h1:PMOp3nn4/12yEZUFfmOuNHJsZToEEOwoWsT+D81KkeA= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= -github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= @@ -64,8 +57,6 @@ github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/oapi-codegen/oapi-codegen/v2 v2.4.1 h1:ykgG34472DWey7TSjd8vIfNykXgjOgYJZoQbKfEeY/Q= github.com/oapi-codegen/oapi-codegen/v2 v2.4.1/go.mod h1:N5+lY1tiTDV3V1BeHtOxeWXHoPVeApvsvjJqegfoaz8= -github.com/oapi-codegen/runtime v1.1.1 h1:EXLHh0DXIJnWhdRPN2w4MXAzFyE4CskzhNLUmtpMYro= -github.com/oapi-codegen/runtime v1.1.1/go.mod h1:SK9X900oXmPWilYR5/WKPzt3Kqxn/uS/+lbpREv+eCg= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.10.2/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= @@ -89,9 +80,7 @@ github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/speakeasy-api/openapi-overlay v0.9.0 h1:Wrz6NO02cNlLzx1fB093lBlYxSI54VRhy1aSutx0PQg= github.com/speakeasy-api/openapi-overlay v0.9.0/go.mod h1:f5FloQrHA7MsxYg9djzMD5h6dxrHjVVByWKh7an8TRc= -github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= diff --git a/models.go b/models.go new file mode 100644 index 0000000..76c9a8d --- /dev/null +++ b/models.go @@ -0,0 +1,109 @@ +package loops + +// String returns a pointer to the string value passed in. +func String(v string) *string { + return &v +} + +// Contact defines model for Contact. +type Contact struct { + // The contact's ID. + Id string `json:"id,omitempty"` + // The contact's email address. + Email string `json:"email,omitempty"` + // The contact's first name. + FirstName *string `json:"firstName,omitempty"` + // The contact's last name. + LastName *string `json:"lastName,omitempty"` + // The source the contact was created from. + Source *string `json:"source,omitempty"` + // Whether the contact will receive campaign and loops emails. + Subscribed bool `json:"subscribed,omitempty"` + // The contact's user group (used to segemnt users when sending emails). + UserGroup *string `json:"userGroup,omitempty"` + // A unique user ID (for example, from an external application). + UserId *string `json:"userId,omitempty"` + // Mailing lists the contact is subscribed to. + MailingLists map[string]interface{} `json:"mailingLists,omitempty"` +} + +type ContactIdentifier struct { + Email *string `json:"email,omitempty"` + UserId *string `json:"userId,omitempty"` +} + +type MailingList struct { + // The ID of the list. + Id string `json:"id"` + // The name of the list. + Name string `json:"name"` + // Whether the list is public (true) or private (false). + // See: https://loops.so/docs/contacts/mailing-lists#list-visibility + IsPublic bool `json:"isPublic"` +} + +type Event struct { + // The contact's email address + Email *string `json:"email,omitempty"` + // The contact's unique user ID. This must already have been added to your contact in Loops. + UserId *string `json:"userId,omitempty"` + // The name of the event + EventName string `json:"eventName"` + // Properties to update the contact with, including custom properties. + ContactProperties map[string]interface{} `json:"contactProperties,omitempty"` + // Event properties, made available in emails triggered by the event. + EventProperties *map[string]interface{} `json:"eventProperties,omitempty"` + // An object of mailing list IDs and boolean subscription statuses. + MailingLists *map[string]interface{} `json:"mailingLists,omitempty"` +} + +type TransactionalEmail struct { + // The ID of the transactional email to send. + TransactionalId string `json:"transactionalId"` + // The email address of the recipient + Email string `json:"email"` + // Create a contact in your audience using the provided email address (if one doesn't already exist). + AddToAudience *bool `json:"addToAudience,omitempty"` + // Data variables as defined by the transational email template. + DataVariables *map[string]interface{} `json:"dataVariables,omitempty"` + // File(s) to be sent along with the email message. + Attachments *[]EmailAttachment `json:"attachments,omitempty"` +} + +type EmailAttachment struct { + // Filename The name of the file, shown in email clients. + Filename string `json:"filename"` + // ContentType The MIME type of the file. + ContentType string `json:"contentType"` + // Data The base64-encoded content of the file. + Data string `json:"data"` +} + +type CustomField struct { + // The property's name key + Key string `json:"key"` + // The human-friendly label for this property + Label string `json:"label"` + // The type of property (one of string, number, boolean or date) + Type string `json:"type"` +} + +type ApiKeyInfo struct { + Success bool `json:"success"` + // The name of the team the API key belongs to. + TeamName string `json:"teamName"` +} + +type errorResponse struct { + Error string `json:"error"` +} + +type IDResponse struct { + Success bool `json:"success"` + ID string `json:"id"` +} + +type MessageResponse struct { + Success bool `json:"success"` + Message string `json:"message"` +} diff --git a/oapi_codegen_config.yaml b/oapi_codegen_config.yaml deleted file mode 100644 index ed0c653..0000000 --- a/oapi_codegen_config.yaml +++ /dev/null @@ -1,6 +0,0 @@ -# yaml-language-server: $schema=https://raw.githubusercontent.com/oapi-codegen/oapi-codegen/HEAD/configuration-schema.json -package: loops -output: client.go -generate: - client: true - models: true diff --git a/openapi.json b/openapi.json deleted file mode 100644 index 48fa270..0000000 --- a/openapi.json +++ /dev/null @@ -1,935 +0,0 @@ -{ - "openapi": "3.1.0", - "info": { - "title": "Loops OpenAPI Spec", - "description": "This is the OpenAPI Spec for the [Loops API](https://loops.so/docs/api).", - "version": "1.3.3" - }, - "servers": [ - { - "url": "https://app.loops.so/api/v1" - } - ], - "tags": [ - { - "name": "API key" - }, - { - "name": "Contacts", - "description": "Manage contacts in your audience" - }, - { - "name": "Mailing lists", - "description": "View mailing lists" - }, - { - "name": "Events", - "description": "Trigger email sending with events" - }, - { - "name": "Transactional emails", - "description": "Send transactional emails" - }, - { - "name": "Custom fields", - "description": "View custom contact properties" - } - ], - "paths": { - "/api-key": { - "get": { - "tags": [ - "API key" - ], - "summary": "Test your API key", - "responses": { - "200": { - "description": "Success", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "examples": [ - true - ] - }, - "teamName": { - "type": "string", - "description": "The name of the team the API key belongs to.", - "examples": [ - "Company name" - ] - } - }, - "required": [ - "success", - "teamName" - ] - } - } - } - }, - "401": { - "description": "Invalid API key", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "error": { - "type": "string", - "examples": [ - "Invalid API key" - ] - } - } - } - } - } - } - }, - "security": [ - { - "apiKey": [] - } - ] - } - }, - "/contacts/create": { - "post": { - "tags": [ - "Contacts" - ], - "summary": "Create a contact", - "description": "Add a contact to your audience.", - "requestBody": { - "description": "You can add custom contact properties as keys in this request (of type `string`, `number`, `boolean` or `date` ([see available date formats](https://loops.so/docs/contacts/properties#dates))).
Make sure to create the properties in Loops before using them in API calls.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ContactRequest" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "Successful create.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ContactSuccessResponse" - } - } - } - }, - "400": { - "description": "Bad request (e.g. invalid email address).", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ContactFailureResponse" - } - } - } - }, - "405": { - "description": "Wrong HTTP request method.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ContactFailureResponse" - } - } - } - }, - "409": { - "description": "Email or `userId` already exists.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ContactFailureResponse" - } - } - } - } - }, - "security": [ - { - "apiKey": [] - } - ] - } - }, - "/contacts/update": { - "put": { - "tags": [ - "Contacts" - ], - "summary": "Update a contact", - "description": "Update a contact by `email` or `userId`.
If you want to update a contact’s email address, the contact will first need a `userId` value. You can then make a request containing the userId field along with an updated email address.", - "requestBody": { - "description": "You can add custom contact properties as keys in this request (of type `string`, `number`, `boolean` or `date` ([see available date formats](https://loops.so/docs/contacts/properties#dates))).
Make sure to create the properties in Loops before using them in API calls.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ContactRequest" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "Successful update.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ContactSuccessResponse" - } - } - } - }, - "400": { - "description": "Bad request (e.g. `email` or `userId` are missing).", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ContactFailureResponse" - } - } - } - }, - "405": { - "description": "Wrong HTTP request method.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ContactFailureResponse" - } - } - } - } - }, - "security": [ - { - "apiKey": [] - } - ] - } - }, - "/contacts/find": { - "get": { - "tags": [ - "Contacts" - ], - "summary": "Find a contact", - "description": "Search for a contact by `email` or `userId`. Only one parameter is allowed.", - "parameters": [ - { - "name": "email", - "in": "query", - "required": false, - "description": "Email address (URI-encoded)", - "schema": { - "type": "string" - } - }, - { - "name": "userId", - "in": "query", - "required": false, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "List of contacts (or an empty array if no contact was found). Contact objects will include any custom properties.", - "content": { - "application/json": { - "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Contact" - } - } - } - } - }, - "400": { - "description": "Bad request (e.g. invalid email address).", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ContactFailureResponse" - } - } - } - }, - "405": { - "description": "Wrong HTTP request method." - } - }, - "security": [ - { - "apiKey": [] - } - ] - } - }, - "/contacts/delete": { - "post": { - "tags": [ - "Contacts" - ], - "summary": "Delete a contact", - "description": "Delete a contact by `email` or `userId`.", - "requestBody": { - "description": "Include only one of `email` or `userId`.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ContactDeleteRequest" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "Successful delete.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ContactDeleteResponse" - } - } - } - }, - "400": { - "description": "Bad request (e.g. `email` and `userId` are both provided).", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ContactFailureResponse" - } - } - } - }, - "404": { - "description": "Contact not found.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ContactFailureResponse" - } - } - } - }, - "405": { - "description": "Wrong HTTP request method." - } - }, - "security": [ - { - "apiKey": [] - } - ] - } - }, - "/lists": { - "get": { - "tags": [ - "Mailing lists" - ], - "summary": "Get a list of mailing lists", - "description": "Retrieve a list of your account's mailing lists.", - "responses": { - "200": { - "description": "Successful.", - "content": { - "application/json": { - "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/MailingList" - } - } - } - } - }, - "405": { - "description": "Wrong HTTP request method." - } - }, - "security": [ - { - "apiKey": [] - } - ] - } - }, - "/events/send": { - "post": { - "tags": [ - "Events" - ], - "summary": "Send an event", - "description": "Send events to trigger emails in Loops.", - "requestBody": { - "description": "Provide either `email` or `userId` to identify the contact ([read more](https://loops.so/docs/api-reference/send-event#body)).
You can add event properties, which will be available in emails sent by this event. Values can be of type string, number, boolean or date ([see allowed date formats](https://loops.so/docs/events/properties#important-information-about-event-properties)).
Make sure to create the properties in Loops before using them in API calls.
You can add contact properties as keys in this request (of type `string`, `number`, `boolean` or `date` ([see available date formats](https://loops.so/docs/contacts/properties#dates))).", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/EventRequest" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "Successful send.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/EventSuccessResponse" - } - } - } - }, - "400": { - "description": "Bad request (e.g. `eventName` is missing).", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/EventFailureResponse" - } - } - } - }, - "405": { - "description": "Wrong HTTP request method." - } - }, - "security": [ - { - "apiKey": [] - } - ] - } - }, - "/transactional": { - "post": { - "tags": [ - "Transactional emails" - ], - "summary": "Send a transactional email", - "description": "Send a transactional email to a contact.
Please [email us](mailto:help@loops.so) to enable attachments on your account before using them with the API.", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/TransactionalRequest" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "Successful send.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/TransactionalSuccessResponse" - } - } - } - }, - "400": { - "description": "Bad request (e.g. transactional email is not published).", - "content": { - "application/json": { - "schema": { - "oneOf": [ - { - "$ref": "#/components/schemas/TransactionalFailureResponse" - }, - { - "$ref": "#/components/schemas/TransactionalFailure2Response" - }, - { - "$ref": "#/components/schemas/TransactionalFailure3Response" - } - ] - } - } - } - }, - "404": { - "description": "Transactional email not found.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/TransactionalFailure2Response" - } - } - } - }, - "405": { - "description": "Wrong HTTP request method." - } - }, - "security": [ - { - "apiKey": [] - } - ] - } - }, - "/contacts/customFields": { - "get": { - "tags": [ - "Custom fields" - ], - "summary": "Get a list of custom contact properties", - "description": "Retrieve a list of your account's custom contact properties.", - "responses": { - "200": { - "description": "Successful.", - "content": { - "application/json": { - "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/CustomField" - } - } - } - } - }, - "405": { - "description": "Wrong HTTP request method." - } - }, - "security": [ - { - "apiKey": [] - } - ] - } - } - }, - "components": { - "schemas": { - "Contact": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "email": { - "type": "string" - }, - "firstName": { - "type": "string" - }, - "lastName": { - "type": "string" - }, - "source": { - "type": "string" - }, - "subscribed": { - "type": "boolean" - }, - "userGroup": { - "type": "string" - }, - "userId": { - "type": "string" - }, - "mailingLists": { - "type": "object", - "description": "An object of mailing list IDs and boolean subscription statuses.", - "examples": [ - { - "list_123": true - } - ] - } - } - }, - "ContactRequest": { - "type": "object", - "required": [ - "email" - ], - "properties": { - "email": { - "type": "string" - }, - "firstName": { - "type": "string" - }, - "lastName": { - "type": "string" - }, - "subscribed": { - "type": "boolean" - }, - "userGroup": { - "type": "string" - }, - "userId": { - "type": "string" - }, - "mailingLists": { - "type": "object", - "description": "An object of mailing list IDs and boolean subscription statuses.", - "examples": [ - { - "list_123": true - } - ] - } - } - }, - "ContactSuccessResponse": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "examples": [ - true - ] - }, - "id": { - "type": "string" - } - }, - "required": [ - "success", - "id" - ] - }, - "ContactFailureResponse": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "examples": [ - false - ] - }, - "message": { - "type": "string" - } - }, - "required": [ - "success", - "message" - ] - }, - "ContactDeleteRequest": { - "type": "object", - "properties": { - "email": { - "type": "string" - }, - "userId": { - "type": "string" - } - }, - "required": [ - "email", - "userId" - ] - }, - "ContactDeleteResponse": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "examples": [ - true - ] - }, - "message": { - "type": "string", - "examples": [ - "Contact deleted." - ] - } - }, - "required": [ - "success", - "message" - ] - }, - "EventRequest": { - "type": "object", - "required": [ - "eventName" - ], - "properties": { - "email": { - "type": "string" - }, - "userId": { - "type": "string" - }, - "eventName": { - "type": "string" - }, - "eventProperties": { - "type": "object", - "description": "An object containing event property data for the event, available in emails sent by the event." - }, - "mailingLists": { - "type": "object", - "description": "An object of mailing list IDs and boolean subscription statuses.", - "examples": [ - { - "list_123": true - } - ] - } - } - }, - "EventSuccessResponse": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "examples": [ - true - ] - } - }, - "required": [ - "success" - ] - }, - "EventFailureResponse": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "examples": [ - false - ] - }, - "message": { - "type": "string" - } - }, - "required": [ - "success", - "message" - ] - }, - "TransactionalRequest": { - "type": "object", - "required": [ - "email", - "transactionalId" - ], - "properties": { - "email": { - "type": "string" - }, - "transactionalId": { - "type": "string", - "description": "The ID of the transactional email to send." - }, - "addToAudience": { - "type": "boolean", - "description": "If `true`, a contact will be created in your audience using the `email` value (if a matching contact doesn't already exist)." - }, - "dataVariables": { - "type": "object", - "description": "An object containing contact data as defined by the data variables added to the transactional email template.", - "examples": [ - { - "name": "Chris", - "passwordResetLink": "https://example.com/reset-password" - } - ] - }, - "attachments": { - "type": "array", - "description": "A list containing file objects to be sent along with an email message.", - "items": { - "type": "object", - "required": [ - "filename", - "contentType", - "data" - ], - "properties": { - "filename": { - "type": "string", - "description": "The name of the file, shown in email clients." - }, - "contentType": { - "type": "string", - "description": "The MIME type of the file." - }, - "data": { - "type": "string", - "description": "The base64-encoded content of the file." - } - } - } - } - } - }, - "TransactionalSuccessResponse": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "examples": [ - true - ] - } - }, - "required": [ - "success" - ] - }, - "TransactionalFailureResponse": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "examples": [ - false - ] - }, - "path": { - "type": "string" - }, - "message": { - "type": "string" - } - }, - "required": [ - "success", - "path", - "message" - ] - }, - "TransactionalFailure2Response": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "examples": [ - false - ] - }, - "error": { - "type": "object", - "properties": { - "path": { - "type": "string" - }, - "message": { - "type": "string" - } - } - } - }, - "required": [ - "success", - "error" - ] - }, - "TransactionalFailure3Response": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "examples": [ - false - ] - }, - "message": { - "type": "string" - } - }, - "required": [ - "success", - "message" - ] - }, - "CustomField": { - "type": "object", - "properties": { - "key": { - "type": "string" - }, - "label": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "examples": [ - { - "key": "favoriteColor", - "label": "Favorite color", - "type": "string" - } - ], - "required": [ - "key", - "label", - "type" - ] - }, - "MailingList": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "isPublic": { - "type": "boolean" - } - }, - "examples": [ - { - "id": "list_123", - "name": "Main mailing list", - "isPublic": true - } - ], - "required": [ - "id", - "name", - "isPublic" - ] - } - }, - "securitySchemes": { - "apiKey": { - "type": "http", - "scheme": "bearer" - } - } - } -} \ No newline at end of file