generated from coulomb/repo-seed
Structured OperationFailure, BatchItemResult, and BatchOperationResult envelopes
This commit is contained in:
@@ -24,6 +24,130 @@ class Diagnostic:
|
||||
}
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class OperationFailure:
|
||||
"""Structured operation failure suitable for API and batch envelopes."""
|
||||
|
||||
code: str
|
||||
message: str
|
||||
operation: str
|
||||
correlation_id: str
|
||||
details: dict[str, Any] = field(default_factory=dict)
|
||||
remediation: str | None = None
|
||||
|
||||
def to_dict(self) -> dict[str, Any]:
|
||||
data: dict[str, Any] = {
|
||||
"code": self.code,
|
||||
"message": self.message,
|
||||
"operation": self.operation,
|
||||
"correlation_id": self.correlation_id,
|
||||
"details": dict(self.details),
|
||||
}
|
||||
if self.remediation:
|
||||
data["remediation"] = self.remediation
|
||||
return data
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class BatchItemResult:
|
||||
"""One item result inside a batch operation envelope."""
|
||||
|
||||
item_id: str
|
||||
operation: str
|
||||
success: bool
|
||||
result_ref: dict[str, Any] = field(default_factory=dict)
|
||||
error: OperationFailure | None = None
|
||||
|
||||
@classmethod
|
||||
def succeeded(
|
||||
cls,
|
||||
*,
|
||||
item_id: str,
|
||||
operation: str,
|
||||
result_ref: dict[str, Any] | None = None,
|
||||
) -> "BatchItemResult":
|
||||
return cls(
|
||||
item_id=item_id,
|
||||
operation=operation,
|
||||
success=True,
|
||||
result_ref=dict(result_ref or {}),
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def failed(
|
||||
cls,
|
||||
*,
|
||||
item_id: str,
|
||||
operation: str,
|
||||
error: OperationFailure,
|
||||
) -> "BatchItemResult":
|
||||
return cls(item_id=item_id, operation=operation, success=False, error=error)
|
||||
|
||||
def to_dict(self) -> dict[str, Any]:
|
||||
data: dict[str, Any] = {
|
||||
"item_id": self.item_id,
|
||||
"operation": self.operation,
|
||||
"success": self.success,
|
||||
}
|
||||
if self.result_ref:
|
||||
data["result_ref"] = dict(self.result_ref)
|
||||
if self.error:
|
||||
data["error"] = self.error.to_dict()
|
||||
return data
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class BatchOperationResult:
|
||||
"""Compact result envelope for batch operations with partial failures."""
|
||||
|
||||
operation: str
|
||||
correlation_id: str
|
||||
items: tuple[BatchItemResult, ...] = ()
|
||||
audit_event_id: str | None = None
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
object.__setattr__(self, "items", tuple(self.items))
|
||||
|
||||
@property
|
||||
def total(self) -> int:
|
||||
return len(self.items)
|
||||
|
||||
@property
|
||||
def succeeded(self) -> int:
|
||||
return sum(1 for item in self.items if item.success)
|
||||
|
||||
@property
|
||||
def failed(self) -> int:
|
||||
return self.total - self.succeeded
|
||||
|
||||
@property
|
||||
def partial(self) -> bool:
|
||||
return self.succeeded > 0 and self.failed > 0
|
||||
|
||||
@property
|
||||
def outcome(self) -> str:
|
||||
if self.partial:
|
||||
return "partial"
|
||||
if self.failed:
|
||||
return "failed"
|
||||
return "success"
|
||||
|
||||
def to_dict(self) -> dict[str, Any]:
|
||||
data: dict[str, Any] = {
|
||||
"operation": self.operation,
|
||||
"correlation_id": self.correlation_id,
|
||||
"outcome": self.outcome,
|
||||
"total": self.total,
|
||||
"succeeded": self.succeeded,
|
||||
"failed": self.failed,
|
||||
"partial": self.partial,
|
||||
"items": [item.to_dict() for item in self.items],
|
||||
}
|
||||
if self.audit_event_id:
|
||||
data["audit_event_id"] = self.audit_event_id
|
||||
return data
|
||||
|
||||
|
||||
class KontextualError(Exception):
|
||||
"""Base class for explicit engine failures."""
|
||||
|
||||
@@ -41,6 +165,22 @@ class KontextualError(Exception):
|
||||
details=dict(self.details),
|
||||
)
|
||||
|
||||
def to_operation_failure(
|
||||
self,
|
||||
*,
|
||||
operation: str,
|
||||
correlation_id: str,
|
||||
remediation: str | None = None,
|
||||
) -> OperationFailure:
|
||||
return OperationFailure(
|
||||
code=str(self.details.get("code") or self.code),
|
||||
message=str(self),
|
||||
operation=operation,
|
||||
correlation_id=correlation_id,
|
||||
details=dict(self.details),
|
||||
remediation=remediation,
|
||||
)
|
||||
|
||||
|
||||
class NotFoundError(KontextualError):
|
||||
code = "kontextual.not_found"
|
||||
|
||||
Reference in New Issue
Block a user