Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(domains_v1): add support for new UDM endpoints #577

Merged
merged 20 commits into from
Jan 17, 2025
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
208 changes: 208 additions & 0 deletions fastly/domainsV1.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
package fastly

import (
"encoding/json"
"fmt"
"net/http"
"strconv"
"time"
)

// DomainsV1Response is the API response structure.
type DomainsV1Response struct {
// Data contains the API data.
Data []DomainsV1Data `json:"data"`
// Meta contains metadata related to paginating the full dataset.
Meta DomainsV1Meta `json:"meta"`
}

// DomainsV1Data is a subset of the API response structure containing the
// specific API data itself.
type DomainsV1Data struct {
// CreatedAt is the date and time in ISO 8601 format.
Integralist marked this conversation as resolved.
Show resolved Hide resolved
CreatedAt time.Time `json:"created_at"`
// ID is the domain identifier (UUID).
DomainID string `json:"id"`
// FQDN is the fully-qualified domain name for your domain. Can be created,
// but not updated.
Integralist marked this conversation as resolved.
Show resolved Hide resolved
FQDN string `json:"fqdn"`
// ServiceID is the service_id associated with your domain or null if there
Integralist marked this conversation as resolved.
Show resolved Hide resolved
// is no association.
ServiceID *string `json:"service_id"`
// UpdatedAt is the date and time in ISO 8601 format.
UpdatedAt time.Time `json:"updated_at"`
}

// DomainsV1Meta is a subset of the API response structure containing metadata
// related to paginating the full dataset.
type DomainsV1Meta struct {
// Limit is how many results are returned.
Integralist marked this conversation as resolved.
Show resolved Hide resolved
Limit int `json:"limit"`
Integralist marked this conversation as resolved.
Show resolved Hide resolved
// NextCursor is the cursor value used to retrieve the next page.
NextCursor string `json:"next_cursor"`
// Sort is the order in which to list the results.
Integralist marked this conversation as resolved.
Show resolved Hide resolved
Sort string `json:"sort"`
// Total is the total number of results.
Total int `json:"total"`
}

// ListDomainsV1Input is used as input to the ListDomainsV1 function.
type ListDomainsV1Input struct {
// Cursor is the cursor value from the next_cursor field of a previous
// response, used to retrieve the next page. To request the first page, this
// should be empty (optional).
Integralist marked this conversation as resolved.
Show resolved Hide resolved
Cursor *string
// FQDN filters results by the FQDN using a fuzzy/partial match (optional).
FQDN *string
// Limit is how many results are returned (optional).
Integralist marked this conversation as resolved.
Show resolved Hide resolved
Limit *int
// ServiceID filter results based on a service_id (optional).
ServiceID *string
// Sort is the order in which to list the results (optional).
Sort *string
}

// ListDomainsV1 retrieves filtered, paginated domains.
Integralist marked this conversation as resolved.
Show resolved Hide resolved
func (c *Client) ListDomainsV1(i *ListDomainsV1Input) (*DomainsV1Response, error) {
ro := &RequestOptions{
Params: map[string]string{},
}
if i.Cursor != nil {
ro.Params["cursor"] = *i.Cursor
Integralist marked this conversation as resolved.
Show resolved Hide resolved
}
if i.Limit != nil {
ro.Params["limit"] = strconv.Itoa(*i.Limit)
}
if i.FQDN != nil {
ro.Params["fqdn"] = *i.FQDN
}
if i.ServiceID != nil {
ro.Params["service_id"] = *i.ServiceID
}
if i.Sort != nil {
ro.Params["sort"] = *i.Sort
}

resp, err := c.Get("/domains/v1", ro)
if err != nil {
return nil, err
}
defer resp.Body.Close()

var dr *DomainsV1Response
if err := json.NewDecoder(resp.Body).Decode(&dr); err != nil {
return nil, fmt.Errorf("failed to decode json response: %w", err)
}

return dr, nil
}

// CreateDomainsV1Input is used as input to the CreateDomainsV1 function.
type CreateDomainsV1Input struct {
// FQDN is the fully-qualified domain name for your domain. Can be created,
// but not updated (required).
Integralist marked this conversation as resolved.
Show resolved Hide resolved
FQDN *string `json:"fqdn"`
// ServiceID is the service_id associated with your domain or null if there
Integralist marked this conversation as resolved.
Show resolved Hide resolved
// is no association (optional)
ServiceID *string `json:"service_id"`
}

// CreateDomainsV1 creates a new domain.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be singular, not plural, if it can only create a single domain.

func (c *Client) CreateDomainsV1(i *CreateDomainsV1Input) (*DomainsV1Data, error) {
resp, err := c.PostJSON("/domains/v1", i, nil)
if err != nil {
return nil, err
}
defer resp.Body.Close()

var dd *DomainsV1Data
if err := json.NewDecoder(resp.Body).Decode(&dd); err != nil {
return nil, fmt.Errorf("failed to decode json response: %w", err)
}
return dd, nil
}

// GetDomainsV1Input is used as input to the GetDomainsV1 function.
type GetDomainsV1Input struct {
// DomainID is the domain identifier (required).
DomainID *string
}

// GetDomainsV1 retrieves a specified domain.
Integralist marked this conversation as resolved.
Show resolved Hide resolved
func (c *Client) GetDomainsV1(i *GetDomainsV1Input) (*DomainsV1Data, error) {
if i.DomainID == nil {
return nil, ErrMissingDomainID
}

path := ToSafeURL("domains", "v1", *i.DomainID)

resp, err := c.Get(path, nil)
if err != nil {
return nil, err
}
defer resp.Body.Close()

var dd *DomainsV1Data
if err := json.NewDecoder(resp.Body).Decode(&dd); err != nil {
return nil, fmt.Errorf("failed to decode json response: %w", err)
}

return dd, nil
}

// UpdateDomainsV1Input is used as input to the UpdateDomainsV1 function.
Integralist marked this conversation as resolved.
Show resolved Hide resolved
type UpdateDomainsV1Input struct {
// DomainID is the domain identifier (required).
DomainID *string `json:"-"`
// ServiceID is the service_id associated with your domain or null if there
Integralist marked this conversation as resolved.
Show resolved Hide resolved
// is no association (optional)
ServiceID *string `json:"service_id"`
}

// UpdateDomainsV1 updates the specified domain.
func (c *Client) UpdateDomainsV1(i *UpdateDomainsV1Input) (*DomainsV1Data, error) {
if i.DomainID == nil {
return nil, ErrMissingDomainID
}

path := ToSafeURL("domains", "v1", *i.DomainID)

resp, err := c.PatchJSON(path, i, nil)
if err != nil {
return nil, err
}
defer resp.Body.Close()

var dd *DomainsV1Data
if err := json.NewDecoder(resp.Body).Decode(&dd); err != nil {
return nil, fmt.Errorf("failed to decode json response: %w", err)
}
return dd, nil
}

// DeleteDomainsV1Input is used as input to the DeleteDomainsV1 function.
type DeleteDomainsV1Input struct {
Integralist marked this conversation as resolved.
Show resolved Hide resolved
// DomainID of definition to delete (required).
Integralist marked this conversation as resolved.
Show resolved Hide resolved
Integralist marked this conversation as resolved.
Show resolved Hide resolved
DomainID *string
}

// DeleteDomainsV1 deletes the specified domain.
func (c *Client) DeleteDomainsV1(i *DeleteDomainsV1Input) error {
if i.DomainID == nil {
return ErrMissingDomainID
}

path := ToSafeURL("domains", "v1", *i.DomainID)

resp, err := c.Delete(path, nil)
if err != nil {
return err
}
defer resp.Body.Close()

if resp.StatusCode != http.StatusNoContent {
return NewHTTPError(resp)
}

return nil
}
124 changes: 124 additions & 0 deletions fastly/domainsV1_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
package fastly

import (
"errors"
"testing"
)

func TestClient_DomainsV1(t *testing.T) {
t.Parallel()

var err error
fqdn := "fastly-sdk-gofastly-testing.com"

// Create
var dd *DomainsV1Data
Record(t, "domains_v1/create_domain", func(c *Client) {
dd, err = c.CreateDomainsV1(&CreateDomainsV1Input{
FQDN: ToPointer(fqdn),
})
})
if err != nil {
t.Fatal(err)
}
// Ensure deleted
defer func() {
Record(t, "domains_v1/cleanup_domains", func(c *Client) {
err = c.DeleteDomainsV1(&DeleteDomainsV1Input{
DomainID: &dd.DomainID,
})
})
}()

if dd.FQDN != fqdn {
t.Errorf("bad fqdn: %v", dd.FQDN)
}
if dd.ServiceID != nil {
t.Errorf("bad service_id: %v", dd.ServiceID)
}

// List Definitions
var ldr *DomainsV1Response
Record(t, "domains_v1/list_domains", func(c *Client) {
ldr, err = c.ListDomainsV1(&ListDomainsV1Input{
// Cursor: ToPointer(""),
Limit: ToPointer(10),
FQDN: ToPointer(dd.FQDN),
Sort: ToPointer("fqdn"),
})
})
if err != nil {
t.Fatal(err)
}
if len(ldr.Data) < 1 {
t.Errorf("bad domains list: %v", ldr)
}

// Get
var gdd *DomainsV1Data
Record(t, "domains_v1/get_domain", func(c *Client) {
gdd, err = c.GetDomainsV1(&GetDomainsV1Input{
DomainID: &dd.DomainID,
})
})
if err != nil {
t.Fatal(err)
}
if dd.FQDN != gdd.FQDN {
t.Errorf("bad fqdn: %q (%q)", dd.FQDN, gdd.FQDN)
}

// Update
var udd *DomainsV1Data
Record(t, "domains_v1/update_domain", func(c *Client) {
udd, err = c.UpdateDomainsV1(&UpdateDomainsV1Input{
DomainID: ToPointer(dd.DomainID),
ServiceID: ToPointer(defaultDeliveryTestServiceID),
})
})
if err != nil {
t.Fatal(err)
}
if udd.ServiceID == nil || *udd.ServiceID != defaultDeliveryTestServiceID {
t.Errorf("bad service id: %v", udd.ServiceID)
}

// Delete
Record(t, "domains_v1/delete_domain", func(c *Client) {
Integralist marked this conversation as resolved.
Show resolved Hide resolved
err = c.DeleteDomainsV1(&DeleteDomainsV1Input{
DomainID: &dd.DomainID,
})
})
if err != nil {
t.Fatal(err)
}
}

func TestClient_GetDomainsV1_validation(t *testing.T) {
var err error
_, err = TestClient.GetDomainsV1(&GetDomainsV1Input{
DomainID: nil,
})
if !errors.Is(err, ErrMissingDomainID) {
t.Errorf("bad error: %s", err)
}
}

func TestClient_UpdateDomainsV1_validation(t *testing.T) {
var err error
_, err = TestClient.UpdateDomainsV1(&UpdateDomainsV1Input{
DomainID: nil,
})
if !errors.Is(err, ErrMissingDomainID) {
t.Errorf("bad error: %s", err)
}
}

func TestClient_DeleteDomainsV1_validation(t *testing.T) {
err := TestClient.DeleteDomainsV1(&DeleteDomainsV1Input{
DomainID: nil,
})
if !errors.Is(err, ErrMissingDomainID) {
t.Errorf("bad error: %s", err)
}
}
4 changes: 4 additions & 0 deletions fastly/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,10 @@ var ErrMissingTokenID = errors.New("missing required field 'TokenID'")
// requires a "ID" key, but one was not set.
var ErrMissingID = NewFieldError("ID")

// ErrMissingDomainID is an error that is returned when an input struct
// requires a "DomainID" key, but one was not set.
var ErrMissingDomainID = NewFieldError("DomainID")

// ErrMissingEntryID is an error that is returned when an input struct
// requires a "EntryID" key, but one was not set.
var ErrMissingEntryID = NewFieldError("EntryID")
Expand Down
46 changes: 46 additions & 0 deletions fastly/fixtures/domains_v1/cleanup_domains.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
---
version: 1
Integralist marked this conversation as resolved.
Show resolved Hide resolved
interactions:
- request:
body: ""
form: {}
headers:
User-Agent:
- FastlyGo/9.11.0 (+github.com/fastly/go-fastly; go1.20.14)
url: https://api.fastly.com/domains/v1/hbRABkI8yvSnUbCLRiqUbQ
method: DELETE
response:
body: '{"errors":[{"title":"Not found","detail":"The domain you requested was
not found"}]}'
headers:
Accept-Ranges:
- bytes
Cache-Control:
- no-store
Content-Length:
- "84"
Content-Type:
- application/json
Date:
- Tue, 14 Jan 2025 14:21:10 GMT
Pragma:
- no-cache
Server:
- fastly control-gateway
Strict-Transport-Security:
- max-age=31536000
Via:
- 1.1 varnish, 1.1 varnish
X-Cache:
- MISS, MISS
X-Cache-Hits:
- 0, 0
X-Content-Type-Options:
- nosniff
X-Served-By:
- cache-chi-kigq8000030-CHI, cache-lon420121-LON
X-Timer:
- S1736864470.058911,VS0,VE170
status: 404 Not Found
code: 404
duration: ""
Loading
Loading