Add scoped identity listings and backend inventory
This commit is contained in:
@@ -17,6 +17,17 @@ func (state *loaded) ListProfiles(ctx context.Context) ([]string, error) {
|
||||
return profiles, nil
|
||||
}
|
||||
|
||||
func (state *loaded) ListBackends(ctx context.Context) ([]string, error) {
|
||||
if err := ctx.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
backends := append([]string(nil), state.backendIDs...)
|
||||
if err := ctx.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return backends, nil
|
||||
}
|
||||
|
||||
func (state *loaded) ListPrompts(ctx context.Context) ([]domain.PromptIdentity, error) {
|
||||
if err := ctx.Err(); err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -25,6 +25,14 @@ func TestLoadedListsSortedEffectiveIdentities(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
backends, err := state.ListBackends(context.Background())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
wantBackends := []string{"local"}
|
||||
if !reflect.DeepEqual(backends, wantBackends) {
|
||||
t.Fatalf("backend IDs = %#v, want %#v", backends, wantBackends)
|
||||
}
|
||||
|
||||
profiles, err := state.ListProfiles(context.Background())
|
||||
if err != nil {
|
||||
@@ -51,6 +59,7 @@ func TestLoadedListsSortedEffectiveIdentities(t *testing.T) {
|
||||
|
||||
func TestLoadedListsAreIndependent(t *testing.T) {
|
||||
state, err := Load(mapping("identities", map[string]any{
|
||||
"backends": map[string]any{"backend": map[string]any{"endpoint": "https://example.test/v1"}},
|
||||
"profiles": map[string]any{"profile": map[string]any{}},
|
||||
"prompts": map[string]any{"example": listingPromptDefinition()},
|
||||
}), nil)
|
||||
@@ -64,6 +73,11 @@ func TestLoadedListsAreIndependent(t *testing.T) {
|
||||
}
|
||||
profiles = append(profiles, "caller profile")
|
||||
profiles[0] = "changed profile"
|
||||
backends, err := state.ListBackends(context.Background())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
backends[0] = "changed backend"
|
||||
|
||||
prompts, err := state.ListPrompts(context.Background())
|
||||
if err != nil {
|
||||
@@ -79,6 +93,10 @@ func TestLoadedListsAreIndependent(t *testing.T) {
|
||||
if !reflect.DeepEqual(profiles, []string{"profile"}) {
|
||||
t.Fatalf("caller mutation changed profile inventory: %#v", profiles)
|
||||
}
|
||||
backends, err = state.ListBackends(context.Background())
|
||||
if err != nil || !reflect.DeepEqual(backends, []string{"backend"}) {
|
||||
t.Fatalf("caller mutation changed backend inventory: %#v, %v", backends, err)
|
||||
}
|
||||
prompts, err = state.ListPrompts(context.Background())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -98,6 +116,10 @@ func TestLoadedListsEmptyInventory(t *testing.T) {
|
||||
if err != nil || len(profiles) != 0 {
|
||||
t.Fatalf("empty profile listing = %#v, %v", profiles, err)
|
||||
}
|
||||
backends, err := state.ListBackends(context.Background())
|
||||
if err != nil || len(backends) != 0 {
|
||||
t.Fatalf("empty backend listing = %#v, %v", backends, err)
|
||||
}
|
||||
prompts, err := state.ListPrompts(context.Background())
|
||||
if err != nil || len(prompts) != 0 {
|
||||
t.Fatalf("empty prompt listing = %#v, %v", prompts, err)
|
||||
@@ -119,6 +141,10 @@ func TestLoadedListsHonorCanceledContext(t *testing.T) {
|
||||
if !errors.Is(err, context.Canceled) || len(profiles) != 0 {
|
||||
t.Fatalf("canceled profile listing = %#v, %v", profiles, err)
|
||||
}
|
||||
backends, err := state.ListBackends(ctx)
|
||||
if !errors.Is(err, context.Canceled) || len(backends) != 0 {
|
||||
t.Fatalf("canceled backend listing = %#v, %v", backends, err)
|
||||
}
|
||||
prompts, err := state.ListPrompts(ctx)
|
||||
if !errors.Is(err, context.Canceled) || len(prompts) != 0 {
|
||||
t.Fatalf("canceled prompt listing = %#v, %v", prompts, err)
|
||||
|
||||
@@ -124,6 +124,7 @@ type loaded struct {
|
||||
prompts map[string]*promptEntry
|
||||
registry *backend.Registry
|
||||
indexedProfiles map[string]domain.ExecutionProfile
|
||||
backendIDs []string
|
||||
profileIDs []string
|
||||
promptIDs []domain.PromptIdentity
|
||||
}
|
||||
@@ -260,6 +261,7 @@ func load(main configkit.Input, underlays []configkit.Underlay) (*loaded, error)
|
||||
if err := state.buildExecutionProfiles(); err != nil {
|
||||
return nil, fmt.Errorf("load configuration: %w", err)
|
||||
}
|
||||
state.backendIDs = sortedKeys(indexedBackends(&result.Config))
|
||||
state.profileIDs = sortedKeys(state.indexedProfiles)
|
||||
for _, id := range sortedKeys(state.prompts) {
|
||||
state.promptIDs = append(state.promptIDs, domain.PromptIdentity{ID: id, Version: stringPointerValue(state.prompts[id].definition.Version)})
|
||||
|
||||
@@ -94,10 +94,27 @@ type resourceNamespaces struct {
|
||||
}
|
||||
|
||||
type identityInventory interface {
|
||||
ListBackends(context.Context) ([]string, error)
|
||||
ListProfiles(context.Context) ([]string, error)
|
||||
ListPrompts(context.Context) ([]domain.PromptIdentity, error)
|
||||
}
|
||||
|
||||
// ListingFilterMode selects namespace membership for one identity listing.
|
||||
type ListingFilterMode uint8
|
||||
|
||||
const (
|
||||
ListingDefault ListingFilterMode = iota
|
||||
ListingNamed
|
||||
ListingUnqualified
|
||||
ListingAll
|
||||
)
|
||||
|
||||
// ListingFilter optionally overrides the namespace selected by an engine view.
|
||||
type ListingFilter struct {
|
||||
Mode ListingFilterMode
|
||||
Name string
|
||||
}
|
||||
|
||||
// New assembles an engine from facade-selected configuration and collaborators.
|
||||
func New(cfg Config, options Options) (*Engine, error) {
|
||||
defaultRetries, err := domain.ResolveTransientRetries(2, cfg.TransientRetries)
|
||||
@@ -231,12 +248,99 @@ func (e *Engine) InspectProfile(ctx context.Context, id string) (*domain.Profile
|
||||
return e.runner.InspectProfile(ctx, id)
|
||||
}
|
||||
|
||||
func (e *Engine) ListProfiles(ctx context.Context) ([]string, error) {
|
||||
return e.listing.ListProfiles(ctx)
|
||||
func (e *Engine) ListBackends(ctx context.Context, filters ...ListingFilter) ([]string, error) {
|
||||
return filterIdentityList(ctx, filters, e.namespaces.backend, e.listing.ListBackends)
|
||||
}
|
||||
|
||||
func (e *Engine) ListPrompts(ctx context.Context) ([]domain.PromptIdentity, error) {
|
||||
return e.listing.ListPrompts(ctx)
|
||||
func (e *Engine) ListProfiles(ctx context.Context, filters ...ListingFilter) ([]string, error) {
|
||||
return filterIdentityList(ctx, filters, e.namespaces.profile, e.listing.ListProfiles)
|
||||
}
|
||||
|
||||
func (e *Engine) ListPrompts(ctx context.Context, filters ...ListingFilter) ([]domain.PromptIdentity, error) {
|
||||
mode, namespace, err := resolveListingFilter(filters, e.namespaces.prompt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
prompts, err := e.listing.ListPrompts(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var filtered []domain.PromptIdentity
|
||||
for _, prompt := range prompts {
|
||||
if matchesListingNamespace(prompt.ID, mode, namespace) {
|
||||
filtered = append(filtered, prompt)
|
||||
}
|
||||
}
|
||||
if err := ctx.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return filtered, nil
|
||||
}
|
||||
|
||||
func filterIdentityList(
|
||||
ctx context.Context,
|
||||
filters []ListingFilter,
|
||||
scope string,
|
||||
list func(context.Context) ([]string, error),
|
||||
) ([]string, error) {
|
||||
mode, namespace, err := resolveListingFilter(filters, scope)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ids, err := list(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var filtered []string
|
||||
for _, id := range ids {
|
||||
if matchesListingNamespace(id, mode, namespace) {
|
||||
filtered = append(filtered, id)
|
||||
}
|
||||
}
|
||||
if err := ctx.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return filtered, nil
|
||||
}
|
||||
|
||||
func resolveListingFilter(filters []ListingFilter, scope string) (ListingFilterMode, string, error) {
|
||||
if len(filters) > 1 {
|
||||
return 0, "", fmt.Errorf("%w: at most one listing filter is allowed", usecase.ErrInvalidRequest)
|
||||
}
|
||||
filter := ListingFilter{}
|
||||
if len(filters) == 1 {
|
||||
filter = filters[0]
|
||||
}
|
||||
switch filter.Mode {
|
||||
case ListingDefault:
|
||||
if scope == "" {
|
||||
return ListingAll, "", nil
|
||||
}
|
||||
return ListingNamed, scope, nil
|
||||
case ListingNamed:
|
||||
if !domain.IsValidResourceNamespace(filter.Name) {
|
||||
return 0, "", fmt.Errorf("%w: invalid listing namespace", usecase.ErrInvalidRequest)
|
||||
}
|
||||
return ListingNamed, filter.Name, nil
|
||||
case ListingUnqualified, ListingAll:
|
||||
return filter.Mode, "", nil
|
||||
default:
|
||||
return 0, "", fmt.Errorf("%w: unknown listing filter mode", usecase.ErrInvalidRequest)
|
||||
}
|
||||
}
|
||||
|
||||
func matchesListingNamespace(id string, mode ListingFilterMode, namespace string) bool {
|
||||
switch mode {
|
||||
case ListingNamed:
|
||||
idNamespace, _, qualified := strings.Cut(id, "/")
|
||||
return qualified && idNamespace == namespace
|
||||
case ListingUnqualified:
|
||||
return !strings.Contains(id, "/")
|
||||
case ListingAll:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
func (e *Engine) Prepare(ctx context.Context, request domain.RunRequest) (*domain.PreparedRun, error) {
|
||||
request, err := e.qualifyRunRequest(request)
|
||||
|
||||
Reference in New Issue
Block a user