api

package
v0.1.0-alpha.6 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 22, 2026 License: MIT Imports: 16 Imported by: 0

Documentation

Overview

Package api — diagnostics helpers for mapping API errors to Terraform diagnostics.

The DevHelm API surfaces a uniform `{status, message, timestamp}` envelope for every non-2xx response. The TF provider's job is to translate those generic errors into per-attribute diagnostics so practitioners see the failing field highlighted in `terraform plan`/`apply` output instead of a summary message floating at the top of the run.

The helpers in this file centralize that translation. Resources call AddAPIError (or one of the operation-specific wrappers) instead of resp.Diagnostics.AddError directly, passing the attribute path (when known) that the error most plausibly originates from. The helper picks the most specific diagnostic shape available:

  • 404 from a Read after import → AddAttributeError on path.Root("id")
  • 409 ("already exists") on Create → AddAttributeError on the name field
  • 400 with a "field <X>" hint → AddAttributeError on that path
  • everything else → AddError with the operation context

Centralizing the mapping keeps the resource files free of repetitive switch-on-error-type boilerplate and ensures we apply the same rules everywhere.

Package api — derived enum lists.

The TF schema validators (stringvalidator.OneOf, …) need string slices to surface the legal value set in `terraform validate` output. The generated types declare each enum constant individually, so we re-export them as flat slices here. Codegen stays the source of truth — a new spec value will appear as a new constant in the generated package, and the test `TestEnumSliceCoverage` (in `enums_coverage_test.go`) verifies these slices stay exhaustive by reflecting over the generated package.

Adding a new slice for a generated enum type:

  1. Add the slice here, populated from the generated constants.
  2. Wire it into the relevant `Schema()` via `stringvalidator.OneOf(api.<X>...)` instead of literal string lists — this both eliminates DRY drift and keeps `TestEnumSliceCoverage` exhaustiveness in one place.
  3. Add the slice to the `enumSliceCoverage` table in `enums_coverage_test.go` so future spec additions force-fail until the slice is updated.

Index

Constants

View Source
const (
	// Top-level collections.
	PathAlertChannels        = "/api/v1/alert-channels"
	PathEnvironments         = "/api/v1/environments"
	PathMonitors             = "/api/v1/monitors"
	PathNotificationPolicies = "/api/v1/notification-policies"
	PathResourceGroups       = "/api/v1/resource-groups"
	PathSecrets              = "/api/v1/secrets"
	PathServiceSubscriptions = "/api/v1/service-subscriptions"
	PathStatusPages          = "/api/v1/status-pages"
	PathTags                 = "/api/v1/tags"
	PathWebhooks             = "/api/v1/webhooks"
)

Variables

AlertChannelTypes lists every wire-format alert channel kind. Used by the alert_channel resource's `channel_type` validator (and by anything else that needs to discriminate channels by wire type).

View Source
var AssertionTypes = []string{
	string(generated.MonitorAssertionDtoAssertionTypeBodyContains),
	string(generated.MonitorAssertionDtoAssertionTypeDnsExpectedCname),
	string(generated.MonitorAssertionDtoAssertionTypeDnsExpectedIps),
	string(generated.MonitorAssertionDtoAssertionTypeDnsMaxAnswers),
	string(generated.MonitorAssertionDtoAssertionTypeDnsMinAnswers),
	string(generated.MonitorAssertionDtoAssertionTypeDnsRecordContains),
	string(generated.MonitorAssertionDtoAssertionTypeDnsRecordEquals),
	string(generated.MonitorAssertionDtoAssertionTypeDnsResolves),
	string(generated.MonitorAssertionDtoAssertionTypeDnsResponseTime),
	string(generated.MonitorAssertionDtoAssertionTypeDnsResponseTimeWarn),
	string(generated.MonitorAssertionDtoAssertionTypeDnsTtlHigh),
	string(generated.MonitorAssertionDtoAssertionTypeDnsTtlLow),
	string(generated.MonitorAssertionDtoAssertionTypeDnsTxtContains),
	string(generated.MonitorAssertionDtoAssertionTypeHeaderValue),
	string(generated.MonitorAssertionDtoAssertionTypeHeartbeatIntervalDrift),
	string(generated.MonitorAssertionDtoAssertionTypeHeartbeatMaxInterval),
	string(generated.MonitorAssertionDtoAssertionTypeHeartbeatPayloadContains),
	string(generated.MonitorAssertionDtoAssertionTypeHeartbeatReceived),
	string(generated.MonitorAssertionDtoAssertionTypeIcmpPacketLoss),
	string(generated.MonitorAssertionDtoAssertionTypeIcmpReachable),
	string(generated.MonitorAssertionDtoAssertionTypeIcmpResponseTime),
	string(generated.MonitorAssertionDtoAssertionTypeIcmpResponseTimeWarn),
	string(generated.MonitorAssertionDtoAssertionTypeJsonPath),
	string(generated.MonitorAssertionDtoAssertionTypeMcpConnects),
	string(generated.MonitorAssertionDtoAssertionTypeMcpHasCapability),
	string(generated.MonitorAssertionDtoAssertionTypeMcpMinTools),
	string(generated.MonitorAssertionDtoAssertionTypeMcpProtocolVersion),
	string(generated.MonitorAssertionDtoAssertionTypeMcpResponseTime),
	string(generated.MonitorAssertionDtoAssertionTypeMcpResponseTimeWarn),
	string(generated.MonitorAssertionDtoAssertionTypeMcpToolAvailable),
	string(generated.MonitorAssertionDtoAssertionTypeMcpToolCountChanged),
	string(generated.MonitorAssertionDtoAssertionTypeRedirectCount),
	string(generated.MonitorAssertionDtoAssertionTypeRedirectTarget),
	string(generated.MonitorAssertionDtoAssertionTypeRegexBody),
	string(generated.MonitorAssertionDtoAssertionTypeResponseSize),
	string(generated.MonitorAssertionDtoAssertionTypeResponseTime),
	string(generated.MonitorAssertionDtoAssertionTypeResponseTimeWarn),
	string(generated.MonitorAssertionDtoAssertionTypeSslExpiry),
	string(generated.MonitorAssertionDtoAssertionTypeStatusCode),
	string(generated.MonitorAssertionDtoAssertionTypeTcpConnects),
	string(generated.MonitorAssertionDtoAssertionTypeTcpResponseTime),
	string(generated.MonitorAssertionDtoAssertionTypeTcpResponseTimeWarn),
}

AssertionTypes lists every wire-format assertion type. Used by the monitor resource's `assertions[*].type` validator.

MatchRuleTypes lists every wire-format notification-policy match-rule kind. Used by the notification_policy resource's `match_rule[*].type` validator.

Functions

func AddAPIError

func AddAPIError(diagnostics *diag.Diagnostics, op string, err error, identityAttr path.Path)

AddAPIError translates a generic API error into the most specific diagnostic shape available. `op` is a short imperative verb describing what the resource was attempting (e.g. "create monitor"). `identityAttr` is the path to the attribute that uniquely names the resource within the workspace (typically path.Root("name") or path.Root("slug")); pass path.Empty() when no such anchor applies.

The function is a no-op when err is nil so callers can wrap the entire API call site without needing a separate guard.

func AddNotFoundError

func AddNotFoundError(diagnostics *diag.Diagnostics, resourceLabel, id string)

AddNotFoundError emits the import-time "resource not found" diagnostic in a uniform shape across all resources. Use during ImportState when a list lookup returns zero results for the requested ID.

func AlertChannelPath

func AlertChannelPath(id string) string

AlertChannelPath returns /api/v1/alert-channels/{id}.

func Create

func Create[T any](ctx context.Context, c *Client, path string, body any) (*T, error)

func CreateList

func CreateList[T any](ctx context.Context, c *Client, path string, body any) ([]T, error)

CreateList POSTs to an endpoint that returns a TableResponse[T] (e.g. the tag-management sub-resources on monitors, which return the full collection after the mutation rather than a single entity). Use Create when the endpoint returns SingleValueResponse[T].

func CreateRaw

func CreateRaw[T any](ctx context.Context, c *Client, path string, body any) (*T, []byte, error)

CreateRaw mirrors Create but also returns the raw response body. See GetRaw.

func Delete

func Delete(ctx context.Context, c *Client, path string) error

func DeleteWithBody

func DeleteWithBody(ctx context.Context, c *Client, path string, body any) error

DeleteWithBody issues a DELETE with a JSON request body. Used for endpoints that accept a body (e.g. DELETE /monitors/{id}/tags with a list of tag IDs).

func EnvironmentPath

func EnvironmentPath(slug string) string

EnvironmentPath returns /api/v1/environments/{slug}.

func Get

func Get[T any](ctx context.Context, c *Client, path string) (*T, error)

func GetRaw

func GetRaw[T any](ctx context.Context, c *Client, path string) (*T, []byte, error)

GetRaw is the escape-hatch variant of Get that also returns the raw response body so callers can extract polymorphic / discriminated-union fields whose generated Go type loses information during a typed unmarshal (e.g. monitor `auth`, where the spec collapsed the oneOf into a base `MonitorAuthConfig{Type string}` and only the `type` discriminator survives). Use the typed result for everything else; only reach into the raw body for the specific field whose round-trip you need to preserve.

func IsNotFound

func IsNotFound(err error) bool

IsNotFound reports whether err is a *DevhelmAPIError with HTTP 404. Used by resources during Read to translate "deleted out-of-band" into the disappear-from-state path Terraform expects.

func List

func List[T any](ctx context.Context, c *Client, basePath string) ([]T, error)

func MonitorPath

func MonitorPath(id string) string

MonitorPath returns /api/v1/monitors/{id}.

func MonitorTagsPath

func MonitorTagsPath(monitorID string) string

MonitorTagsPath returns /api/v1/monitors/{id}/tags.

func NotificationPolicyPath

func NotificationPolicyPath(id string) string

NotificationPolicyPath returns /api/v1/notification-policies/{id}.

func Patch

func Patch[T any](ctx context.Context, c *Client, path string, body any) (*T, error)

func PathEscape

func PathEscape(s string) string

func ResourceGroupMemberPath

func ResourceGroupMemberPath(groupID, memberID string) string

ResourceGroupMemberPath returns /api/v1/resource-groups/{groupId}/members/{memberId}.

func ResourceGroupMembersPath

func ResourceGroupMembersPath(groupID string) string

ResourceGroupMembersPath returns /api/v1/resource-groups/{id}/members.

func ResourceGroupPath

func ResourceGroupPath(id string) string

ResourceGroupPath returns /api/v1/resource-groups/{id}.

func SecretPath

func SecretPath(key string) string

SecretPath returns /api/v1/secrets/{key}.

func ServiceSubscriptionAlertSensitivityPath

func ServiceSubscriptionAlertSensitivityPath(id string) string

ServiceSubscriptionAlertSensitivityPath returns /api/v1/service-subscriptions/{id}/alert-sensitivity.

func ServiceSubscriptionPath

func ServiceSubscriptionPath(idOrSlug string) string

ServiceSubscriptionPath returns /api/v1/service-subscriptions/{idOrSlug}. Callers that pass user-supplied input should pre-escape the segment.

func StatusPageComponentPath

func StatusPageComponentPath(pageID, componentID string) string

StatusPageComponentPath returns /api/v1/status-pages/{pageId}/components/{componentId}.

func StatusPageComponentsPath

func StatusPageComponentsPath(pageID string) string

StatusPageComponentsPath returns /api/v1/status-pages/{id}/components.

func StatusPageDomainPath

func StatusPageDomainPath(pageID, domainID string) string

StatusPageDomainPath returns /api/v1/status-pages/{pageId}/domains/{domainId}.

func StatusPageDomainVerifyPath

func StatusPageDomainVerifyPath(pageID, domainID string) string

StatusPageDomainVerifyPath returns /api/v1/status-pages/{pageId}/domains/{domainId}/verify.

func StatusPageDomainsPath

func StatusPageDomainsPath(pageID string) string

StatusPageDomainsPath returns /api/v1/status-pages/{id}/domains.

func StatusPageGroupPath

func StatusPageGroupPath(pageID, groupID string) string

StatusPageGroupPath returns /api/v1/status-pages/{pageId}/groups/{groupId}.

func StatusPageGroupsPath

func StatusPageGroupsPath(pageID string) string

StatusPageGroupsPath returns /api/v1/status-pages/{id}/groups.

func StatusPagePath

func StatusPagePath(id string) string

StatusPagePath returns /api/v1/status-pages/{id}.

func TagPath

func TagPath(id string) string

TagPath returns /api/v1/tags/{id}.

func Update

func Update[T any](ctx context.Context, c *Client, path string, body any) (*T, error)

func UpdateRaw

func UpdateRaw[T any](ctx context.Context, c *Client, path string, body any) (*T, []byte, error)

UpdateRaw mirrors Update but also returns the raw response body. See GetRaw.

func ValidateDTO

func ValidateDTO(dto any, context string) error

ValidateDTO performs structural validation on a DTO returned by the API.

It leverages the fact that oapi-codegen encodes the OpenAPI required/optional distinction directly in Go's type system:

  • Required fields are value types (string, UUID, enum, time.Time)
  • Optional fields are pointer types (*string, *bool, *int32)

The validator walks the struct and enforces two invariants:

  1. Non-pointer fields must not be zero-valued (catches missing required fields that json.Unmarshal silently accepts).
  2. Fields whose type implements Valid() bool must return true (catches enum values the provider doesn't recognize, e.g. after an API update adds a new variant).

This is the Go equivalent of Zod safeParse (SDK-JS) and Pydantic model_validate (SDK-Python) — runtime response validation driven by the spec, with zero hand-written per-DTO code.

func WebhookPath

func WebhookPath(id string) string

WebhookPath returns /api/v1/webhooks/{id}.

Types

type Client

type Client struct {
	BaseURL     string
	Token       string
	OrgID       string
	WorkspaceID string
	HTTPClient  *http.Client
	UserAgent   string
}

func NewClient

func NewClient(baseURL, token, orgID, workspaceID, version string) *Client

type DevhelmAPIError

type DevhelmAPIError struct {
	StatusCode int
	Code       string
	Message    string
	RequestID  string
	Body       string
}

DevhelmAPIError represents a non-2xx response from the DevHelm API. It is the error class every TF resource wraps via `api.AddAPIError` (or branches on via `api.IsNotFound`).

  • StatusCode is the HTTP status line (always non-zero for this error type).
  • Code mirrors `ErrorResponse.code` from the API — a stable, machine- readable category like "NOT_FOUND" or "RATE_LIMITED". May be empty for legacy error bodies that do not include the field.
  • Message is the human-readable text from `ErrorResponse.message`, or the `error` field, or — when neither is present — the raw response body.
  • RequestID is the `X-Request-Id` response header. Always include it in support tickets; when blank, the response did not carry the header (should not happen against the production API).
  • Body is the raw response body, retained for debugging non-conforming replies.

func (*DevhelmAPIError) Error

func (e *DevhelmAPIError) Error() string

type DevhelmTransportError

type DevhelmTransportError struct {
	// Op describes the failing transport step ("build request", "send
	// request", "read response"). Stable enough to switch on in tests.
	Op string
	// URL is the resolved target URL, useful when diagnosing DNS or TLS
	// issues against a specific endpoint.
	URL string
	// Err is the underlying transport-level error.
	Err error
}

DevhelmTransportError represents a failure to complete an HTTP exchange: the request never reached the server, the server never returned a complete response, or a TLS/DNS/connection layer surfaced an error. Wraps the underlying error so callers can `errors.As` for the original cause.

func (*DevhelmTransportError) Error

func (e *DevhelmTransportError) Error() string

func (*DevhelmTransportError) Unwrap

func (e *DevhelmTransportError) Unwrap() error

type RequestBody

type RequestBody = any

RequestBody is the P5-tracked boundary type for any request payload that `doRequest` will serialize via `json.Marshal`. Callers must pass a typed struct from `internal/generated` (or a properly tagged handwritten equivalent), never a raw `map[string]any`. The alias is here so a future audit can grep for `RequestBody` and find every site that crosses the json.Marshal boundary, even though Go's type system can't prevent the `map[string]any` case at compile time.

type SingleValueResponse

type SingleValueResponse[T any] struct {
	Data T `json:"data"`
}

SingleValueResponse is the single-value envelope used by most endpoints.

Note on the zero-value contract: when the upstream JSON is malformed or the server returns a 2xx without a `data` field (which the API contract should never produce, but is worth being explicit about), `Data` will be the zero value of `T` and `Get`/`Create`/`Update`/`Patch` will return a non-nil pointer to that zero value. Callers should NOT treat a non-nil return as "the resource exists" — they should rely on the per-resource semantic invariants instead (e.g. `dto.Id != uuid.Nil` for newly-created resources, or `IsNotFound(err)` for explicit 404 handling). Nil-checking `&resp.Data` itself is meaningless because the address of a value-typed struct field inside a stack-allocated struct is always non-nil.

type TableResponse

type TableResponse[T any] struct {
	Data          []T    `json:"data"`
	HasNext       bool   `json:"hasNext"`
	HasPrev       bool   `json:"hasPrev,omitempty"`
	TotalElements *int64 `json:"totalElements,omitempty"`
	TotalPages    *int32 `json:"totalPages,omitempty"`
}

Table response wrapper used by list endpoints. Mirrors the API's full pagination envelope; fields beyond Data/HasNext are unused by the provider today but must be declared so that the strict decoder doesn't reject them.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL