Skip to main content
Version: 1.0.2

Working at Low Level

Why this page exists

The SDK is designed around a single-import principle: importing only github.com/Arubacloud/sdk-go/pkg/aruba covers ~99.9% of real-world use cases.

The wrapper surface — typed accessors, WaitUntil* helpers, RawJSON() / RawYAML() — is designed so you rarely need to reach into the underlying wire structs. For the 0.1% of cases where you do, this page collects every escape hatch that requires a second import.

These patterns are intentional and supported — they are escape hatches, not workarounds. When you hit a case not covered by the wrapper API, reach for these rather than opening a feature request.


Accessing non-promoted wire fields with Raw()

Every wrapper exposes a Raw() method that returns the underlying *types.XxxResponse struct. Use it when you need a field that hasn't been promoted to the wrapper surface:

import (
"github.com/Arubacloud/sdk-go/pkg/aruba"
"github.com/Arubacloud/sdk-go/pkg/types"
)

vpc, err := arubaClient.FromNetwork().VPCs().Get(ctx, ref)
if err != nil { /* … */ }

raw := vpc.Raw() // *types.VPCResponse
fmt.Println(raw.Properties.IsDefault) // field not promoted to the wrapper

For lists, Raw() returns any and you type-assert to the concrete list type:

vpcList, err := arubaClient.FromNetwork().VPCs().List(ctx, proj)
if err != nil { /* … */ }

raw := vpcList.Raw().(*types.VPCListResponse) // requires pkg/types import
fmt.Println("server total:", raw.Total) // same as vpcList.Total() — shown for illustration
fmt.Println("self link:", raw.Self)

Prefer wrapper accessors for serialisation. If your goal is JSON or YAML output, use vpc.RawJSON() / vpc.RawYAML() (or the List[T] equivalents) — no pkg/types import required.

fmt.Println(string(vpcList.RawJSON()))  // JSON without pkg/types
fmt.Println(string(vpcList.RawYAML())) // YAML without pkg/types

Inspecting structured validation errors

*aruba.HTTPError is the error type for all 4xx/5xx responses. Its ErrResp field is a *types.ErrorResponse and holds structured RFC 7807 details — including a []types.ValidationError slice for field-level 400 errors:

import (
"errors"
"github.com/Arubacloud/sdk-go/pkg/aruba"
"github.com/Arubacloud/sdk-go/pkg/types"
)

_, err := arubaClient.FromNetwork().VPCs().Create(ctx, vpc)
if err != nil {
var httpErr *aruba.HTTPError
if errors.As(err, &httpErr) && httpErr.ErrResp != nil {
fmt.Printf("title: %s\n", derefStr(httpErr.ErrResp.Title))
fmt.Printf("detail: %s\n", derefStr(httpErr.ErrResp.Detail))

// Field-level validation errors — require types.ValidationError
for _, ve := range httpErr.ErrResp.Errors {
fmt.Printf(" field %s: %s\n", ve.Field, ve.Message)
}

// TraceID for support requests
fmt.Printf("trace-id: %s\n", derefStr(httpErr.ErrResp.TraceID))
}
}

MetadataValidationError

A *types.MetadataValidationError is returned (alongside a non-nil wrapper) when an API response is missing required metadata fields (id or uri). Use errors.As to detect it:

import (
"errors"
"github.com/Arubacloud/sdk-go/pkg/aruba"
"github.com/Arubacloud/sdk-go/pkg/types"
)

result, err := arubaClient.FromNetwork().VPCs().Create(ctx, vpc)
if err != nil {
var mvErr *types.MetadataValidationError
if errors.As(err, &mvErr) {
// result is non-nil and partially hydrated; mvErr lists the missing fields
fmt.Printf("metadata incomplete: %v\n", mvErr)
fmt.Printf("ID so far: %s\n", result.ID())
}
}

Iterating LinkedResources()

Every resource wrapper exposes LinkedResources() which returns []types.LinkedResourceCommon. Each entry has a URI string and a StrictCorrelation bool:

import (
"github.com/Arubacloud/sdk-go/pkg/aruba"
"github.com/Arubacloud/sdk-go/pkg/types"
)

vpc, err := arubaClient.FromNetwork().VPCs().Get(ctx, ref)
if err != nil { /* … */ }

for _, lr := range vpc.LinkedResources() {
fmt.Println("linked URI:", lr.URI)
if lr.StrictCorrelation {
fmt.Println(" → strict correlation (lifecycle-linked)")
}
}

If you only need the linked URI strings — for example, to pass them back to another SDK call as aruba.URI(lr.URI) — you don't need types.LinkedResourceCommon at all:

for _, lr := range vpc.LinkedResources() {
ref := aruba.URI(lr.URI) // no pkg/types import
_ = ref
}

Inspecting request bodies before they are sent

Every wrapper exposes RawRequest() which returns the wire-level request struct (*types.XxxRequest). This is useful for debugging or for feeding the request into another tool:

import (
"encoding/json"
"github.com/Arubacloud/sdk-go/pkg/aruba"
"github.com/Arubacloud/sdk-go/pkg/types"
)

vpc := aruba.NewVPC().
Named("my-vpc").
InProject(proj).
InRegion(aruba.RegionITBGBergamo).
AsDefault()

req := vpc.RawRequest() // types.VPCRequest — requires pkg/types import
b, _ := json.MarshalIndent(req, "", " ")
fmt.Println(string(b))

Background polling with pkg/async

WaitUntilReady, WaitUntilActive, and WaitUntilStates block the calling goroutine. If you need to start multiple waits concurrently, or poll an arbitrary condition (not just a resource state), use the lower-level pkg/async package directly.

pkg/async is a public package — import it alongside pkg/aruba:

import (
"github.com/Arubacloud/sdk-go/pkg/aruba"
"github.com/Arubacloud/sdk-go/pkg/async"
"github.com/Arubacloud/sdk-go/pkg/types"
)

WaitFor — start a background future

async.WaitFor launches a polling goroutine immediately and returns an *async.AsyncClient[T] (a future). You call .Await(ctx) later to block for the result:

// Start polling VPC1 and VPC2 concurrently
futureVPC1 := async.DefaultWaitFor(ctx,
func(ctx context.Context) (*types.Response[types.VPCResponse], error) {
return arubaClient.FromNetwork().VPCs().Get(ctx, vpc1)
},
func(resp *types.Response[types.VPCResponse]) (bool, error) {
if resp == nil || resp.Data == nil {
return false, nil
}
var state types.State
if resp.Data.Properties != nil && resp.Data.Properties.Status != nil &&
resp.Data.Properties.Status.State != nil {
state = *resp.Data.Properties.Status.State
}
return state == types.StateActive, nil
},
)

futureVPC2 := async.DefaultWaitFor(ctx, /* same pattern for vpc2 */)

// Block for both results
resp1, err1 := futureVPC1.Await(ctx)
resp2, err2 := futureVPC2.Await(ctx)

DefaultWaitFor uses the package defaults: DefaultRetries=60, DefaultBaseDelay=10s, DefaultTimeout=600s. Use async.WaitFor(ctx, retries, baseDelay, timeout, call, check) to override.

WaitFor signature

func WaitFor[T any](
ctx context.Context,
retries int,
baseDelay time.Duration,
timeout time.Duration,
call func(ctx context.Context) (*types.Response[T], error),
check func(*types.Response[T]) (bool, error),
) *AsyncClient[T]
  • call — the polling function, called once per iteration.
  • check — returns (true, nil) to signal success, (true, error) to signal terminal failure, (false, nil) to keep polling.
  • If check is nil, any non-nil response.Data is treated as success.

AsyncClient.Await

func (f *AsyncClient[T]) Await(ctx context.Context) (*types.Response[T], error)

Blocks until the background goroutine sends its result or ctx is cancelled. Subsequent calls return the cached result immediately — safe to call multiple times.

pkg/async works directly with the pkg/types wire structs. This is the only layer of the SDK where you'll interact with types.Response[T] and types.*Response types directly.


What does NOT require pkg/types

The following are all available via a single pkg/aruba import — no second import needed:

What you needpkg/aruba surface
State constants (StateActive, StateStopped, …)aruba.StateActive, aruba.StateStopped, …
Region / zone constantsaruba.RegionITBGBergamo, aruba.ZoneITBG1, …
Billing periodaruba.BillingPeriodHour, aruba.BillingPeriodMonth, …
All compute, storage, network, security enumsSee pkg/aruba/aliases.go
Wait for state transitionswrapper.WaitUntilReady(ctx), WaitUntilActive, WaitUntilStates
Serialise a response to JSON / YAMLwrapper.RawJSON(), wrapper.RawYAML()
HTTP envelope introspectionwrapper.StatusCode(), .Headers(), .RawHTTP(), .RawError()
Paginationlist.Total(), .HasNext(), .Next(ctx), .All(ctx, yield)
HTTP error details*aruba.HTTPErrorStatusCode, ErrResp.Title, ErrResp.Detail, ErrResp.TraceID

See Also


// Helper used in examples above
func derefStr(s *string) string {
if s == nil {
return ""
}
return *s
}