Skip to content

feat(contact): add support for customer contacts#813

Open
bclerc wants to merge 6 commits into
fastly:mainfrom
bclerc:feat/customer-contacts
Open

feat(contact): add support for customer contacts#813
bclerc wants to merge 6 commits into
fastly:mainfrom
bclerc:feat/customer-contacts

Conversation

@bclerc

@bclerc bclerc commented May 22, 2026

Copy link
Copy Markdown

Change summary

Adds support for the customer contacts API under /customer/{customer_id}/contacts.

Three new client methods:

  • Client.ListContacts
  • Client.CreateContact
  • Client.DeleteContact

The endpoint uses JSON:API with the customer_contact resource type. The Fastly API does not expose an update endpoint for contacts, so only list/create/delete are implemented.

The integration test creates a guard contact first because the API refuses to delete the last contact of a given type (You cannot remove the last <type> contact).

All Submissions:

  • Have you followed the guidelines in our Contributing document?
  • Have you checked to ensure there aren't other open Pull Requests for the same update/change?

New Feature Submissions:

  • Does your submission pass tests?

User Impact

Users can now manage customer-level contacts (primary, billing, technical, security, emergency) through the Go client instead of having to call the API directly. This unblocks support for customer contacts in the Terraform provider.

Are there any considerations that need to be addressed for release?

No breaking changes. New surface only.

Adds Client.ListContacts, Client.CreateContact, and Client.DeleteContact
backed by /customer/{customer_id}/contacts. The endpoint uses JSON:API
with the customer_contact resource type. The Fastly API does not expose
an update endpoint for contacts, so only list/create/delete are
implemented.

Includes VCR fixtures and validation tests for missing required inputs.
@bclerc bclerc requested a review from a team as a code owner May 22, 2026 13:08
@bclerc bclerc requested a review from kpfleming May 22, 2026 13:08
@kpfleming

Copy link
Copy Markdown
Member

Thanks for the PR! Unfortunately we are no longer adding API endpoints to the top-level fastly package unless they belong there because there are existing endpoints for the same resource type. Since this is a new resource type, it will need to be put into fastly/customer/contacts - refer to fastly/apisecurity/operations for an example of how this is done.

@bclerc

bclerc commented May 22, 2026

Copy link
Copy Markdown
Author

Thanks for the quick review and for pointing me to the right pattern! I'll move the new endpoints into a dedicated fastly/customer/contacts sub-package and use fastly/apisecurity/operations as the reference. I'll push the refactor shortly.

Per review feedback on fastly#813, move the customer contacts API
(list/create/delete) out of the root fastly package and into
fastly/customer/contacts/, matching the convention established by
fastly/apisecurity/operations.

- Rename ListContacts/CreateContact/DeleteContact to
  contacts.{List,Create,Delete} with (ctx, *fastly.Client, *Input)
  signatures and pointer-valued input fields.
- Keep JSON:API marshaling via Client.PostJSONAPI and the
  google/jsonapi unmarshaling helpers; preserve the internal payload
  struct that omits CustomerID from the body.
- Move fixtures to fastly/customer/contacts/fixtures/ (API paths and
  cassette contents unchanged).
- Update CHANGELOG entry scope to customer/contacts.
@bclerc

bclerc commented May 22, 2026

Copy link
Copy Markdown
Author

Done — I've pushed the refactor. The contacts client now lives at fastly/customer/contacts following the fastly/apisecurity/operations layout (Create/List/Delete with (ctx, *fastly.Client, *Input) signatures, pointer-valued inputs, fixtures moved under the sub-package). The endpoint is JSON:API so the code imports github.com/google/jsonapi directly for MediaType and the unmarshaling helpers — happy to adjust if you'd prefer a different approach there. CHANGELOG entry updated to feat(customer/contacts).

@kpfleming

Copy link
Copy Markdown
Member

Thanks - we're about to start a three day weekend, so we'll look into this next week.

@kpfleming kpfleming left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Some small change requests, but overall this look great!

Comment thread fastly/customer/contacts/api_create.go Outdated
Comment on lines +83 to +88
func deref(s *string) string {
if s == nil {
return ""
}
return *s
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I believe ToValue() already exists for this purpose.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Thanks, I'd missed that one. Updated to use fastly.ToValue and dropped the local helper.

Comment on lines +19 to +27
fastly.Record(t, "get_current_user", func(c *fastly.Client) {
var u *fastly.User
u, err = c.GetCurrentUser(ctx)
if err == nil && u != nil && u.CustomerID != nil {
customerID = *u.CustomerID
}
})
require.NoError(t, err)
require.NotEmpty(t, customerID)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This does not need to be recorded as a fixture, it's an API call which is not part of the package being tested.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Good point. Removed the GetCurrentUser call and its fixture. I now source the customer ID from a hardcoded constant in the test, the same way apisecurity/operations uses fastly.TestDeliveryServiceID for its service ID.

GetCurrentUser is an API call outside the package being tested, so it
should not be recorded as a fixture. Use a hardcoded customer ID
instead, matching the apisecurity/operations pattern.

// The customer ID is an opaque identifier that is only replayed against the
// recorded fixtures; its exact value does not matter for the test.
customerID := "7i6ZbMEdjnTUNyk75XgWO0"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This should probably use the GetCurrentCustomer API endpoint (https://www.fastly.com/documentation/reference/api/account/customer/#get-logged-in-customer), but without recording it into the fixtures. This can be done by using DefaultClient() to get a regular Fastly client, avoiding the recording process.

@bclerc bclerc Jun 7, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Sorry for the delay, busy week. GetCurrentCustomer isn't implemented in go-fastly today, so i'm not sure i follow what you mean here. I hardcoded the customer id because that's how it's done in the other tests (token_test.go, account_event_test.go), so i followed the same pattern.
If you'd rather fetch it at runtime, the closest thing i see is to grab a regular client with DefaultClient() and call GetCurrentUser, the returned User have a CustomerID field, something like:

user, err := fastly.DefaultClient().GetCurrentUser(ctx)
require.NoError(t, err)
customerID := fastly.ToValue(user.CustomerID)

Let me know which you prefer.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The underlying issue is that hardcoding a CID won't work, because it will have to match the CID linked to the token that is being used to authenticate to the API or the tests will fail. If we followed the existing pattern there would have to be additions to fastly_test_utils.go to hold the hardcoded CID, and every developer trying to run this test would have to provide the CID which matches the token they are using.

However, since these are the only API calls/tests which require a CID, I would be fine with determining that CID at runtime so that running the tests doesn't require additional setup. While GetCurrentUser can be used for that, we've also seen a number of cases where that API operation returns an error because the API token is service-limited or has some other limits on it, but GetCurrentCustomer should not fail in those cases. That's why I suggested using GetCurrentCustomer.

We can implement GetCurrentCustomer if that's something you'd prefer us to do. it would be implemented in the fastly package since it's a client-related operation and does not need to be in a subpackage.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants