eddy

package module
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Oct 10, 2025 License: Apache-2.0 Imports: 4 Imported by: 1

README

Go Report Card Build status Go Reference Quality Gate Status

eddy

eddy is a type-safe, rule-driven client resolution package for Go. It lets you:

  • describe how to obtain service-specific clients via composable rules
  • provide builders that know how to turn rule output into clients
  • reuse cached client instances with per-tenant TTLs

Everything is expressed using Go generics so you keep static typing across the flow (builder → resolver → client service). Optional values travel through the system as mo.Option, which keeps error handling explicit without resorting to sentinel values.

The module targets Go 1.25, but any recent Go release with generics support works fine.

Installation

go get github.com/theopenlane/eddy

If you want the batteries-included helper rules (for context hints, fallback chains, etc.), also pull in:

go get github.com/theopenlane/eddy/helpers

Core Building Blocks

  • Resolver: evaluates rules in order and returns the first match as an eddy.Result.
  • Rule / RuleFunc: your decision logic. Rules can return mo.None to signal “not me”.
  • Result: bundles the Builder, output data (credentials), configuration, and a suggested CacheKey.
  • Builder: turns a rule result into a concrete client. BuilderFunc adapts plain functions.
  • ClientPool: a TTL-protected cache keyed by anything that satisfies the CacheKey interface.
  • ClientService: reads from the pool, falls back to the supplied builder, and optionally clones rule data for safety.

Because everything is generic, you pick the client, output, and config types that make sense for your service surface.

Caching Clients

Create a pool with the TTL that makes sense for your provider:

pool := eddy.NewClientPool[*MyClient](30 * time.Minute)

ClientService wraps this pool and handles cache-aside logic. Defensive copies help when your resolver returns mutable state:

service := eddy.NewClientService[*MyClient, Credentials, Config](
	pool,
	eddy.WithOutputClone[*MyClient](func(in Credentials) Credentials {
		return in.Clone()
	}),
	eddy.WithConfigClone[*MyClient](func(cfg Config) Config {
		return cfg.Clone()
	}),
)

GetClient returns an mo.Option[*MyClient]; if the builder returns an error you will see mo.None and the pool is left untouched. Periodically call pool.CleanExpired() if you want to eagerly discard stale entries instead of waiting for them to be overwritten.

Building Rules Without Helpers

The fluent RuleBuilder covers many common patterns without leaving the core API:

rule := eddy.NewRule[*MyClient, Credentials, Config]().
	WhenFunc(func(ctx context.Context) bool {
		return isTrustedTenant(ctx)
	}).
	WhenFunc(func(ctx context.Context) bool {
		return hasFeatureFlag(ctx, "beta-provider")
	}).
	Resolve(func(ctx context.Context) (*eddy.ResolvedProvider[*MyClient, Credentials, Config], error) {
		creds, cfg, err := loadProviderData(ctx)
		if err != nil {
			return nil, err
		}
		return &eddy.ResolvedProvider[*MyClient, Credentials, Config]{
			Builder: myBuilder,
			Output:  creds,
			Config:  cfg,
		}, nil
	})

resolver.AddRule(rule)

RuleBuilder short-circuits on the first failing condition, so expensive lookups only happen when everything else lines up.

Helper Rules

The helpers package demonstrates how to consume the core types and covers common scenarios:

  • MatchHintRule: match an exact typed string hint in the context.
  • MatchHintAnyRule: match any of a set of hints.
  • FallbackChainRule: try resolvers in sequence until one succeeds.
  • ConditionalRule: wrap an arbitrary predicate plus resolver.

Every helper simply returns an eddy.Result, so mixing and matching with your own rules is seamless. The helpers rely on contextx for typed context values, but you can adapt them to any scheme you prefer.

Working With Options

Most API surfaces return mo.Option[T]. The important helpers are:

  • IsPresent() bool / IsAbsent() bool
  • MustGet() – panics if absent, great for tests
  • OrElse(defaultValue) – supply a fallback

Use whatever style fits your codebase best; the package stays unopinionated about error handling.

Housekeeping Tips

  • Call ClientPool.RemoveClient when you know a client should be evicted before TTL.
  • Use CleanExpired() from a background job if you expect many idle entries.
  • Keep your CacheKey deterministic and stringified; the library only needs something that implements String() string.
  • Register builders per provider type and share them across rules; they are meant to be stateless factories.

Advanced Example

The snippet below wires the major pieces together: a rule that resolves an S3-like client when a context hint is present, and a ClientService that caches instances per tenant.

package main

import (
	"context"
	"fmt"
	"time"

	"github.com/theopenlane/eddy"
	"github.com/theopenlane/eddy/helpers"
	"github.com/theopenlane/utils/contextx"
)

type ProviderHint string

type storageClient struct {
	endpoint string
	bucket   string
}

type storageCreds struct {
	AccessKey string
	SecretKey string
	Region    string
}

type storageConfig struct {
	Bucket   string
	Endpoint string
	Timeout  time.Duration
}

type tenantCacheKey struct {
	TenantID string
	Type     string
}

func (k tenantCacheKey) String() string {
	return fmt.Sprintf("%s:%s", k.TenantID, k.Type)
}

func main() {
	ctx := contextx.WithString(context.Background(), ProviderHint("s3"))

	s3Builder := &eddy.BuilderFunc[*storageClient, storageCreds, storageConfig]{
		Type: "s3",
		Func: func(ctx context.Context, out storageCreds, cfg storageConfig) (*storageClient, error) {
			return &storageClient{endpoint: cfg.Endpoint, bucket: cfg.Bucket}, nil
		},
	}

	resolver := eddy.NewResolver[*storageClient, storageCreds, storageConfig]()
	resolver.AddRule(&helpers.MatchHintRule[*storageClient, storageCreds, storageConfig, ProviderHint]{
		Value: "s3",
		Resolver: func(context.Context) (*eddy.ResolvedProvider[*storageClient, storageCreds, storageConfig], error) {
			return &eddy.ResolvedProvider[*storageClient, storageCreds, storageConfig]{
				Builder: s3Builder,
				Output: storageCreds{
					AccessKey: "access",
					SecretKey: "secret",
					Region:    "us-east-1",
				},
				Config: storageConfig{
					Bucket:   "my-bucket",
					Endpoint: "s3.amazonaws.com",
					Timeout:  30 * time.Second,
				},
			}, nil
		},
	})

	result := resolver.Resolve(ctx)
	if !result.IsPresent() {
		panic("no provider matched")
	}

	pool := eddy.NewClientPool[*storageClient](time.Hour)
	service := eddy.NewClientService[*storageClient, storageCreds, storageConfig](pool)

	res := result.MustGet()
	cacheKey := tenantCacheKey{
		TenantID: "tenant-123",
		Type:     res.Builder.ProviderType(),
	}

	client := service.GetClient(ctx, cacheKey, res.Builder, res.Output, res.Config)
	if !client.IsPresent() {
		panic("builder failed")
	}

	fmt.Printf("reusing client for bucket %s\n", client.MustGet().bucket)
}

License

Distributed under the Apache 2.0 License.

Documentation

Overview

Package eddy is a type-safe, rule-driven client resolution package

Example (BuilderInRules)

Example: Rules provide builders directly

package main

import (
	"context"
	"fmt"
	"time"

	"github.com/theopenlane/eddy"
	"github.com/theopenlane/eddy/helpers"
	"github.com/theopenlane/utils/contextx"
)

// Mock types for examples
type StorageClient interface {
	Upload(ctx context.Context, data []byte) error
	Download(ctx context.Context, key string) ([]byte, error)
}

type S3Client struct {
	endpoint string
	bucket   string
}

func (c *S3Client) Upload(ctx context.Context, data []byte) error {
	return nil
}

func (c *S3Client) Download(ctx context.Context, key string) ([]byte, error) {
	return nil, nil
}

type StorageCredentials struct {
	AccessKey string
	SecretKey string
	Region    string
}

type StorageConfig struct {
	Bucket   string
	Endpoint string
	Timeout  time.Duration
}

type StorageCacheKey struct {
	TenantID   string
	ProviderID string
}

func (k StorageCacheKey) String() string {
	return fmt.Sprintf("%s:%s", k.TenantID, k.ProviderID)
}

// S3Builder implements Builder interface
type S3Builder struct{}

func (b *S3Builder) Build(ctx context.Context, creds StorageCredentials, config StorageConfig) (StorageClient, error) {
	return &S3Client{
		endpoint: config.Endpoint,
		bucket:   config.Bucket,
	}, nil
}

func (b *S3Builder) ProviderType() string {
	return "s3"
}

// Context hint types
type ProviderHint string

func main() {
	// Create a resolver with rules that provide builders
	resolver := eddy.NewResolver[StorageClient, StorageCredentials, StorageConfig]()

	// Define builders once (these are stateless factories, reused across rules)
	s3Builder := &S3Builder{}

	// Rule 1: S3 provider
	resolver.AddRule(&helpers.MatchHintRule[StorageClient, StorageCredentials, StorageConfig, ProviderHint]{
		Value: "s3",
		Resolver: func(ctx context.Context) (*eddy.ResolvedProvider[StorageClient, StorageCredentials, StorageConfig], error) {
			// Resolve credentials from environment, vault, etc.
			creds := StorageCredentials{
				AccessKey: "access-key",
				SecretKey: "secret-key",
				Region:    "us-east-1",
			}

			config := StorageConfig{
				Bucket:   "my-bucket",
				Endpoint: "s3.amazonaws.com",
				Timeout:  30 * time.Second,
			}

			return &eddy.ResolvedProvider[StorageClient, StorageCredentials, StorageConfig]{
				Builder: s3Builder, // Rule provides the builder
				Output:  creds,
				Config:  config,
			}, nil
		},
	})

	// Setup context with hint
	ctx := context.Background()
	ctx = contextx.WithString(ctx, ProviderHint("s3"))

	// Resolve returns Result with Builder included
	result := resolver.Resolve(ctx)
	if !result.IsPresent() {
		fmt.Println("no provider resolved")
		return
	}

	res := result.MustGet()

	// Create client service and pool
	pool := eddy.NewClientPool[StorageClient](1 * time.Hour)
	service := eddy.NewClientService[StorageClient, StorageCredentials, StorageConfig](pool)

	// Build cache key
	cacheKey := StorageCacheKey{
		TenantID:   "tenant-1",
		ProviderID: res.Builder.ProviderType(), // Get type from builder
	}

	// GetClient now takes the builder directly from the result
	client := service.GetClient(ctx, cacheKey, res.Builder, res.Output, res.Config)
	if !client.IsPresent() {
		fmt.Println("failed to get client")
		return
	}

	fmt.Println("client created successfully")
}
Output:
client created successfully
Example (FallbackChain)

Example: Fallback chain with different builders

package main

import (
	"context"
	"fmt"
	"time"

	"github.com/theopenlane/eddy"
	"github.com/theopenlane/eddy/helpers"
)

// Mock types for examples
type StorageClient interface {
	Upload(ctx context.Context, data []byte) error
	Download(ctx context.Context, key string) ([]byte, error)
}

type S3Client struct {
	endpoint string
	bucket   string
}

func (c *S3Client) Upload(ctx context.Context, data []byte) error {
	return nil
}

func (c *S3Client) Download(ctx context.Context, key string) ([]byte, error) {
	return nil, nil
}

type StorageCredentials struct {
	AccessKey string
	SecretKey string
	Region    string
}

type StorageConfig struct {
	Bucket   string
	Endpoint string
	Timeout  time.Duration
}

// S3Builder implements Builder interface
type S3Builder struct{}

func (b *S3Builder) Build(ctx context.Context, creds StorageCredentials, config StorageConfig) (StorageClient, error) {
	return &S3Client{
		endpoint: config.Endpoint,
		bucket:   config.Bucket,
	}, nil
}

func (b *S3Builder) ProviderType() string {
	return "s3"
}

func main() {
	s3Builder := &S3Builder{}
	// Could have other builders like &R2Builder{}, &GCSBuilder{}, etc.

	resolver := eddy.NewResolver[StorageClient, StorageCredentials, StorageConfig]()

	// Try multiple credential sources in order
	resolver.AddRule(&helpers.FallbackChainRule[StorageClient, StorageCredentials, StorageConfig]{
		Resolvers: []func(context.Context) (*eddy.ResolvedProvider[StorageClient, StorageCredentials, StorageConfig], error){
			// Try database credentials first
			func(ctx context.Context) (*eddy.ResolvedProvider[StorageClient, StorageCredentials, StorageConfig], error) {
				// Would fetch from database...
				return nil, fmt.Errorf("no database credentials")
			},
			// Fall back to environment variables
			func(ctx context.Context) (*eddy.ResolvedProvider[StorageClient, StorageCredentials, StorageConfig], error) {
				return &eddy.ResolvedProvider[StorageClient, StorageCredentials, StorageConfig]{
					Builder: s3Builder,
					Output: StorageCredentials{
						AccessKey: "env-key",
						SecretKey: "env-secret",
						Region:    "us-east-1",
					},
					Config: StorageConfig{
						Bucket:  "env-bucket",
						Timeout: 30 * time.Second,
					},
				}, nil
			},
		},
	})

	ctx := context.Background()
	result := resolver.Resolve(ctx)

	if result.IsPresent() {
		res := result.MustGet()
		fmt.Printf("resolved with builder: %s\n", res.Builder.ProviderType())
	}
}
Output:
resolved with builder: s3
Example (Multitenancy)

Example: Multitenancy with builder-in-rules

package main

import (
	"context"
	"fmt"
	"time"

	"github.com/theopenlane/eddy"
	"github.com/theopenlane/eddy/helpers"
	"github.com/theopenlane/utils/contextx"
)

// Mock types for examples
type StorageClient interface {
	Upload(ctx context.Context, data []byte) error
	Download(ctx context.Context, key string) ([]byte, error)
}

type S3Client struct {
	endpoint string
	bucket   string
}

func (c *S3Client) Upload(ctx context.Context, data []byte) error {
	return nil
}

func (c *S3Client) Download(ctx context.Context, key string) ([]byte, error) {
	return nil, nil
}

type StorageCredentials struct {
	AccessKey string
	SecretKey string
	Region    string
}

type StorageConfig struct {
	Bucket   string
	Endpoint string
	Timeout  time.Duration
}

type StorageCacheKey struct {
	TenantID   string
	ProviderID string
}

func (k StorageCacheKey) String() string {
	return fmt.Sprintf("%s:%s", k.TenantID, k.ProviderID)
}

// S3Builder implements Builder interface
type S3Builder struct{}

func (b *S3Builder) Build(ctx context.Context, creds StorageCredentials, config StorageConfig) (StorageClient, error) {
	return &S3Client{
		endpoint: config.Endpoint,
		bucket:   config.Bucket,
	}, nil
}

func (b *S3Builder) ProviderType() string {
	return "s3"
}

type TenantHint string

func main() {
	s3Builder := &S3Builder{}

	resolver := eddy.NewResolver[StorageClient, StorageCredentials, StorageConfig]()

	// Each tenant can have different credentials, same builder
	resolver.AddRule(&helpers.MatchHintRule[StorageClient, StorageCredentials, StorageConfig, TenantHint]{
		Value: "tenant-A",
		Resolver: func(ctx context.Context) (*eddy.ResolvedProvider[StorageClient, StorageCredentials, StorageConfig], error) {
			return &eddy.ResolvedProvider[StorageClient, StorageCredentials, StorageConfig]{
				Builder: s3Builder,
				Output: StorageCredentials{
					AccessKey: "tenant-a-key",
					SecretKey: "tenant-a-secret",
					Region:    "us-east-1",
				},
				Config: StorageConfig{
					Bucket:  "tenant-a-bucket",
					Timeout: 30 * time.Second,
				},
			}, nil
		},
	})

	resolver.AddRule(&helpers.MatchHintRule[StorageClient, StorageCredentials, StorageConfig, TenantHint]{
		Value: "tenant-B",
		Resolver: func(ctx context.Context) (*eddy.ResolvedProvider[StorageClient, StorageCredentials, StorageConfig], error) {
			return &eddy.ResolvedProvider[StorageClient, StorageCredentials, StorageConfig]{
				Builder: s3Builder, // Same builder
				Output: StorageCredentials{
					AccessKey: "tenant-b-key",
					SecretKey: "tenant-b-secret",
					Region:    "us-west-2",
				},
				Config: StorageConfig{
					Bucket:  "tenant-b-bucket",
					Timeout: 30 * time.Second,
				},
			}, nil
		},
	})

	pool := eddy.NewClientPool[StorageClient](1 * time.Hour)
	service := eddy.NewClientService[StorageClient, StorageCredentials, StorageConfig](pool)

	// Tenant A request
	ctxA := context.Background()
	ctxA = contextx.WithString(ctxA, TenantHint("tenant-A"))

	resultA := resolver.Resolve(ctxA)
	if resultA.IsPresent() {
		resA := resultA.MustGet()
		cacheKeyA := StorageCacheKey{
			TenantID:   "tenant-A",
			ProviderID: resA.Builder.ProviderType(),
		}
		clientA := service.GetClient(ctxA, cacheKeyA, resA.Builder, resA.Output, resA.Config)
		if clientA.IsPresent() {
			fmt.Println("tenant-A client created")
		}
	}

	// Tenant B request
	ctxB := context.Background()
	ctxB = contextx.WithString(ctxB, TenantHint("tenant-B"))

	resultB := resolver.Resolve(ctxB)
	if resultB.IsPresent() {
		resB := resultB.MustGet()
		cacheKeyB := StorageCacheKey{
			TenantID:   "tenant-B",
			ProviderID: resB.Builder.ProviderType(),
		}
		clientB := service.GetClient(ctxB, cacheKeyB, resB.Builder, resB.Output, resB.Config)
		if clientB.IsPresent() {
			fmt.Println("tenant-B client created")
		}
	}

}
Output:
tenant-A client created
tenant-B client created
Example (SharedBuilder)

Example: Multiple rules can share the same builder

package main

import (
	"context"
	"fmt"
	"time"

	"github.com/theopenlane/eddy"
	"github.com/theopenlane/eddy/helpers"
	"github.com/theopenlane/utils/contextx"
)

// Mock types for examples
type StorageClient interface {
	Upload(ctx context.Context, data []byte) error
	Download(ctx context.Context, key string) ([]byte, error)
}

type S3Client struct {
	endpoint string
	bucket   string
}

func (c *S3Client) Upload(ctx context.Context, data []byte) error {
	return nil
}

func (c *S3Client) Download(ctx context.Context, key string) ([]byte, error) {
	return nil, nil
}

type StorageCredentials struct {
	AccessKey string
	SecretKey string
	Region    string
}

type StorageConfig struct {
	Bucket   string
	Endpoint string
	Timeout  time.Duration
}

// S3Builder implements Builder interface
type S3Builder struct{}

func (b *S3Builder) Build(ctx context.Context, creds StorageCredentials, config StorageConfig) (StorageClient, error) {
	return &S3Client{
		endpoint: config.Endpoint,
		bucket:   config.Bucket,
	}, nil
}

func (b *S3Builder) ProviderType() string {
	return "s3"
}

type EnvironmentHint string

func main() {
	resolver := eddy.NewResolver[StorageClient, StorageCredentials, StorageConfig]()

	// Single builder instance shared by multiple rules
	s3Builder := &S3Builder{}

	// Production S3 rule
	resolver.AddRule(&helpers.ConditionalRule[StorageClient, StorageCredentials, StorageConfig]{
		Predicate: func(ctx context.Context) bool {
			env, ok := contextx.StringFrom[EnvironmentHint](ctx)
			return ok && env == "production"
		},
		Resolver: func(ctx context.Context) (*eddy.ResolvedProvider[StorageClient, StorageCredentials, StorageConfig], error) {
			return &eddy.ResolvedProvider[StorageClient, StorageCredentials, StorageConfig]{
				Builder: s3Builder, // Same builder
				Output: StorageCredentials{
					AccessKey: "prod-key",
					SecretKey: "prod-secret",
					Region:    "us-east-1",
				},
				Config: StorageConfig{
					Bucket:  "production-bucket",
					Timeout: 30 * time.Second,
				},
			}, nil
		},
	})

	// Development S3 rule
	resolver.AddRule(&helpers.ConditionalRule[StorageClient, StorageCredentials, StorageConfig]{
		Predicate: func(ctx context.Context) bool {
			env, ok := contextx.StringFrom[EnvironmentHint](ctx)
			return ok && env == "development"
		},
		Resolver: func(ctx context.Context) (*eddy.ResolvedProvider[StorageClient, StorageCredentials, StorageConfig], error) {
			return &eddy.ResolvedProvider[StorageClient, StorageCredentials, StorageConfig]{
				Builder: s3Builder, // Same builder, different creds
				Output: StorageCredentials{
					AccessKey: "dev-key",
					SecretKey: "dev-secret",
					Region:    "us-west-2",
				},
				Config: StorageConfig{
					Bucket:  "development-bucket",
					Timeout: 10 * time.Second,
				},
			}, nil
		},
	})

	ctx := context.Background()
	ctx = contextx.WithString(ctx, EnvironmentHint("production"))

	result := resolver.Resolve(ctx)
	if result.IsPresent() {
		res := result.MustGet()
		fmt.Printf("resolved provider type: %s\n", res.Builder.ProviderType())
	}
}
Output:
resolved provider type: s3

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Builder

type Builder[T any, Output any, Config any] interface {
	// Build constructs a client instance using the provided output and config
	Build(ctx context.Context, output Output, config Config) (T, error)
	// ProviderType returns the provider type identifier for cache key construction
	ProviderType() string
}

Builder builds client instances with output and configuration

type BuilderFunc

type BuilderFunc[T any, Output any, Config any] struct {
	// Type is the provider type identifier
	Type string
	// Func is the function that builds the client
	Func func(context.Context, Output, Config) (T, error)
}

BuilderFunc is a function adapter for Builder interface Use this when you want to create a Builder from a function without defining a new type

Example:

builder := &BuilderFunc[*s3.Client, S3Credentials, S3Config]{
    Type: "s3",
    Func: func(ctx context.Context, output S3Credentials, config S3Config) (*s3.Client, error) {
        return buildS3Client(ctx, output, config)
    },
}

func (*BuilderFunc[T, Output, Config]) Build

func (b *BuilderFunc[T, Output, Config]) Build(ctx context.Context, output Output, config Config) (T, error)

Build implements Builder.Build

func (*BuilderFunc[T, Output, Config]) ProviderType

func (b *BuilderFunc[T, Output, Config]) ProviderType() string

ProviderType implements Builder.ProviderType

type CacheKey

type CacheKey interface {
	String() string
}

CacheKey is the interface that cache keys must implement Implementations must be comparable to work as map keys

type ClientEntry

type ClientEntry[T any] struct {
	// Client is the cached client instance
	Client T
	// Expiration is when this cache entry expires
	Expiration time.Time
}

ClientEntry wraps a client instance with expiration metadata

type ClientPool

type ClientPool[T any] struct {
	// contains filtered or unexported fields
}

ClientPool holds cached client instances with TTL expiration

func NewClientPool

func NewClientPool[T any](ttl time.Duration) *ClientPool[T]

NewClientPool creates a new client pool with the specified TTL

func (*ClientPool[T]) CleanExpired

func (p *ClientPool[T]) CleanExpired() int

CleanExpired removes expired clients from the pool and returns the count of removed clients

func (*ClientPool[T]) GetClient

func (p *ClientPool[T]) GetClient(key CacheKey) mo.Option[T]

GetClient retrieves a client from the pool if it exists and hasn't expired

func (*ClientPool[T]) RemoveClient

func (p *ClientPool[T]) RemoveClient(key CacheKey)

RemoveClient removes a client from the pool

func (*ClientPool[T]) SetClient

func (p *ClientPool[T]) SetClient(key CacheKey, client T)

SetClient stores a client in the pool with TTL expiration

type ClientService

type ClientService[T any, Output any, Config any] struct {
	// contains filtered or unexported fields
}

ClientService manages client pooling and provides cached client instances The builder is provided directly by the rule evaluation result rather than via a registry

func NewClientService

func NewClientService[T any, Output any, Config any](pool *ClientPool[T], opts ...ServiceOption[T, Output, Config]) *ClientService[T, Output, Config]

NewClientService creates a new client service with the specified pool

func (*ClientService[T, Output, Config]) GetClient

func (s *ClientService[T, Output, Config]) GetClient(ctx context.Context, key CacheKey, builder Builder[T, Output, Config], output Output, config Config) mo.Option[T]

GetClient retrieves a client from cache or builds a new one using the provided builder The builder is provided directly from the rule evaluation result

func (*ClientService[T, Output, Config]) Pool

func (s *ClientService[T, Output, Config]) Pool() *ClientPool[T]

Pool returns the underlying client pool

type ResolvedProvider

type ResolvedProvider[T any, Output any, Config any] struct {
	// Builder is the client builder to use
	Builder Builder[T, Output, Config]
	// Output contains the credentials or output data needed to build the client
	Output Output
	// Config contains the configuration for the client
	Config Config
}

ResolvedProvider represents a resolved provider configuration This is returned by resolver functions

type Resolver

type Resolver[T any, Output any, Config any] struct {
	// contains filtered or unexported fields
}

Resolver is a generic struct that handles rule-based resolution

func NewResolver

func NewResolver[T any, Output any, Config any]() *Resolver[T, Output, Config]

NewResolver creates a new resolver instance

func (*Resolver[T, Output, Config]) AddRule

func (r *Resolver[T, Output, Config]) AddRule(rule Rule[T, Output, Config]) *Resolver[T, Output, Config]

AddRule adds a resolution rule to the resolver

func (*Resolver[T, Output, Config]) Resolve

func (r *Resolver[T, Output, Config]) Resolve(ctx context.Context) mo.Option[Result[T, Output, Config]]

Resolve evaluates rules in order and returns the first matching result

type Result

type Result[T any, Output any, Config any] struct {
	// Builder is the client builder to use
	Builder Builder[T, Output, Config]
	// Output contains the credentials or output data needed to build the client
	Output Output
	// Config contains the configuration for the client
	Config Config
	// CacheKey is the key used to cache the built client
	CacheKey CacheKey
}

Result represents the output of rule evaluation

type Rule

type Rule[T any, Output any, Config any] interface {
	Evaluate(ctx context.Context) mo.Option[Result[T, Output, Config]]
}

Rule is a generic interface that evaluates context and returns a result

type RuleBuilder

type RuleBuilder[T any, Output any, Config any] struct {
	// contains filtered or unexported fields
}

RuleBuilder provides a fluent interface for creating resolution rules

func NewRule

func NewRule[T any, Output any, Config any]() *RuleBuilder[T, Output, Config]

NewRule creates a rule builder for static resolution

func (*RuleBuilder[T, Output, Config]) Resolve

func (b *RuleBuilder[T, Output, Config]) Resolve(resolver func(context.Context) (*ResolvedProvider[T, Output, Config], error)) Rule[T, Output, Config]

Resolve creates a rule that uses a function to resolve the provider

func (*RuleBuilder[T, Output, Config]) WhenFunc

func (b *RuleBuilder[T, Output, Config]) WhenFunc(condition func(context.Context) bool) *RuleBuilder[T, Output, Config]

WhenFunc adds a custom condition function

type RuleFunc

type RuleFunc[T any, Output any, Config any] struct {
	// EvaluateFunc is the function that evaluates the rule
	EvaluateFunc func(ctx context.Context) mo.Option[Result[T, Output, Config]]
}

RuleFunc is a function adapter for Rule interface

func (*RuleFunc[T, Output, Config]) Evaluate

func (r *RuleFunc[T, Output, Config]) Evaluate(ctx context.Context) mo.Option[Result[T, Output, Config]]

Evaluate implements Rule.Evaluate

type ServiceOption

type ServiceOption[T any, Output any, Config any] func(*ClientService[T, Output, Config])

ServiceOption configures a ClientService

func WithConfigClone

func WithConfigClone[T any, Output any, Config any](cloneFn func(Config) Config) ServiceOption[T, Output, Config]

WithConfigClone sets the config cloning function for defensive copying

func WithOutputClone

func WithOutputClone[T any, Output any, Config any](cloneFn func(Output) Output) ServiceOption[T, Output, Config]

WithOutputClone sets the output cloning function for defensive copying

Directories

Path Synopsis
Package helpers provides convenience helpers for common rule patterns
Package helpers provides convenience helpers for common rule patterns

Jump to

Keyboard shortcuts

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