mirror of
https://github.com/larksuite/cli.git
synced 2026-09-14 18:42:53 +08:00
153 lines
4.6 KiB
Go
153 lines
4.6 KiB
Go
// Copyright (c) 2026 Lark Technologies Pte. Ltd.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
// Package consume is the consume use case: it turns a request plus a compiled
|
|
// catalog entry into one immutable decision, renders that decision for
|
|
// dry-run, and executes the very same decision for a real run. Deciding is
|
|
// free of external writes; every write happens behind Execute.
|
|
package consume
|
|
|
|
import (
|
|
"errors"
|
|
"maps"
|
|
"slices"
|
|
"time"
|
|
|
|
"github.com/larksuite/cli/errs"
|
|
)
|
|
|
|
// Request carries the caller's consume inputs, already parsed from flags.
|
|
type Request struct {
|
|
EventKey string
|
|
Params map[string]string
|
|
JQExpr string
|
|
OutputDir string
|
|
DryRun bool
|
|
MaxEvents int
|
|
Timeout time.Duration
|
|
IsTTY bool
|
|
}
|
|
|
|
type PreconditionStatus string
|
|
|
|
const (
|
|
// PreconditionOK: the read-only check passed.
|
|
PreconditionOK PreconditionStatus = "ok"
|
|
// PreconditionUnknown: a weak dependency could not answer. Real execution
|
|
// proceeds (matching the long-standing degrade-and-continue behavior);
|
|
// dry-run reports the fact instead of pretending readiness.
|
|
PreconditionUnknown PreconditionStatus = "unknown"
|
|
// PreconditionBlocked: the check found a state that makes a real run
|
|
// refuse to start. Execution returns the blocking error; dry-run renders it.
|
|
PreconditionBlocked PreconditionStatus = "blocked"
|
|
)
|
|
|
|
// Precondition is one read-only preflight finding. BlockErr carries the exact
|
|
// error a real run would return, so the refusal is identical whether or not a
|
|
// decision was rendered first.
|
|
type Precondition struct {
|
|
Name string
|
|
Status PreconditionStatus
|
|
Detail string
|
|
BlockErr error
|
|
}
|
|
|
|
// Decision is the single-step consume decision: the classified result of one
|
|
// request against one compiled entry. Fields are unexported and deep-copied
|
|
// at construction; renderers read it through View.
|
|
type Decision struct {
|
|
eventKey string
|
|
domain string
|
|
identity string
|
|
status string
|
|
params map[string]string
|
|
scope string
|
|
preconditions []Precondition
|
|
preparation *PreparationDecision
|
|
wouldRead []string
|
|
wouldWrite []string
|
|
blockErr error
|
|
}
|
|
|
|
const (
|
|
StatusReady = "ready"
|
|
StatusUnknown = "unknown"
|
|
StatusBlocked = "blocked"
|
|
)
|
|
|
|
// View returns a deep-copied, exported view of the decision — the only way
|
|
// renderers and other packages read it. Mutating the view never touches the
|
|
// decision.
|
|
func (d *Decision) View() DecisionView {
|
|
v := DecisionView{
|
|
EventKey: d.eventKey,
|
|
Domain: d.domain,
|
|
Identity: d.identity,
|
|
Status: d.status,
|
|
Params: maps.Clone(d.params),
|
|
Scope: d.scope,
|
|
WouldRead: slices.Clone(d.wouldRead),
|
|
WouldWrite: slices.Clone(d.wouldWrite),
|
|
}
|
|
for _, p := range d.preconditions {
|
|
pv := PreconditionView{Name: p.Name, Status: string(p.Status), Detail: p.Detail}
|
|
// A blocking error already carries the classification and the recovery
|
|
// action; a preview that dropped them would be the one surface with no
|
|
// way forward, even though it exists to be read before acting.
|
|
if problem, ok := errs.ProblemOf(p.BlockErr); ok {
|
|
pv.Subtype = string(problem.Subtype)
|
|
pv.Hint = problem.Hint
|
|
}
|
|
var permission *errs.PermissionError
|
|
if errors.As(p.BlockErr, &permission) {
|
|
pv.MissingScopes = slices.Clone(permission.MissingScopes)
|
|
}
|
|
v.Preconditions = append(v.Preconditions, pv)
|
|
}
|
|
if d.preparation != nil {
|
|
v.Preparation = &PreparationView{
|
|
Strategy: string(d.preparation.Strategy),
|
|
Condition: d.preparation.Condition,
|
|
Action: d.preparation.Action,
|
|
}
|
|
}
|
|
return v
|
|
}
|
|
|
|
// DecisionView is the exported render model of a Decision.
|
|
type DecisionView struct {
|
|
EventKey string
|
|
Domain string
|
|
Identity string
|
|
Status string
|
|
Params map[string]string
|
|
Scope string
|
|
Preconditions []PreconditionView
|
|
Preparation *PreparationView
|
|
WouldRead []string
|
|
WouldWrite []string
|
|
}
|
|
|
|
// PreconditionView is the render model of one precondition. Beyond the human
|
|
// sentence in Detail it carries the machine-readable half of the failure, so a
|
|
// caller previewing a consume gets the same recovery information a real run
|
|
// would put in its error envelope instead of having to parse prose.
|
|
type PreconditionView struct {
|
|
Name string
|
|
Status string
|
|
Detail string
|
|
// Subtype classifies the failure the way the error envelope does, which is
|
|
// what callers are told to branch on.
|
|
Subtype string
|
|
// Hint is the recovery action, verbatim from the error that blocked.
|
|
Hint string
|
|
// MissingScopes lists the scopes to grant, when that is what is missing.
|
|
MissingScopes []string
|
|
}
|
|
|
|
type PreparationView struct {
|
|
Strategy string
|
|
Condition string
|
|
Action string
|
|
}
|