///|
struct AppliedRequest {
idempotency_key : String
request_fingerprint : String
receipt : AdapterReceipt
}
///|
pub struct MockBackend {
mut scene_digest : String
applied : Array[AppliedRequest]
}
///|
pub fn MockBackend::new(
initial_digest? : String = "mm1-empty-scene",
) -> MockBackend {
{ scene_digest: initial_digest, applied: [] }
}
///|
pub fn MockBackend::scene_digest(self : MockBackend) -> String {
self.scene_digest
}
///|
fn operation_fingerprint(operation : SemanticOperation) -> String {
deterministic_manifest_key(to_repr(operation).to_string())
}
///|
fn changed_objects(operation : SemanticOperation) -> Array[String] {
match operation {
CreateModel(..) | ValidateScene | ExportRepresentation(..) => []
CreateBox(object_id~, ..)
| CreateCylinder(object_id~, ..)
| SetTransform(object_id~, ..)
| AssignMaterial(object_id~, ..) => [object_id]
BooleanSubtract(target_id~, cutter_id~) => [target_id, cutter_id]
}
}
///|
fn accepted_evidence(subject_digest : String) -> ValidationEvidence {
{
validator: "moonmold-mock-validator",
validator_version: "1",
subject_digest,
accepted: true,
findings: [],
}
}
///|
pub fn MockBackend::execute(
self : MockBackend,
request : AdapterRequest,
) -> AdapterReceipt raise AdapterError {
validate_request(request)
let fingerprint = operation_fingerprint(request.operation)
for prior in self.applied {
if prior.idempotency_key == request.idempotency_key {
if prior.request_fingerprint != fingerprint {
raise ConflictingIdempotencyKey(request.idempotency_key)
}
let old = prior.receipt
return {
request_id: request.request_id,
idempotency_key: request.idempotency_key,
outcome: IdempotentNoOp,
before_digest: old.after_digest,
after_digest: old.after_digest,
changed_objects: [],
warnings: ["identical completed request was not applied twice"],
evidence: old.evidence,
}
}
}
if request.expected_parent_digest != self.scene_digest {
raise StaleParent(
expected=request.expected_parent_digest,
actual=self.scene_digest,
)
}
let before = self.scene_digest
let after = deterministic_manifest_key(
"scene-v1\nparent=\{before}\noperation=\{to_repr(request.operation)}\n",
)
let receipt : AdapterReceipt = {
request_id: request.request_id,
idempotency_key: request.idempotency_key,
outcome: Applied,
before_digest: before,
after_digest: after,
changed_objects: changed_objects(request.operation),
warnings: [],
evidence: accepted_evidence(after),
}
self.scene_digest = after
self.applied.push({
idempotency_key: request.idempotency_key,
request_fingerprint: fingerprint,
receipt,
})
receipt
}