package profile import ( "context" "fmt" "io/fs" "sort" "strings" "gitea.maximumdirect.net/eric/promptkit/internal/domain" "gitea.maximumdirect.net/eric/promptkit/internal/filecatalog" "gitea.maximumdirect.net/eric/promptkit/internal/jsonvalue" ) // LoadedProfileMetadata identifies one profile accepted by LoadFSRepository. type LoadedProfileMetadata struct { ID string Path string ExplicitFields []string } // LoadFSRepository eagerly validates every profile under root and returns an // immutable raw repository and independently owned source metadata. func LoadFSRepository(ctx context.Context, fsys fs.FS, root string) (Repository, []LoadedProfileMetadata, error) { if fsys == nil { return nil, nil, fmt.Errorf("failed to read profile directory: filesystem is nil") } paths, err := filecatalog.FindFSYAMLFiles(ctx, fsys, root) if err != nil { return nil, nil, fmt.Errorf("failed to read profile directory: %w", err) } repository := &loadedRepository{profiles: make(map[string]domain.ExecutionProfile, len(paths))} metadata := make([]LoadedProfileMetadata, 0, len(paths)) for _, path := range paths { if err := ctx.Err(); err != nil { return nil, nil, err } data, err := fs.ReadFile(fsys, path) if err != nil { return nil, nil, fmt.Errorf("failed to read profile file %s: %w", filecatalog.DisplayPath(root, path), err) } fileMetadata, err := readProfileFileMetadata(data) if err != nil { return nil, nil, fmt.Errorf("%w: %s: %v", ErrInvalidYAML, filecatalog.DisplayPath(root, path), err) } if fileMetadata.hasRawAPIKey { return nil, nil, fmt.Errorf("%w: %s", ErrRawAPIKeyNotAllowed, filecatalog.DisplayPath(root, path)) } definition, err := decodeProfile(data) if err != nil { return nil, nil, fmt.Errorf("%w: %s: %v", ErrInvalidYAML, filecatalog.DisplayPath(root, path), err) } definition.ExtraParams, err = jsonvalue.CopyMap(definition.ExtraParams) if err != nil { return nil, nil, fmt.Errorf("%w: %s: %v", ErrInvalidProfile, filecatalog.DisplayPath(root, path), err) } if err := NormalizeAndValidateDefinition(definition); err != nil { return nil, nil, fmt.Errorf("%w: %s: %v", ErrInvalidProfile, filecatalog.DisplayPath(root, path), err) } if _, exists := repository.profiles[definition.ID]; exists { return nil, nil, fmt.Errorf("%w: duplicate execution profile id %q", ErrInvalidProfile, definition.ID) } repository.profiles[definition.ID] = *definition fields := append([]string(nil), fileMetadata.explicitFields...) sort.Strings(fields) metadata = append(metadata, LoadedProfileMetadata{ ID: definition.ID, Path: filecatalog.DisplayPath(root, path), ExplicitFields: fields, }) } sort.Slice(metadata, func(left, right int) bool { return metadata[left].ID < metadata[right].ID }) return repository, metadata, nil } type loadedRepository struct { profiles map[string]domain.ExecutionProfile } func (r *loadedRepository) GetProfile(ctx context.Context, id string) (*domain.ExecutionProfile, error) { if err := ctx.Err(); err != nil { return nil, err } definition, found := r.profiles[strings.TrimSpace(id)] if !found { return nil, ErrProfileNotFound } extraParams, err := jsonvalue.CopyMap(definition.ExtraParams) if err != nil { return nil, fmt.Errorf("copy loaded profile %q: %w", definition.ID, err) } definition.ExtraParams = extraParams return &definition, nil }