Skip to content

Latest commit

 

History

History

README.md

cloudflare/flagship Go SDK

Go Reference license

OpenFeature-compliant provider SDK for Cloudflare Flagship in Go server applications.

The Go SDK supports HTTP mode only. The Cloudflare Workers binding mode is exclusive to the TypeScript SDK.

Installation

go get github.com/cloudflare/flagship/sdks/go

Quick Start

package main

import (
	"context"
	"log"
	"time"

	flagship "github.com/cloudflare/flagship/sdks/go"
	"github.com/open-feature/go-sdk/openfeature"
)

func main() {
	ctx := context.Background()

	provider, err := flagship.NewProvider(flagship.Options{
		AppID:     "your-app-id",
		AccountID: "your-account-id",
		AuthToken: "your-token",
		CacheTTL:  30 * time.Second, // cache evaluations per context for 30s (off by default)
	})
	if err != nil {
		log.Fatal(err)
	}

	if err := openfeature.SetProviderAndWait(provider); err != nil {
		log.Fatal(err)
	}
	defer openfeature.Shutdown()

	client := openfeature.NewDefaultClient()
	evalCtx := openfeature.NewEvaluationContext("user-123", map[string]any{
		"plan": "premium",
	})

	enabled, err := client.BooleanValue(ctx, "dark-mode", false, evalCtx)
	if err != nil {
		log.Fatal(err)
	}

	log.Println("dark-mode:", enabled)
}

Configuration

flagship.NewProvider and flagship.NewClient accept either AppID plus AccountID or a full Endpoint URL.

provider, err := flagship.NewProvider(flagship.Options{
	AppID:     "your-app-id",
	AccountID: "your-account-id",

	// Endpoint: "http://localhost:8787/client/v4/accounts/acct/flagship/apps/app/evaluate",
	// BaseURL:  "http://localhost:8787",

	AuthToken: "your-token",
	Headers: http.Header{
		"X-Custom": []string{"value"},
	},
	HeadersFactory: func(ctx context.Context) (http.Header, error) {
		return http.Header{"Authorization": []string{"Bearer rotated-token"}}, nil
	},

	Timeout:      5 * time.Second,
	Retries:      1,
	RetryDelay:   time.Second,
	CacheTTL:     30 * time.Second,
	CacheMaxSize: 1000,

	Logging: true,
})
Option Description
AppID Flagship app ID. Mutually exclusive with Endpoint.
AccountID Required with AppID.
BaseURL Base URL override used with AppID; defaults to https://api.cloudflare.com.
Endpoint Full absolute evaluation endpoint URL.
AuthToken Adds Authorization: Bearer <token> to each request.
Headers Static headers. Explicit Authorization overrides AuthToken.
HeadersFactory Dynamic per-request headers. Values override Headers and AuthToken.
HTTPClient Custom HTTP client.
Timeout Per-attempt timeout; defaults to 5 seconds.
Retries Retry attempts on transient errors; defaults to 1 and is capped at 10.
DisableRetries Disables retries when set to true.
RetryDelay Delay between retries; defaults to 1 second and is capped at 30 seconds.
CacheTTL Cache TTL; enables response caching when greater than 0.
CacheMaxSize Maximum cached entries; defaults to 1000 when CacheTTL is set.
LocalEvaluation Downloads definitions once and evaluates flags in-process; off by default.
RefreshInterval Background definitions refresh period in local mode; defaults to 30s.
Logging Enables provider debug/error logs; off by default.
Logger Optional slog-compatible logger.
Hooks Provider-level OpenFeature hooks.

Local Evaluation

When LocalEvaluation is enabled, the provider downloads the app's flag definitions once during Init / SetProviderAndWait, evaluates flags in-process with no network call per evaluation, and refreshes definitions in the background on RefreshInterval (default 30s).

provider, err := flagship.NewProvider(flagship.Options{
	AppID:           "your-app-id",
	AccountID:       "your-account-id", // required — used as the rollout hash seed
	AuthToken:       "your-read-token", // needs app **read** permission (not evaluate)
	LocalEvaluation: true,
	RefreshInterval: 30 * time.Second,
})

Requirements and constraints:

  • AccountID is always required in local mode (including when Endpoint is used).
  • The auth token needs app read permission, not evaluate.
  • Endpoint, when set, must end in /evaluate so the definitions URL can be derived.
  • Incompatible with CacheTTL — there is nothing to cache per evaluation.
  • Local evaluations do not appear in server-side analytics.
  • Init fails (and puts the OpenFeature provider in ERROR) if the first definitions fetch fails. Later refresh failures keep the last good snapshot.

See examples/local for a full program.

Response Caching

The provider can cache evaluations to avoid a network round-trip for repeated flag/context pairs. Caching is off by default and enabled by setting CacheTTL:

provider, err := flagship.NewProvider(flagship.Options{
	AppID:        "your-app-id",
	AccountID:    "your-account-id",
	AuthToken:    "your-token",
	CacheTTL:     30 * time.Second, // values may be up to this stale
	CacheMaxSize: 1000,             // LRU-evicted beyond this many entries
})

Each entry is keyed by flag key, flag type, and the full evaluation context, so distinct contexts never share a value. Cache hits resolve with reason == openfeature.CachedReason. Disabled flags, errors, and type mismatches are never cached. Because freshness is TTL-based, a flag change in Flagship takes effect after the entry expires.

The cache is per-provider instance, guarded by a mutex for concurrent use, and cleared on Shutdown.

Evaluation Context

Primitive-only context is sent as URL query parameters. Context containing maps, slices, arrays, or nil uses a JSON POST request. Nested values are preserved recursively, and time.Time values are converted to RFC3339Nano strings. Unsupported map keys, structs, cyclic values, and non-finite numbers return INVALID_CONTEXT before transport.

Flag Types

All OpenFeature server-side flag types are supported:

enabled, _ := client.BooleanValue(ctx, "new-checkout", false, evalCtx)
variant, _ := client.StringValue(ctx, "homepage-hero", "control", evalCtx)
rate, _ := client.FloatValue(ctx, "sample-rate", 0.1, evalCtx)
limit, _ := client.IntValue(ctx, "upload-limit", 10, evalCtx)
config, _ := client.ObjectValue(ctx, "ui-config", map[string]any{"theme": "light"}, evalCtx)

Use *ValueDetails methods when you need reason, variant, metadata, or error codes.

Error Handling

Provider resolution methods return the default value plus an OpenFeature resolution error when evaluation fails. The lower-level FlagshipClient returns *flagship.Error with a typed Code, HTTP StatusCode, and wrapped cause when available.

Error code Cause
FLAG_NOT_FOUND Flag key does not exist (HTTP 404 or missing from local definitions)
BAD_REQUEST Evaluation request was invalid (HTTP 400)
INVALID_CONTEXT Evaluation context contains unsupported value types
NETWORK_ERROR Network request failed
TIMEOUT_ERROR Request timed out
PARSE_ERROR API response or local flag definition was invalid
PROVIDER_NOT_READY Local evaluation used before init or after shutdown
GENERAL Any other transient or unexpected failure

400 and 404 responses are never retried. Other failures are retried up to Retries times unless DisableRetries is set.

Development

gofmt -w .
go vet ./...
go test ./...

License

Apache-2.0