载入中...
搜索中...
未找到
EditingAuthority.cpp
浏览该文件的文档.
2
3#include <algorithm>
4#include <exception>
5
6namespace eve::editing {
7namespace {
8
9void appendAffected(TransactionReceipt& receipt, const DomainOperation& operation) {
10 for (const ObjectRefValue& object : operation.affectedObjects) {
11 if (std::find(receipt.affectedObjects.begin(), receipt.affectedObjects.end(), object) ==
12 receipt.affectedObjects.end())
13 receipt.affectedObjects.push_back(object);
14 }
15}
16
17} // namespace
18
20 std::span<const DomainOperation> operations) {
21 if (!target_)
22 return eve::editing::failed<AuthorityPlan>(Status::Failed, RuleId("editor.authority.missing-target"),
23 "Local authority has no target");
24 if (transaction.id.empty() || transaction.target.empty())
25 return eve::editing::failed<AuthorityPlan>(Status::Rejected, RuleId("editor.authority.invalid-transaction"),
26 "Transaction id and target are required");
27 if (transaction.target != target_->targetId())
28 return eve::editing::failed<AuthorityPlan>(Status::Rejected, RuleId("editor.authority.target-mismatch"),
29 "Transaction target does not match the authority target");
30 if (transaction.baseRevision != target_->revision())
31 return eve::editing::failed<AuthorityPlan>(Status::Conflict, RuleId("editor.authority.revision-conflict"),
32 "Target changed since the transaction was planned");
33 if (committed_.contains(transaction.id))
34 return eve::editing::failed<AuthorityPlan>(Status::Conflict, RuleId("editor.authority.duplicate-transaction"),
35 "Transaction id was already committed");
36 for (const DomainOperation& operation : operations) {
37 if (operation.type.empty())
38 return eve::editing::failed<AuthorityPlan>(Status::Rejected, RuleId("editor.operation.missing-type"),
39 "Domain operation type is required");
40 if (operation.target != transaction.target)
41 return eve::editing::failed<AuthorityPlan>(Status::Rejected, RuleId("editor.operation.target-mismatch"),
42 "Domain operation target does not match the transaction");
43 }
44
45 AuthorityPlan plan;
46 plan.transaction = transaction;
47 plan.validatedRevision = target_->revision();
48 plan.operations.assign(operations.begin(), operations.end());
49 return eve::editing::applied<AuthorityPlan>(std::move(plan));
50}
51
53 if (!target_)
54 return eve::editing::failed<TransactionReceipt>(Status::Failed, RuleId("editor.authority.missing-target"),
55 "Local authority has no target");
56 if (plan.validatedRevision != target_->revision())
57 return eve::editing::failed<TransactionReceipt>(Status::Conflict, RuleId("editor.authority.revision-conflict"),
58 "Target changed after authority preflight");
59
60 TransactionReceipt receipt;
61 receipt.id = plan.transaction.id;
63 receipt.beforeRevision = target_->revision();
64
65 std::size_t appliedCount = 0;
66 try {
67 for (const DomainOperation& operation : plan.operations) {
69 if (!result.ok()) {
70 Result<void> rollback = rollbackApplied(plan.operations, appliedCount);
72 receipt.afterRevision = target_->revision();
73 receipt.diagnostics = result.diagnostics();
74 const auto& rollbackDiagnostics = rollback.diagnostics();
75 receipt.diagnostics.insert(receipt.diagnostics.end(), rollbackDiagnostics.begin(),
76 rollbackDiagnostics.end());
77 const Status status = rollback.ok() ? result.code() : Status::Failed;
79 }
80 ++appliedCount;
81 appendAffected(receipt, operation);
82 }
83 } catch (const std::exception& exception) {
84 Result<void> rollback = rollbackApplied(plan.operations, appliedCount);
85 return eve::editing::failed<TransactionReceipt>(Status::Failed, RuleId("editor.authority.target-exception"),
86 exception.what());
87 } catch (...) {
88 Result<void> rollback = rollbackApplied(plan.operations, appliedCount);
89 (void)rollback;
90 return eve::editing::failed<TransactionReceipt>(Status::Failed, RuleId("editor.authority.target-exception"),
91 "Domain operation target threw an unknown exception");
92 }
93
95 receipt.afterRevision = target_->revision();
96 receipt.authorityReceipt = "local:" + std::to_string(++receiptSequence_);
97 committed_.emplace(receipt.id, CommittedEntry{receipt, plan.operations});
98 commitOrder_.push_back(receipt.id);
99 return eve::editing::applied<TransactionReceipt>(std::move(receipt));
100}
101
103 if (!target_)
104 return eve::editing::failed<TransactionReceipt>(Status::Failed, RuleId("editor.authority.missing-target"),
105 "Local authority has no target");
106 auto entry = committed_.find(receipt.id);
107 if (entry == committed_.end())
108 return eve::editing::failed<TransactionReceipt>(Status::NotFound, RuleId("editor.authority.receipt-not-found"),
109 "Committed transaction is not available for compensation");
110 if (commitOrder_.empty() || commitOrder_.back() != receipt.id)
111 return eve::editing::failed<TransactionReceipt>(Status::Conflict, RuleId("editor.authority.compensation-order"),
112 "Only the latest committed transaction can be compensated");
113 for (const DomainOperation& operation : entry->second.operations) {
114 if (!operation.hasInverse)
115 return eve::editing::failed<TransactionReceipt>(Status::Unsupported,
116 RuleId("editor.authority.operation-not-reversible"),
117 "Transaction contains an operation without an inverse");
118 }
119
120 if (entry->second.receipt.afterRevision != target_->revision())
121 return eve::editing::failed<TransactionReceipt>(Status::Conflict, RuleId("editor.authority.revision-conflict"),
122 "Target changed after the committed transaction");
123
124 auto* staging = dynamic_cast<IDomainOperationTargetStaging*>(target_);
125 if (!staging)
126 return eve::editing::failed<TransactionReceipt>(Status::Unsupported,
127 RuleId("editor.authority.staging-unavailable"),
128 "Target cannot stage a complete compensation candidate");
129
130 TransactionReceipt compensation;
131 compensation.id = TransactionId(receipt.id.value() + ".undo." + std::to_string(++receiptSequence_));
133 compensation.beforeRevision = target_->revision();
134
135 const auto makeFailure = [&](Status status, std::string rule, std::string message) {
136 compensation.state = TransactionState::Failed;
137 compensation.afterRevision = target_->revision();
138 if (compensation.diagnostics.empty())
139 compensation.diagnostics.push_back(
141 DiagnosticSeverity::Error, std::move(message)));
143 };
144
145 std::unique_ptr<IDomainOperationTarget> candidate;
146 try {
147 candidate = staging->cloneDomainState();
148 } catch (const std::exception& exception) {
149 return makeFailure(Status::Failed, "editor.authority.candidate-exception",
150 std::string("Could not clone compensation candidate: ") + exception.what());
151 } catch (...) {
152 return makeFailure(Status::Failed, "editor.authority.candidate-exception",
153 "Could not clone compensation candidate");
154 }
155 if (!candidate)
156 return makeFailure(Status::Unsupported, "editor.authority.staging-unavailable",
157 "Target did not provide a compensation candidate");
158 if (candidate->targetId() != target_->targetId())
159 return makeFailure(Status::Conflict, "editor.authority.candidate-mismatch",
160 "Compensation candidate belongs to another target");
161
162 for (auto operation = entry->second.operations.rbegin(); operation != entry->second.operations.rend();
163 ++operation) {
164 try {
165 Result<void> result = candidate->applyDomainOperation(inverseOf(*operation));
166 if (!result.ok()) {
167 compensation.diagnostics = result.diagnostics();
168 return makeFailure(result.code(), "editor.authority.compensation-candidate-rejected",
169 "Compensation candidate rejected an inverse operation");
170 }
171 } catch (const std::exception& exception) {
172 compensation.diagnostics.push_back(ruleDiagnostic(
173 eve::DiagnosticCode::Failed, RuleId("editor.authority.candidate-exception"),
174 DiagnosticSeverity::Error,
175 std::string("Compensation candidate threw: ") + exception.what()));
176 return makeFailure(Status::Failed, "editor.authority.candidate-exception", "Compensation candidate threw");
177 } catch (...) {
178 compensation.diagnostics.push_back(ruleDiagnostic(
179 eve::DiagnosticCode::Failed, RuleId("editor.authority.candidate-exception"),
180 DiagnosticSeverity::Error, "Compensation candidate threw an unknown exception"));
181 return makeFailure(Status::Failed, "editor.authority.candidate-exception",
182 "Compensation candidate threw an unknown exception");
183 }
184 appendAffected(compensation, *operation);
185 }
186
187 try {
188 Result<void> published = staging->commitDomainState(std::move(candidate));
189 if (!published.ok()) {
190 compensation.diagnostics = published.diagnostics();
191 return makeFailure(published.code(), "editor.authority.candidate-publish-rejected",
192 "Target rejected the compensation candidate");
193 }
194 } catch (const std::exception& exception) {
195 return makeFailure(Status::Failed, "editor.authority.candidate-publish-exception",
196 std::string("Could not publish compensation candidate: ") + exception.what());
197 } catch (...) {
198 return makeFailure(Status::Failed, "editor.authority.candidate-publish-exception",
199 "Could not publish compensation candidate");
200 }
201 compensation.state = TransactionState::Committed;
202 compensation.afterRevision = target_->revision();
203 compensation.authorityReceipt = "local:" + std::to_string(receiptSequence_);
204 committed_.erase(entry);
205 commitOrder_.pop_back();
206 if (!commitOrder_.empty()) committed_.at(commitOrder_.back()).receipt.afterRevision = compensation.afterRevision;
207 return eve::editing::applied<TransactionReceipt>(std::move(compensation));
208}
209
210DomainOperation LocalWorldAuthority::inverseOf(const DomainOperation& operation) {
211 DomainOperation inverse = operation;
212 if (!operation.inverseType.empty()) inverse.type = operation.inverseType;
213 inverse.inverseType = operation.type;
214 inverse.payload = operation.inverse;
215 inverse.inverse = operation.payload;
216 return inverse;
217}
218
219Result<void> LocalWorldAuthority::rollbackApplied(std::span<const DomainOperation> operations,
220 std::size_t count) {
221 while (count > 0) {
222 const DomainOperation& operation = operations[--count];
223 if (!operation.hasInverse)
224 return eve::editing::failed<void>(Status::Failed, RuleId("editor.authority.rollback-impossible"),
225 "Applied operation has no inverse");
226 Result<void> result = target_->applyDomainOperation(inverseOf(operation));
227 if (!result.ok()) return result;
228 }
229 return eve::editing::applied<void>();
230}
231
233 std::span<const DomainOperation> operations) {
234 (void)transaction;
235 (void)operations;
236 return eve::editing::failed<AuthorityPlan>(Status::Rejected, RuleId("editor.authority.read-only"),
237 "The current editor authority is read-only");
238}
239
241 (void)plan;
242 return eve::editing::failed<TransactionReceipt>(Status::Rejected, RuleId("editor.authority.read-only"),
243 "The current editor authority is read-only");
244}
245
247 (void)receipt;
248 return eve::editing::failed<TransactionReceipt>(Status::Rejected, RuleId("editor.authority.read-only"),
249 "The current editor authority is read-only");
250}
251
252} // namespace eve::editing
ActionParameterOperation operation
std::string message
wgpu::PopErrorScopeStatus status
std::uint32_t count
Move-only operation result carrying either a value or Status.
Definition Result.h:155
const std::vector< Diagnostic > & diagnostics() const noexcept
Inspect all diagnostics; this counts as checking the Result.
Definition Result.h:283
bool ok() const noexcept
Whether this result represents a non-failure outcome.
Definition Result.h:255
StatusCode code() const noexcept
Inspect the stable operation code.
Definition Result.h:276
static Result failure(Status status)
Construct a failed result from a structured status.
Definition Result.h:175
Structured status and zero or more diagnostics for an operation.
Definition Status.h:68
const std::string & value() const noexcept
Returns the exact legacy or canonical compatibility spelling.
Definition Identity.h:362
bool empty() const noexcept
True when no compatibility spelling has been assigned.
Definition Identity.h:374
Optional candidate/publish capability for atomic domain operations.
virtual Result< void > applyDomainOperation(const DomainOperation &operation)=0
Apply one validated domain operation.
virtual TargetId targetId() const =0
Target id.
virtual std::uint64_t revision() const =0
Revision.
Result< AuthorityPlan > preflight(const TransactionSpec &transaction, std::span< const DomainOperation > operations) override
Preflight.
Result< TransactionReceipt > compensate(const TransactionReceipt &receipt) override
Compensate.
Result< TransactionReceipt > commit(const AuthorityPlan &plan) override
Commits commit.
Result< TransactionReceipt > compensate(const TransactionReceipt &receipt) override
Compensate.
Result< AuthorityPlan > preflight(const TransactionSpec &transaction, std::span< const DomainOperation > operations) override
Preflight.
Result< TransactionReceipt > commit(const AuthorityPlan &plan) override
Commits commit.
constexpr eve::DiagnosticCode diagnosticCodeForStatus(Status status) noexcept
Coarse default diagnostic category for a failure status.
StrongId< TransactionIdTag > TransactionId
Definition EditingIds.h:62
StrongId< RuleIdTag > RuleId
Definition EditingIds.h:60
Diagnostic ruleDiagnostic(eve::DiagnosticCode code, RuleId rule, DiagnosticSeverity severity, std::string message)
Build a common diagnostic carrying an open editing rule identity.
StatusCode
Stable outcome category for an operation.
Definition Status.h:27
AuthorityPlan public API.
std::vector< DomainOperation > operations
DomainOperation public API.
TransactionReceipt public API.
std::vector< Diagnostic > diagnostics
TransactionSpec public API.