Files
commandkit/integrations/ckprompt/command.go

633 lines
25 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package ckprompt
import (
"context"
"errors"
"fmt"
"strings"
"go.maximumdirect.net/ckconfig"
commandkit "go.maximumdirect.net/commandkit"
promptkit "go.maximumdirect.net/promptkit"
)
type definitionInput[I any] struct {
namespaces namespaceInput
selection ckconfig.Selection
id string
captured commandkit.FlagValues[I]
selected I
}
type catalogInput[I any] struct {
selection ckconfig.Selection
namespace commandkit.Supplied[string]
allNamespaces commandkit.Supplied[bool]
kinds []promptkit.ResourceKind
filter promptkit.NamespaceFilter
captured commandkit.FlagValues[I]
selected I
}
type requirementsInput[I any] struct {
namespaces namespaceInput
selection ckconfig.Selection
inspection promptkit.RequirementInspectionRequest
resources ResourceInput
inputs PromptInputs
captured commandkit.FlagValues[I]
selected I
}
type variableAssignment struct {
key string
value string
}
type inputAssignment struct {
name string
value string
}
// Command captures the optional PromptKit catalogs, named inspection,
// requirements discovery, concrete request validation and preparation, and smoke-test
// callbacks and adds only supplied operations. Discovery and validation share the
// `requirements` group and remain independently optional. An absent suite
// claims no namespace; attached construction issues remain reportable. It adds
// no configuration family; callers compose ConfigScope independently.
// Construction, installation, help, and version do not run suite factories or
// providers. Command captures suite callbacks and selector metadata;
// callback-referenced objects and effects remain caller-owned. Suite documents
// invocation input and state initialization, zero values, and caller ownership
// of returned objects.
// Named resource inspections return native reports through shared formatting.
// Catalog commands accept config/overlay selection and suite selectors.
func Command[I, S any](suite Suite[I, S]) commandkit.Command {
return buildCommand(suite, nil)
}
func buildCommand[I, S any](suite Suite[I, S], aliases *[]commandkit.Declaration) commandkit.Command {
if suite.InspectPrompt == nil && suite.InspectProfile == nil && suite.InspectBackend == nil && suite.InspectRequirements == nil && suite.ValidateRequest == nil && suite.PrepareRequest == nil && suite.SmokeTest == nil && suite.ListPrompts == nil && suite.ListProfiles == nil && suite.ListBackends == nil && suite.ListNamespaces == nil && suite.ListCollections == nil {
command := commandkit.NoCommand()
for _, issue := range suite.issues {
command = commandkit.WithError(command, issue)
}
return command
}
suite.Selectors = append([]commandkit.Flag[I](nil), suite.Selectors...)
rootCommands := make([]commandkit.Command, 0, 3)
showCommands := make([]commandkit.Command, 0, 2)
if suite.InspectPrompt != nil {
showCommands = append(showCommands, newDefinitionCommand(suite, "prompt", aliases))
}
if suite.InspectProfile != nil {
showCommands = append(showCommands, newDefinitionCommand(suite, "profile", aliases))
}
if suite.InspectBackend != nil {
showCommands = append(showCommands, newDefinitionCommand(suite, "backend", aliases))
}
if suite.PrepareRequest != nil {
showCommands = append(showCommands, newPreparationCommand(suite, aliases))
}
if len(showCommands) != 0 {
rootCommands = append(rootCommands, commandkit.Group("show", "Show a named PromptKit definition.", showCommands...))
}
listCommands := make([]commandkit.Command, 0, 5)
if suite.ListPrompts != nil {
listCommands = append(listCommands, newCatalogListCommand(suite, "prompts"))
}
if suite.ListProfiles != nil {
listCommands = append(listCommands, newCatalogListCommand(suite, "profiles"))
}
if suite.ListBackends != nil {
listCommands = append(listCommands, newCatalogListCommand(suite, "backends"))
}
if suite.ListNamespaces != nil {
listCommands = append(listCommands, newCatalogListCommand(suite, "namespaces"))
}
if suite.ListCollections != nil {
listCommands = append(listCommands, newCatalogListCommand(suite, "collections"))
}
if len(listCommands) != 0 {
rootCommands = append(rootCommands, commandkit.Group("list", "List PromptKit catalogs.", listCommands...))
}
requirementsCommands := make([]commandkit.Command, 0, 2)
if suite.InspectRequirements != nil {
requirementsCommands = append(requirementsCommands, newRequirementsCommand(suite, aliases))
}
if suite.ValidateRequest != nil {
requirementsCommands = append(requirementsCommands, newValidationCommand(suite, aliases))
}
if len(requirementsCommands) != 0 {
rootCommands = append(rootCommands, commandkit.Group("requirements", "Inspect and validate prompt requirements.", requirementsCommands...))
}
if suite.SmokeTest != nil {
rootCommands = append(rootCommands, newSmokeTestCommand(suite, aliases))
}
command := commandkit.RootGroup("promptkit", "Inspect, list, validate requests, and run PromptKit smoke tests.", rootCommands...)
for _, issue := range suite.issues {
command = commandkit.WithError(command, issue)
}
return command
}
func newCatalogListCommand[I, S any](suite Suite[I, S], name string) commandkit.Command {
flags := commandkit.CaptureFlags(suite.Selectors, func(input **catalogInput[I]) *commandkit.FlagValues[I] {
if input == nil || *input == nil {
return nil
}
return &(*input).captured
})
flags = append(flags,
commandkit.ValueFlag(commandkit.FlagMeta{Name: "config", Usage: "Configuration input path.", Completion: commandkit.CompleteFiles()}, (*string)(nil), parseDefinitionString,
func(input **catalogInput[I], value commandkit.Supplied[string]) { (*input).selection.Config = value }),
commandkit.ValueFlag(commandkit.FlagMeta{Name: "overlay", Usage: "Configuration overlay path.", Completion: commandkit.CompleteFiles()}, (*string)(nil), parseDefinitionString,
func(input **catalogInput[I], value commandkit.Supplied[string]) { (*input).selection.Overlay = value }),
)
if name == "prompts" || name == "profiles" || name == "backends" {
flags = append(flags,
commandkit.ValueFlag(commandkit.FlagMeta{Name: "namespace", Usage: "Select one native namespace."}, (*string)(nil), parseDefinitionString,
func(input **catalogInput[I], value commandkit.Supplied[string]) { (*input).namespace = value }),
commandkit.BoolFlag(commandkit.FlagMeta{Name: "all-namespaces", Usage: "Select identities from every namespace."}, false,
func(input **catalogInput[I], value commandkit.Supplied[bool]) { (*input).allNamespaces = value }),
)
}
if name == "namespaces" {
flags = append(flags,
commandkit.RepeatFlag(commandkit.FlagMeta{Name: "kind", Usage: "Resource kind to include."}, []promptkit.ResourceKind(nil),
func(value string) (promptkit.ResourceKind, error) { return promptkit.ResourceKind(value), nil },
func(input **catalogInput[I], value commandkit.Supplied[[]promptkit.ResourceKind]) {
(*input).kinds = value.Value
}),
)
}
return commandkit.NewCommand(commandkit.CommandSpec[*catalogInput[I]]{
Name: name,
Summary: "List PromptKit " + name + ".",
NewInput: func() *catalogInput[I] {
return &catalogInput[I]{}
},
Flags: flags,
CheckInput: func(input *catalogInput[I]) error {
if input == nil {
return errors.New("PromptKit catalog input must not be nil")
}
if err := prepareCatalogFilter(input, name); err != nil {
return err
}
selected, err := prepareCatalogInput(suite, input)
if err != nil {
return err
}
input.selected = selected
return nil
},
Run: func(invocation commandkit.Invocation, input *catalogInput[I]) commandkit.Outcome {
switch name {
case "prompts":
return runCatalogList(invocation, suite, input, name, suite.ListPrompts, promptCatalogReport)
case "profiles":
return runCatalogList(invocation, suite, input, name, suite.ListProfiles, stringCatalogReport)
case "backends":
return runCatalogList(invocation, suite, input, name, suite.ListBackends, stringCatalogReport)
case "namespaces":
list := func(ctx context.Context, request CatalogRequest, value I, state S) (commandkit.Acquired[[]string], error) {
return suite.ListNamespaces(ctx, NamespaceRequest{Selection: request.Selection, Kinds: input.kinds}, value, state)
}
return runCatalogList(invocation, suite, input, name, list, stringCatalogReport)
case "collections":
list := func(ctx context.Context, request CatalogRequest, value I, state S) (commandkit.Acquired[[]promptkit.CollectionInfo], error) {
return suite.ListCollections(ctx, request.Selection, value, state)
}
return runCatalogList(invocation, suite, input, name, list, collectionCatalogReport)
default:
return definitionFailure("Unable to list the selected PromptKit catalog.", commandkit.Explanation{}, fmt.Errorf("unknown PromptKit catalog %q", name))
}
},
})
}
func prepareCatalogFilter[I any](input *catalogInput[I], name string) error {
input.filter = promptkit.NamespaceFilter{Mode: promptkit.NamespaceDefault}
if name != "prompts" && name != "profiles" && name != "backends" {
return nil
}
if input.allNamespaces.Set && input.allNamespaces.Value && input.namespace.Set {
return errors.New("--namespace cannot be combined with --all-namespaces")
}
if input.allNamespaces.Set && input.allNamespaces.Value {
input.filter.Mode = promptkit.NamespaceAll
return nil
}
if input.namespace.Set {
if input.namespace.Value == "" {
input.filter.Mode = promptkit.NamespaceUnqualified
} else {
input.filter.Mode = promptkit.NamespaceNamed
input.filter.Name = input.namespace.Value
}
}
return nil
}
func prepareCatalogInput[I, S any](suite Suite[I, S], input *catalogInput[I]) (I, error) {
return prepareSuiteInput(suite, input.selection, input.captured)
}
func newDefinitionCommand[I, S any](suite Suite[I, S], name string, aliases *[]commandkit.Declaration) commandkit.Command {
flags := commandkit.CaptureFlags(suite.Selectors, func(input **definitionInput[I]) *commandkit.FlagValues[I] {
if input == nil || *input == nil {
return nil
}
return &(*input).captured
})
flags = append(flags,
commandkit.ValueFlag(commandkit.FlagMeta{Name: "config", Usage: "Configuration input path.", Completion: commandkit.CompleteFiles()}, (*string)(nil), parseDefinitionString,
func(input **definitionInput[I], value commandkit.Supplied[string]) { (*input).selection.Config = value }),
commandkit.ValueFlag(commandkit.FlagMeta{Name: "overlay", Usage: "Configuration overlay path.", Completion: commandkit.CompleteFiles()}, (*string)(nil), parseDefinitionString,
func(input **definitionInput[I], value commandkit.Supplied[string]) {
(*input).selection.Overlay = value
}),
)
if suite.namespaces != nil {
flags = appendRequestBindings(flags, "promptkit show "+name, aliases, namespaceFlags(func(input **definitionInput[I]) *namespaceInput { return &(*input).namespaces }, name)...)
}
spec := commandkit.CommandSpec[*definitionInput[I]]{
Name: name,
Summary: "Inspect a named PromptKit " + name + ".",
NewInput: func() *definitionInput[I] {
return &definitionInput[I]{}
},
Flags: flags,
Arguments: []commandkit.Argument[*definitionInput[I]]{
commandkit.ValueArgument(commandkit.ArgumentMeta{Name: strings.ToUpper(name) + "_ID", Usage: "Resource ID."}, true, parseDefinitionString,
func(input **definitionInput[I], value commandkit.Supplied[string]) { (*input).id = value.Value }),
},
CheckInput: func(input *definitionInput[I]) error {
selected, err := prepareDefinitionInput(suite, input)
if err != nil {
return err
}
input.selected = selected
return nil
},
Run: func(invocation commandkit.Invocation, input *definitionInput[I]) commandkit.Outcome {
return runDefinitionInspection(invocation, suite, input, name)
},
}
return commandkit.NewCommand(spec)
}
func newRequirementsCommand[I, S any](suite Suite[I, S], aliases *[]commandkit.Declaration) commandkit.Command {
flags := commandkit.CaptureFlags(suite.Selectors, func(input **requirementsInput[I]) *commandkit.FlagValues[I] {
if input == nil || *input == nil {
return nil
}
return &(*input).captured
})
flags = append(flags,
commandkit.ValueFlag(commandkit.FlagMeta{Name: "config", Usage: "Configuration input path.", Completion: commandkit.CompleteFiles()}, (*string)(nil), parseDefinitionString,
func(input **requirementsInput[I], value commandkit.Supplied[string]) {
(*input).selection.Config = value
}),
commandkit.ValueFlag(commandkit.FlagMeta{Name: "overlay", Usage: "Configuration overlay path.", Completion: commandkit.CompleteFiles()}, (*string)(nil), parseDefinitionString,
func(input **requirementsInput[I], value commandkit.Supplied[string]) {
(*input).selection.Overlay = value
}),
)
resources := NewResourceFlags(func(input **requirementsInput[I]) *ResourceInput { return &(*input).resources })
inputs := NewPromptInputFlags(func(input **requirementsInput[I]) *PromptInputs { return &(*input).inputs })
flags = appendRequestBindings(flags, "promptkit requirements show", aliases, requestBinding[*requirementsInput[I]]{resources.Profile, requestFlags.Profile}, requestBinding[*requirementsInput[I]]{resources.SessionID, requestFlags.SessionID}, requestBinding[*requirementsInput[I]]{inputs.Variable, requestFlags.Var})
if suite.namespaces != nil {
flags = appendRequestBindings(flags, "promptkit requirements show", aliases, namespaceFlags(func(input **requirementsInput[I]) *namespaceInput { return &(*input).namespaces }, "prompt", "profile")...)
}
spec := commandkit.CommandSpec[*requirementsInput[I]]{
Name: "show",
Summary: "Inspect requirements for a named PromptKit prompt.",
Description: "Profile selection establishes the reported profile identity, not a different set of requirements. A profile namespace qualifies an explicitly supplied short profile ID, not the prompt’s stored default reference.",
NewInput: func() *requirementsInput[I] {
return &requirementsInput[I]{}
},
Flags: flags,
Arguments: []commandkit.Argument[*requirementsInput[I]]{
commandkit.ValueArgument(commandkit.ArgumentMeta{Name: "id", Usage: "Prompt ID."}, true, parseDefinitionString,
func(input **requirementsInput[I], value commandkit.Supplied[string]) {
(*input).inspection.PromptID = value.Value
}),
},
CheckInput: func(input *requirementsInput[I]) error {
input.inspection.ProfileID = input.resources.Profile.Value
input.inspection.SessionID = input.resources.SessionID.Value
input.inspection.Vars = input.inputs.Vars
selected, err := prepareSuiteInput(suite, input.selection, input.captured)
if err != nil {
return err
}
input.selected = selected
return nil
},
Run: func(invocation commandkit.Invocation, input *requirementsInput[I]) commandkit.Outcome {
return runRequirementsInspection(invocation, suite, input)
},
}
return commandkit.NewCommand(spec)
}
func prepareDefinitionInput[I, S any](suite Suite[I, S], input *definitionInput[I]) (I, error) {
return prepareSuiteInput(suite, input.selection, input.captured)
}
func prepareSuiteInput[I, S any](suite Suite[I, S], selection ckconfig.Selection, captured commandkit.FlagValues[I]) (I, error) {
var selected I
if suite.NewInput != nil {
selected = suite.NewInput()
}
if err := captured.ApplyTo(&selected, suite.Selectors...); err != nil {
return selected, err
}
if suite.CheckInput != nil {
if err := suite.CheckInput(selection, selected); err != nil {
return selected, err
}
}
return selected, nil
}
func runCatalogList[T any, I, S any](
invocation commandkit.Invocation,
suite Suite[I, S],
input *catalogInput[I],
catalog string,
list func(context.Context, CatalogRequest, I, S) (commandkit.Acquired[T], error),
project func(T) any,
) commandkit.Outcome {
if invocation.Context != nil {
if err := invocation.Context.Err(); err != nil {
return definitionFailure("PromptKit catalog listing was canceled.", commandkit.Explanation{}, err)
}
}
var state S
if suite.NewState != nil {
state = suite.NewState()
}
if invocation.Context != nil {
if err := invocation.Context.Err(); err != nil {
return definitionFailure("PromptKit catalog listing was canceled.", commandkit.Explanation{}, err)
}
}
acquired, err := list(invocation.Context, CatalogRequest{Selection: input.selection, Filter: input.filter}, input.selected, state)
queuePromptDiagnostics(invocation.Reporter(), acquired.Diagnostics)
if err != nil {
return definitionFailure(fmt.Sprintf("Unable to list PromptKit %s.", catalog), acquired.Explanation, err)
}
if invocation.Context != nil {
if err := invocation.Context.Err(); err != nil {
return definitionFailure("PromptKit catalog listing was canceled.", acquired.Explanation, err)
}
}
_ = invocation.Reporter().Report(commandkit.Report{Value: project(acquired.Value)})
return commandkit.Success()
}
func runDefinitionInspection[I, S any](invocation commandkit.Invocation, suite Suite[I, S], input *definitionInput[I], kind string) commandkit.Outcome {
if err := promptKitContextError(invocation.Context); err != nil {
return definitionFailure("PromptKit inspection was canceled.", commandkit.Explanation{}, err)
}
var state S
if suite.NewState != nil {
state = suite.NewState()
}
if err := promptKitContextError(invocation.Context); err != nil {
return definitionFailure("PromptKit inspection was canceled.", commandkit.Explanation{}, err)
}
var value any
var diagnostics []commandkit.Diagnostic
var explanation commandkit.Explanation
var err error
switch kind {
case "prompt":
var result commandkit.Acquired[*promptkit.PromptInspection]
result, err = suite.InspectPrompt(invocation.Context, PromptRequest{Selection: input.selection, ID: input.id, Namespaces: selectedNamespaces(suite.namespaces, input.namespaces)}, input.selected, state)
if result.Value != nil {
value = result.Value
}
diagnostics, explanation = result.Diagnostics, result.Explanation
case "profile":
var result commandkit.Acquired[*promptkit.ProfileInspection]
result, err = suite.InspectProfile(invocation.Context, ProfileRequest{Selection: input.selection, ID: input.id, Namespaces: selectedNamespaces(suite.namespaces, input.namespaces)}, input.selected, state)
if result.Value != nil {
value = result.Value
}
diagnostics, explanation = result.Diagnostics, result.Explanation
case "backend":
var result commandkit.Acquired[*promptkit.BackendInspection]
result, err = suite.InspectBackend(invocation.Context, BackendRequest{Selection: input.selection, ID: input.id, Namespaces: selectedNamespaces(suite.namespaces, input.namespaces)}, input.selected, state)
if result.Value != nil {
value = result.Value
}
diagnostics, explanation = result.Diagnostics, result.Explanation
}
queuePromptDiagnostics(invocation.Reporter(), diagnostics)
if err != nil {
return definitionFailure(fmt.Sprintf("Unable to inspect PromptKit %s %q.", kind, input.id), explanation, err)
}
if value == nil {
return definitionFailure(fmt.Sprintf("PromptKit %s inspection returned no result.", kind), explanation, nil)
}
if err := promptKitContextError(invocation.Context); err != nil {
return definitionFailure(fmt.Sprintf("Inspection of PromptKit %s %q was canceled.", kind, input.id), explanation, err)
}
_ = invocation.Reporter().Report(commandkit.Report{Value: value})
return commandkit.Success()
}
type requirementsInspectionReport struct {
PromptID string `json:"prompt_id"`
ProfileID string `json:"profile_id"`
Complete bool `json:"complete"`
Inputs []requirementInputReport `json:"inputs"`
Variables []requirementVariableReport `json:"variables"`
Issues []requirementIssueReport `json:"issues"`
}
type requirementInputReport struct {
Name string `json:"name"`
Kind string `json:"kind"`
Declared bool `json:"declared"`
ContentType *string `json:"content_type"`
Description *string `json:"description"`
Explanation string `json:"explanation"`
}
type requirementVariableReport struct {
Name string `json:"name"`
Kind string `json:"kind"`
Supplied bool `json:"supplied"`
Explanation string `json:"explanation"`
}
type requirementIssueReport struct {
Code string `json:"code"`
InputName string `json:"input_name"`
VariableName string `json:"variable_name"`
Description string `json:"description"`
}
func requirementsReportFromInspection(inspection *promptkit.RequirementInspection) requirementsInspectionReport {
report := requirementsInspectionReport{
PromptID: inspection.PromptID,
ProfileID: inspection.ProfileID,
Complete: inspection.Complete,
Inputs: make([]requirementInputReport, 0, len(inspection.Inputs)),
Variables: make([]requirementVariableReport, 0, len(inspection.Variables)),
Issues: make([]requirementIssueReport, 0, len(inspection.Issues)),
}
for _, input := range inspection.Inputs {
report.Inputs = append(report.Inputs, requirementInputReport{
Name: input.Name,
Kind: string(input.Kind),
Declared: input.Declared,
ContentType: input.ContentType,
Description: input.Description,
Explanation: input.Explanation,
})
}
for _, variable := range inspection.Variables {
report.Variables = append(report.Variables, requirementVariableReport{
Name: variable.Name,
Kind: string(variable.Kind),
Supplied: variable.Supplied,
Explanation: variable.Explanation,
})
}
for _, issue := range inspection.Issues {
report.Issues = append(report.Issues, requirementIssueReport{
Code: string(issue.Code),
InputName: issue.InputName,
VariableName: issue.VariableName,
Description: issue.Description,
})
}
return report
}
func runRequirementsInspection[I, S any](invocation commandkit.Invocation, suite Suite[I, S], input *requirementsInput[I]) commandkit.Outcome {
if invocation.Context != nil {
if err := invocation.Context.Err(); err != nil {
return definitionFailure("PromptKit requirements inspection was canceled.", commandkit.Explanation{}, err)
}
}
var state S
if suite.NewState != nil {
state = suite.NewState()
}
if invocation.Context != nil {
if err := invocation.Context.Err(); err != nil {
return definitionFailure("PromptKit requirements inspection was canceled.", commandkit.Explanation{}, err)
}
}
request := RequirementsRequest{Selection: input.selection, Inspection: input.inspection, Namespaces: selectedNamespaces(suite.namespaces, input.namespaces)}
acquired, err := suite.InspectRequirements(invocation.Context, request, input.selected, state)
queuePromptDiagnostics(invocation.Reporter(), acquired.Diagnostics)
if err != nil {
return definitionFailure(fmt.Sprintf("Unable to inspect requirements for PromptKit prompt %q.", input.inspection.PromptID), acquired.Explanation, err)
}
if acquired.Value == nil {
return definitionFailure("PromptKit requirements inspection returned no result.", acquired.Explanation, nil)
}
if invocation.Context != nil {
if err := invocation.Context.Err(); err != nil {
return definitionFailure(fmt.Sprintf("Requirements inspection for PromptKit prompt %q was canceled.", input.inspection.PromptID), acquired.Explanation, err)
}
}
_ = invocation.Reporter().Report(commandkit.Report{Value: requirementsReportFromInspection(acquired.Value)})
return commandkit.Success()
}
type promptCatalogEntry struct {
ID string `json:"id"`
}
func promptCatalogReport(prompts []promptkit.PromptIdentity) any {
entries := make([]promptCatalogEntry, len(prompts))
for index, prompt := range prompts {
entries[index] = promptCatalogEntry{ID: prompt.ID}
}
return entries
}
func stringCatalogReport(values []string) any {
if values == nil {
return []string{}
}
return values
}
type collectionCatalogEntry struct {
Namespace string `json:"namespace"`
Version *string `json:"version,omitempty"`
}
func collectionCatalogReport(collections []promptkit.CollectionInfo) any {
entries := make([]collectionCatalogEntry, len(collections))
for index, collection := range collections {
entries[index] = collectionCatalogEntry{Namespace: collection.Namespace, Version: collection.ReleaseVersion}
}
return entries
}
func queuePromptDiagnostics(reporter *commandkit.Reporter, diagnostics []commandkit.Diagnostic) {
deferred := reporter.AtEnd()
for _, diagnostic := range diagnostics {
if diagnostic.Write == nil {
continue
}
switch diagnostic.Severity {
case commandkit.SeverityInfo:
_ = deferred.Info("", diagnostic.Write)
case commandkit.SeverityDebug:
_ = deferred.Debug("", diagnostic.Write)
case commandkit.SeverityProgress:
_ = reporter.Progress("", diagnostic.Write)
default:
_ = deferred.Warn("", diagnostic.Write)
}
}
}
func definitionFailure(message string, explanation commandkit.Explanation, cause error) commandkit.Outcome {
outcome := commandkit.Failure(message)
if explanation.Message != "" || len(explanation.Details) > 0 {
outcome = outcome.WithExplanation(explanation.Message, explanation.Details...)
}
if cause != nil {
outcome = outcome.WithCause(cause)
}
return outcome
}
func parseDefinitionString(value string) (string, error) { return value, nil }
func parseVariableAssignment(raw string) (variableAssignment, error) {
key, value, found := strings.Cut(raw, "=")
if !found {
return variableAssignment{}, fmt.Errorf("variable assignment %q must contain =", raw)
}
return variableAssignment{key: key, value: value}, nil
}
func parseInputAssignment(raw string) (inputAssignment, error) {
name, value, found := strings.Cut(raw, "=")
if !found {
return inputAssignment{}, fmt.Errorf("input assignment %q must contain =", raw)
}
return inputAssignment{name: name, value: value}, nil
}