载入中...
搜索中...
未找到
StepKindRegistry.cpp
浏览该文件的文档.
2
3#include <utility>
4#include <vector>
5
6namespace eve::dnut {
7
8namespace {
9
10bool isBareWord(const std::string& value) {
11 if (value.empty()) return false;
12 for (const char c : value) {
13 const bool accepted = (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') ||
14 c == '_' || c == '.' || c == ':';
15 if (!accepted) return false;
16 }
17 return true;
18}
19
20bool matchesFieldType(const eve::Value& value, StepFieldType type) {
21 switch (type) {
22 case StepFieldType::String: return value.isString();
23 case StepFieldType::Number: return value.isNumeric();
24 case StepFieldType::Integer: return value.isInt64();
25 case StepFieldType::Boolean: return value.isBool();
26 case StepFieldType::Any: return !value.isNull();
27 }
28 return false;
29}
30
31const char* fieldTypeName(StepFieldType type) noexcept {
32 switch (type) {
33 case StepFieldType::String: return "string";
34 case StepFieldType::Number: return "number";
35 case StepFieldType::Integer: return "integer";
36 case StepFieldType::Boolean: return "boolean";
37 case StepFieldType::Any: return "value";
38 }
39 return "value";
40}
41
42} // namespace
43
44const char* stepShapeName(StepShape shape) noexcept {
45 switch (shape) {
46 case StepShape::Instant: return "instant";
47 case StepShape::Await: return "await";
48 }
49 return "unknown";
50}
51
53 if (!isBareWord(descriptor.type))
55 eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "step type must be a non-empty bare word", "type", {}, "dnut.step-registry"));
56 if (descriptors_.contains(descriptor.type))
58 eve::Diagnostic::error(eve::DiagnosticCode::AlreadyExists, "step type '" + descriptor.type + "' is already registered", descriptor.type, {}, "dnut.step-registry"));
59 for (const auto& field : descriptor.fields) {
60 if (!isBareWord(field.name))
62 eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "step '" + descriptor.type + "' declares an invalid field name", field.name, {}, "dnut.step-registry"));
63 }
64 if (descriptor.displayName.empty()) descriptor.displayName = descriptor.type;
65 descriptors_.emplace(descriptor.type, std::move(descriptor));
67}
68
70 const std::string key(type);
71 if (!descriptors_.contains(key))
73 eve::Diagnostic::error(eve::DiagnosticCode::NotFound, "step type '" + key + "' must be declared before a handler is bound", key, {}, "dnut.step-registry"));
74 if (!handler)
76 eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "step handler for '" + key + "' must be callable", key, {}, "dnut.step-registry"));
77 handlers_[key] = std::move(handler);
79}
80
81void StepKindRegistry::unregisterStep(std::string_view type) {
82 const std::string key(type);
83 descriptors_.erase(key);
84 handlers_.erase(key);
85}
86
87bool StepKindRegistry::contains(std::string_view type) const { return descriptors_.contains(type); }
88
90 const auto found = descriptors_.find(type);
91 return found == descriptors_.end() ? nullptr : &found->second;
92}
93
94std::vector<StepKindDescriptor> StepKindRegistry::descriptors() const {
95 std::vector<StepKindDescriptor> out;
96 out.reserve(descriptors_.size());
97 for (const auto& entry : descriptors_) out.push_back(entry.second);
98 return out;
99}
100
101bool StepKindRegistry::hasHandler(std::string_view type) const { return handlers_.contains(type); }
102
104 const auto* contract = descriptor(node.type);
105 if (!contract)
107 eve::Diagnostic::error(eve::DiagnosticCode::NotFound, "node '" + node.id + "' uses unregistered step type '" + node.type + "'", "nodes." + node.id + ".type", {}, "dnut.step-registry"));
108 if (!node.payload.isObject())
110 eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "node '" + node.id + "' payload must be an object", "nodes." + node.id + ".payload", {}, "dnut.step-registry"));
111
112 for (const auto& field : contract->fields) {
113 const eve::Value* value = node.payload.find(field.name);
114 if (!value) {
115 if (field.required)
117 eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "step '" + node.type + "' on node '" + node.id + "' requires field '" +
118 field.name + "'", "nodes." + node.id + ".payload." + field.name, {}, "dnut.step-registry"));
119 continue;
120 }
121 if (!matchesFieldType(*value, field.type))
123 eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "step '" + node.type + "' field '" + field.name + "' must be " +
124 fieldTypeName(field.type), "nodes." + node.id + ".payload." + field.name, {}, "dnut.step-registry"));
125 }
126
127 for (const auto& key : node.payload.keys()) {
128 bool declared = false;
129 for (const auto& field : contract->fields) {
130 if (field.name == key) {
131 declared = true;
132 break;
133 }
134 }
135 if (!declared)
137 eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "step '" + node.type + "' does not accept field '" + key + "'", "nodes." + node.id + ".payload." + key, {}, "dnut.step-registry"));
138 }
139
140 if (contract->shape == StepShape::Instant && !hasHandler(node.type))
142 eve::Diagnostic::error(eve::DiagnosticCode::PreconditionViolation, "step '" + node.type + "' is instant but has no handler bound", "nodes." + node.id + ".type", {}, "dnut.step-registry"));
143 if (contract->validate) {
144 auto domainResult = contract->validate(node);
145 if (!domainResult.ok()) return domainResult;
146 }
148}
149
151 const auto* contract = descriptor(node.type);
152 if (!contract) {
153 StepOutcome outcome;
154 outcome.status = StepStatus::Failed;
155 outcome.error = "step type '" + node.type + "' is not registered";
156 return outcome;
157 }
158 const auto handler = handlers_.find(node.type);
159 if (handler == handlers_.end()) {
160 if (contract->shape == StepShape::Await) {
161 StepOutcome outcome;
162 outcome.status = StepStatus::Blocked;
163 return outcome;
164 }
165 StepOutcome outcome;
166 outcome.status = StepStatus::Failed;
167 outcome.error = "step type '" + node.type + "' has no handler";
168 return outcome;
169 }
170 return handler->second(node, context);
171}
172
173} // namespace eve::dnut
double value
std::string descriptor
std::uint32_t key
ShaderImageInput shape
std::int32_t c
HSQOBJECT handler
const RoadNode * node
bool found
Extensible step vocabulary, payload schema and dispatch.
const VegetationPresetContext & context
static Diagnostic error(DiagnosticCode code, std::string message, std::string path={}, DiagnosticDetails details={}, std::string source={})
Construct an error diagnostic with the standard error severity.
Definition Diagnostic.h:125
Move-only operation result carrying either a value or Status.
Definition Result.h:155
static Result success(T value)
Construct a successful result owning value.
Definition Result.h:164
static Result failure(Status status)
Construct a failed result from a structured status.
Definition Result.h:175
The canonical owning dynamic value used by data-facing protocols.
Definition Value.h:31
Value * find(const std::string &key) noexcept
Return an object member, or nullptr when absent.
Definition Value.cpp:124
StepOutcome dispatch(const SequenceNode &node, const StepContext &context) const
Route one already validated node to its handler.
eve::Result< void > registerHandler(std::string_view type, StepHandler handler)
Bind an owning handler to an already declared step type.
eve::Result< void > registerStep(StepKindDescriptor descriptor)
Declare one step type.
eve::Result< void > validate(const SequenceNode &node) const
Validate one node against its declared payload schema.
bool contains(std::string_view type) const
Return whether type is declared.
bool hasHandler(std::string_view type) const
Return whether a handler is bound for type.
const StepKindDescriptor * descriptor(std::string_view type) const
Return the declared contract for type.
void unregisterStep(std::string_view type)
Remove a step type and its handler; absent types are a no-op.
std::vector< StepKindDescriptor > descriptors() const
Return every declared contract in lexical type order.
Type type() const
Return the active value type.
StepFieldType
Accepted value kind of one declared payload field.
std::function< StepOutcome(const SequenceNode &, const StepContext &)> StepHandler
Consumer-owned handler for one registered step type.
StepShape
Declares whether a step completes inside its call or suspends the runtime.
@ Await
The step may suspend the runtime.
@ Instant
The step completes synchronously; Completed continues at next.
const char * stepShapeName(StepShape shape) noexcept
Return the stable lowercase spelling of a step shape.
One immutable step of a compiled sequence.
Borrowed execution context handed to one step handler.
Owning authoring/runtime contract for one step type.
Structured outcome of one dispatched step.