fix: align protocol error semantics

This commit is contained in:
2026-09-30 05:26:34 +09:00
parent 75119f23e5
commit 027555eb4e
9 changed files with 323 additions and 47 deletions
+17 -7
View File
@@ -74,8 +74,9 @@ the distinct domain, host, authorization, validator-recovery, and interface
failure classes. Exhaustive public type construction and matching make legacy failure classes. Exhaustive public type construction and matching make legacy
ObjectId, descriptor `name`, and semantic `Value` variants compile-time ObjectId, descriptor `name`, and semantic `Value` variants compile-time
regressions. Unit tests in the public crates additionally cover every host regressions. Unit tests in the public crates additionally cover every host
dispatch failure, invalid arguments and results, interface membership, dispatch failure, `InvalidRequest` versus descriptor-bound `InvalidArguments`,
validators, and same-named operations. invalid local responses, validators, same-named operations, and exhaustive
per-interaction protocol error-code sets.
## Interface authoring ## Interface authoring
@@ -200,11 +201,20 @@ are never projected by `WipObject`; host-private fields and backend handles are
also excluded. also excluded.
Application domain failures belong in a derived result union and are returned Application domain failures belong in a derived result union and are returned
through `Ok`. `wip::HostError` is reserved for runtime/backend failures. Invalid through `Ok`. `wip::HostError` is reserved for runtime/backend failures. A
arguments are rejected before invocation; malformed encoded results are structurally malformed call is `InvalidRequest`; only argument values checked
reported as `InvalidResult` and never returned as application data. Validator, after the current Interface Descriptor is fixed are `InvalidArguments`. A Host
membership, authorization, and result failures retain their distinct canonical must never return a wire `InvalidResult`: Clients keep invalid Object,
protocol error codes. Descriptor, operation-result, and error responses as local `InvalidResponse`
failures, while unsupported descriptor formats remain local
`UnsupportedDescriptorFormat` failures.
For operation calls, `Internal` and `ResourceLimitExceeded` guarantee that the
operation did not run or that its effects were rolled back.
`OperationOutcomeUnknown` is the only protocol code that makes no such
guarantee, so Clients must not automatically retry it. The current host API maps
implementation failures and invalid post-execution results conservatively to
`OperationOutcomeUnknown`.
Providers remain responsible for backend atomicity. Enter a transaction, lock, Providers remain responsible for backend atomicity. Enter a transaction, lock,
stable-handle scope, or conditional-update primitive before invoking the stable-handle scope, or conditional-update primitive before invoking the
+10 -6
View File
@@ -545,9 +545,9 @@ pub trait ObjectProvider {
self.with_object(path, PublishedObject::protocol_object) self.with_object(path, PublishedObject::protocol_object)
.ok_or_else(|| CallError::new(CallErrorKind::NotFound, "target path is not published")) .ok_or_else(|| CallError::new(CallErrorKind::NotFound, "target path is not published"))
.and_then(|object| { .and_then(|object| {
object.validate_at_path(path).map_err(|error| { object
CallError::new(CallErrorKind::InvalidResult, error.to_string()) .validate_at_path(path)
})?; .map_err(|error| CallError::new(CallErrorKind::Internal, error.to_string()))?;
Ok(object) Ok(object)
}) })
} }
@@ -607,7 +607,9 @@ pub enum CallErrorKind {
OperationNotFound, OperationNotFound,
/// Supplied named values do not satisfy the operation declaration. /// Supplied named values do not satisfy the operation declaration.
InvalidArguments, InvalidArguments,
/// The encoded implementation result violates its declaration. /// The Host constructed an invalid response before operation execution.
Internal,
/// The encoded implementation result violates its declaration after execution.
InvalidResult, InvalidResult,
/// This operation requires a validator and the call omitted it. /// This operation requires a validator and the call omitted it.
ValidatorRequired, ValidatorRequired,
@@ -683,7 +685,10 @@ impl CallError {
CallErrorKind::InterfaceMismatch => ProtocolErrorCode::InterfaceMismatch, CallErrorKind::InterfaceMismatch => ProtocolErrorCode::InterfaceMismatch,
CallErrorKind::OperationNotFound => ProtocolErrorCode::OperationNotFound, CallErrorKind::OperationNotFound => ProtocolErrorCode::OperationNotFound,
CallErrorKind::InvalidArguments => ProtocolErrorCode::InvalidArguments, CallErrorKind::InvalidArguments => ProtocolErrorCode::InvalidArguments,
CallErrorKind::InvalidResult => ProtocolErrorCode::InvalidResult, CallErrorKind::Internal => ProtocolErrorCode::Internal,
CallErrorKind::InvalidResult | CallErrorKind::Implementation => {
ProtocolErrorCode::OperationOutcomeUnknown
}
CallErrorKind::ValidatorRequired => ProtocolErrorCode::ValidatorRequired, CallErrorKind::ValidatorRequired => ProtocolErrorCode::ValidatorRequired,
CallErrorKind::ValidatorMismatch => ProtocolErrorCode::ValidatorMismatch, CallErrorKind::ValidatorMismatch => ProtocolErrorCode::ValidatorMismatch,
CallErrorKind::InterfaceValidatorRequired => { CallErrorKind::InterfaceValidatorRequired => {
@@ -693,7 +698,6 @@ impl CallError {
ProtocolErrorCode::InterfaceValidatorMismatch ProtocolErrorCode::InterfaceValidatorMismatch
} }
CallErrorKind::PermissionDenied => ProtocolErrorCode::PermissionDenied, CallErrorKind::PermissionDenied => ProtocolErrorCode::PermissionDenied,
CallErrorKind::Implementation => ProtocolErrorCode::Internal,
}; };
ProtocolError { ProtocolError {
code, code,
+13 -3
View File
@@ -235,6 +235,10 @@ impl ObjectProvider for TestProvider {
) )
} }
"/empty" => (Document::open("empty", None, Rc::clone(&self.calls)), 0), "/empty" => (Document::open("empty", None, Rc::clone(&self.calls)), 0),
"/invalid-object" => (
Document::open("wrong-name", None, Rc::clone(&self.calls)),
0,
),
"/broken" => ( "/broken" => (
Document::open("broken", Some("broken-ref"), Rc::clone(&self.calls)), Document::open("broken", Some("broken-ref"), Rc::clone(&self.calls)),
3, 3,
@@ -624,9 +628,15 @@ fn immutable_interface_rejects_a_supplied_validator() {
} }
#[test] #[test]
fn invalid_results_and_backend_failures_keep_distinct_protocol_codes() { fn invalid_host_responses_and_uncertain_operation_failures_use_canonical_codes() {
let (_, provider, _) = registry_and_provider(); let (_, provider, _) = registry_and_provider();
assert_failure(
provider.fetch("/invalid-object").unwrap_err(),
CallErrorKind::Internal,
ProtocolErrorCode::Internal,
);
assert_failure( assert_failure(
provider provider
.call_operation(request( .call_operation(request(
@@ -639,7 +649,7 @@ fn invalid_results_and_backend_failures_keep_distinct_protocol_codes() {
)) ))
.unwrap_err(), .unwrap_err(),
CallErrorKind::InvalidResult, CallErrorKind::InvalidResult,
ProtocolErrorCode::InvalidResult, ProtocolErrorCode::OperationOutcomeUnknown,
); );
assert_failure( assert_failure(
provider provider
@@ -653,6 +663,6 @@ fn invalid_results_and_backend_failures_keep_distinct_protocol_codes() {
)) ))
.unwrap_err(), .unwrap_err(),
CallErrorKind::Implementation, CallErrorKind::Implementation,
ProtocolErrorCode::Internal, ProtocolErrorCode::OperationOutcomeUnknown,
); );
} }
+102 -6
View File
@@ -223,10 +223,29 @@ impl Display for ValidationError {
impl Error for ValidationError {} impl Error for ValidationError {}
/// Stable protocol failure code returned by a host or produced by a client. /// One core interaction whose failure codes form a closed set.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ProtocolInteraction {
/// Fetch one object by its Worldspace path.
Fetch,
/// Fetch a bounded tree of objects.
FetchTree,
/// Fetch one interface descriptor by its opaque reference.
FetchInterface,
/// Invoke one operation on a path-targeted object.
CallOperation,
}
/// Stable protocol failure code returned by a host.
///
/// This is the complete wire-level code set. A Client treats a malformed success
/// or failure response, including a code disallowed for its interaction, as a
/// Client-local `InvalidResponse`. An unsupported descriptor format is likewise
/// a Client-local `UnsupportedDescriptorFormat`; neither condition is a code a
/// Host can return.
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ProtocolErrorCode { pub enum ProtocolErrorCode {
/// The request is structurally invalid. /// The request is structurally invalid before an operation descriptor is fixed.
InvalidRequest, InvalidRequest,
/// The target object is not published. /// The target object is not published.
NotFound, NotFound,
@@ -236,10 +255,8 @@ pub enum ProtocolErrorCode {
InterfaceMismatch, InterfaceMismatch,
/// The selected operation is not declared by the current interface. /// The selected operation is not declared by the current interface.
OperationNotFound, OperationNotFound,
/// Supplied operation arguments are invalid. /// Supplied arguments do not match the fixed operation declaration.
InvalidArguments, InvalidArguments,
/// A host result does not satisfy the declaration.
InvalidResult,
/// The operation requires an object validator and none was supplied. /// The operation requires an object validator and none was supplied.
ValidatorRequired, ValidatorRequired,
/// The supplied object validator does not match current state. /// The supplied object validator does not match current state.
@@ -251,9 +268,88 @@ pub enum ProtocolErrorCode {
/// A published operation is not permitted for the caller. /// A published operation is not permitted for the caller.
PermissionDenied, PermissionDenied,
/// A bounded protocol resource limit was exceeded. /// A bounded protocol resource limit was exceeded.
///
/// For `call_operation`, the Host guarantees that the operation was not run
/// or that all of its effects were rolled back.
ResourceLimitExceeded, ResourceLimitExceeded,
/// The host encountered an internal failure. /// The Host encountered an internal failure.
///
/// For `call_operation`, the Host guarantees that the operation was not run
/// or that all of its effects were rolled back.
Internal, Internal,
/// The Host cannot guarantee that the operation was not run or rolled back.
///
/// A Client must not automatically retry because the operation may have
/// committed effects even though no success response was available.
OperationOutcomeUnknown,
}
impl ProtocolErrorCode {
/// Every canonical wire-level error code.
pub const ALL: [Self; 14] = [
Self::InvalidRequest,
Self::NotFound,
Self::InterfaceNotFound,
Self::InterfaceMismatch,
Self::OperationNotFound,
Self::InvalidArguments,
Self::ValidatorRequired,
Self::ValidatorMismatch,
Self::InterfaceValidatorRequired,
Self::InterfaceValidatorMismatch,
Self::PermissionDenied,
Self::ResourceLimitExceeded,
Self::Internal,
Self::OperationOutcomeUnknown,
];
/// Whether a Host may return this code for `interaction`.
///
/// `false` identifies an invalid error response; it does not turn that
/// response into another wire-level protocol failure.
#[must_use]
pub const fn is_allowed_for(self, interaction: ProtocolInteraction) -> bool {
use ProtocolErrorCode::{
InterfaceMismatch, InterfaceNotFound, InterfaceValidatorMismatch,
InterfaceValidatorRequired, Internal, InvalidArguments, InvalidRequest, NotFound,
OperationNotFound, OperationOutcomeUnknown, PermissionDenied, ResourceLimitExceeded,
ValidatorMismatch, ValidatorRequired,
};
use ProtocolInteraction::{CallOperation, Fetch, FetchInterface, FetchTree};
match (interaction, self) {
(_, InvalidRequest | ResourceLimitExceeded | Internal) => true,
(Fetch | FetchTree, NotFound) => true,
(FetchInterface, InterfaceNotFound) => true,
(
CallOperation,
NotFound
| InterfaceMismatch
| OperationNotFound
| InvalidArguments
| ValidatorRequired
| ValidatorMismatch
| InterfaceValidatorRequired
| InterfaceValidatorMismatch
| PermissionDenied
| OperationOutcomeUnknown,
) => true,
(
_,
NotFound
| InterfaceNotFound
| InterfaceMismatch
| OperationNotFound
| InvalidArguments
| ValidatorRequired
| ValidatorMismatch
| InterfaceValidatorRequired
| InterfaceValidatorMismatch
| PermissionDenied
| OperationOutcomeUnknown,
) => false,
}
}
} }
/// Transport-independent protocol failure. /// Transport-independent protocol failure.
+16 -3
View File
@@ -16,7 +16,20 @@
//! | `fetch_interface` | [`FetchInterfaceRequest`] / [`FetchInterfaceResponse`] | //! | `fetch_interface` | [`FetchInterfaceRequest`] / [`FetchInterfaceResponse`] |
//! | Object and interface call preconditions | [`Target`] / [`InterfaceTarget`] | //! | Object and interface call preconditions | [`Target`] / [`InterfaceTarget`] |
//! | `call_operation` | [`CallOperationRequest`] / [`CallOperationResponse`] | //! | `call_operation` | [`CallOperationRequest`] / [`CallOperationResponse`] |
//! | Stable failure taxonomy | [`ProtocolErrorCode`] / [`ProtocolError`] | //! | Stable Host failure taxonomy | [`ProtocolErrorCode`] / [`ProtocolError`] |
//! | Per-interaction allowed failure codes | [`ProtocolInteraction`] / [`ProtocolErrorCode::is_allowed_for`] |
//!
//! [`ProtocolErrorCode`] is a closed set of Host-returned wire failures. After a
//! Client receives a response, an invalid Object, Descriptor, operation result,
//! malformed error, or error code disallowed by [`ProtocolInteraction`] is a
//! Client-local `InvalidResponse`, not another wire error. A descriptor whose
//! `format` is not supported is a Client-local `UnsupportedDescriptorFormat`.
//!
//! For `call_operation`, [`ProtocolErrorCode::Internal`] and
//! [`ProtocolErrorCode::ResourceLimitExceeded`] guarantee that the operation did
//! not run or that all effects were rolled back. Only
//! [`ProtocolErrorCode::OperationOutcomeUnknown`] means the Host cannot make that
//! guarantee; Clients must not automatically retry it.
//! //!
//! Paths, object refs, interface refs, and validators remain separate concepts. //! Paths, object refs, interface refs, and validators remain separate concepts.
//! They are represented by ordinary protocol strings/bytes in the corresponding //! They are represented by ordinary protocol strings/bytes in the corresponding
@@ -35,8 +48,8 @@ mod model;
mod validation; mod validation;
pub use error::{ pub use error::{
NameNamespace, PathSegment, ProtocolError, ProtocolErrorCode, ValidationError, NameNamespace, PathSegment, ProtocolError, ProtocolErrorCode, ProtocolInteraction,
ValidationErrorKind, ValueKind, ValidationError, ValidationErrorKind, ValueKind,
}; };
pub use model::{ pub use model::{
CallOperationRequest, CallOperationResponse, Documentation, EnumCase, FetchInterfaceRequest, CallOperationRequest, CallOperationResponse, Documentation, EnumCase, FetchInterfaceRequest,
+14
View File
@@ -379,6 +379,9 @@ impl FetchRequest {
} }
/// Validates an object returned for this request. /// Validates an object returned for this request.
///
/// Failure means the Client received an invalid success response; it is not a
/// Host-returned [`crate::ProtocolErrorCode`].
pub fn validate_response(&self, response: &FetchResponse) -> Result<(), ValidationError> { pub fn validate_response(&self, response: &FetchResponse) -> Result<(), ValidationError> {
response.validate_at_path(&self.path) response.validate_at_path(&self.path)
} }
@@ -393,6 +396,9 @@ impl FetchTreeRequest {
impl FetchTreeResponse { impl FetchTreeResponse {
/// Validates tree shape, names, depth, and node limit for `request`. /// Validates tree shape, names, depth, and node limit for `request`.
///
/// Failure means the Client received an invalid success response; it is not a
/// Host-returned [`crate::ProtocolErrorCode`].
pub fn validate_for(&self, request: &FetchTreeRequest) -> Result<(), ValidationError> { pub fn validate_for(&self, request: &FetchTreeRequest) -> Result<(), ValidationError> {
crate::validation::validate_fetch_tree_response(self, request) crate::validation::validate_fetch_tree_response(self, request)
} }
@@ -400,6 +406,11 @@ impl FetchTreeResponse {
impl FetchInterfaceResponse { impl FetchInterfaceResponse {
/// Validates request correspondence and descriptor semantics. /// Validates request correspondence and descriptor semantics.
///
/// Failure means the Client received an invalid success response; it is not a
/// Host-returned [`crate::ProtocolErrorCode`]. Descriptor format support is a
/// separate Client concern: an otherwise valid descriptor with an unsupported
/// `format` is a Client-local `UnsupportedDescriptorFormat`.
pub fn validate_for(&self, request: &FetchInterfaceRequest) -> Result<(), ValidationError> { pub fn validate_for(&self, request: &FetchInterfaceRequest) -> Result<(), ValidationError> {
crate::validation::validate_fetch_interface_response(self, request) crate::validation::validate_fetch_interface_response(self, request)
} }
@@ -414,6 +425,9 @@ impl CallOperationRequest {
impl CallOperationResponse { impl CallOperationResponse {
/// Validates this result against the named operation in `descriptor`. /// Validates this result against the named operation in `descriptor`.
///
/// Failure means the Client received an invalid success response; it is not a
/// Host-returned [`crate::ProtocolErrorCode`].
pub fn validate_for( pub fn validate_for(
&self, &self,
descriptor: &InterfaceDescriptor, descriptor: &InterfaceDescriptor,
+146 -20
View File
@@ -5,8 +5,8 @@ use wip_protocol::{
FetchInterfaceResponse, FetchRequest, FetchTreeRequest, FetchTreeResponse, FieldDeclaration, FetchInterfaceResponse, FetchRequest, FetchTreeRequest, FetchTreeResponse, FieldDeclaration,
INTERFACE_FORMAT_V1, InterfaceDescriptor, InterfaceTarget, MAX_SAFE_INTEGER, NameNamespace, INTERFACE_FORMAT_V1, InterfaceDescriptor, InterfaceTarget, MAX_SAFE_INTEGER, NameNamespace,
Object, ObjectTreeNode, OperationDeclaration, ParameterDeclaration, PathSegment, ProtocolError, Object, ObjectTreeNode, OperationDeclaration, ParameterDeclaration, PathSegment, ProtocolError,
ProtocolErrorCode, ProtocolResult, ReturnDeclaration, Target, TypeDeclaration, TypeExpr, ProtocolErrorCode, ProtocolInteraction, ProtocolResult, ReturnDeclaration, Target,
UnionCase, ValidationErrorKind, Value, ValueKind, TypeDeclaration, TypeExpr, UnionCase, ValidationErrorKind, Value, ValueKind,
}; };
fn record(entries: impl IntoIterator<Item = (&'static str, Value)>) -> Value { fn record(entries: impl IntoIterator<Item = (&'static str, Value)>) -> Value {
@@ -729,24 +729,26 @@ fn fetch_tree_dtos_validate_names_depth_limit_and_siblings() {
} }
#[test] #[test]
fn protocol_result_and_every_canonical_error_code_are_available() { fn protocol_result_exposes_the_exact_canonical_wire_error_codes() {
let codes = [ assert_eq!(
ProtocolErrorCode::InvalidRequest, ProtocolErrorCode::ALL,
ProtocolErrorCode::NotFound, [
ProtocolErrorCode::InterfaceNotFound, ProtocolErrorCode::InvalidRequest,
ProtocolErrorCode::InterfaceMismatch, ProtocolErrorCode::NotFound,
ProtocolErrorCode::OperationNotFound, ProtocolErrorCode::InterfaceNotFound,
ProtocolErrorCode::InvalidArguments, ProtocolErrorCode::InterfaceMismatch,
ProtocolErrorCode::InvalidResult, ProtocolErrorCode::OperationNotFound,
ProtocolErrorCode::ValidatorRequired, ProtocolErrorCode::InvalidArguments,
ProtocolErrorCode::ValidatorMismatch, ProtocolErrorCode::ValidatorRequired,
ProtocolErrorCode::InterfaceValidatorRequired, ProtocolErrorCode::ValidatorMismatch,
ProtocolErrorCode::InterfaceValidatorMismatch, ProtocolErrorCode::InterfaceValidatorRequired,
ProtocolErrorCode::PermissionDenied, ProtocolErrorCode::InterfaceValidatorMismatch,
ProtocolErrorCode::ResourceLimitExceeded, ProtocolErrorCode::PermissionDenied,
ProtocolErrorCode::Internal, ProtocolErrorCode::ResourceLimitExceeded,
]; ProtocolErrorCode::Internal,
assert_eq!(codes.len(), 14); ProtocolErrorCode::OperationOutcomeUnknown,
]
);
let failure: ProtocolResult<()> = ProtocolResult::Failure(ProtocolError { let failure: ProtocolResult<()> = ProtocolResult::Failure(ProtocolError {
code: ProtocolErrorCode::InvalidArguments, code: ProtocolErrorCode::InvalidArguments,
@@ -758,3 +760,127 @@ fn protocol_result_and_every_canonical_error_code_are_available() {
ProtocolResult::Success(Value::Unit) ProtocolResult::Success(Value::Unit)
); );
} }
fn assert_allowed_codes(interaction: ProtocolInteraction, expected: &[ProtocolErrorCode]) {
let actual: Vec<_> = ProtocolErrorCode::ALL
.into_iter()
.filter(|code| code.is_allowed_for(interaction))
.collect();
assert_eq!(actual, expected);
for code in ProtocolErrorCode::ALL {
assert_eq!(
code.is_allowed_for(interaction),
expected.contains(&code),
"unexpected {code:?} validity for {interaction:?}"
);
}
}
#[test]
fn fetch_error_codes_are_exhaustive() {
assert_allowed_codes(
ProtocolInteraction::Fetch,
&[
ProtocolErrorCode::InvalidRequest,
ProtocolErrorCode::NotFound,
ProtocolErrorCode::ResourceLimitExceeded,
ProtocolErrorCode::Internal,
],
);
}
#[test]
fn fetch_tree_error_codes_are_exhaustive() {
assert_allowed_codes(
ProtocolInteraction::FetchTree,
&[
ProtocolErrorCode::InvalidRequest,
ProtocolErrorCode::NotFound,
ProtocolErrorCode::ResourceLimitExceeded,
ProtocolErrorCode::Internal,
],
);
}
#[test]
fn fetch_interface_error_codes_are_exhaustive() {
assert_allowed_codes(
ProtocolInteraction::FetchInterface,
&[
ProtocolErrorCode::InvalidRequest,
ProtocolErrorCode::InterfaceNotFound,
ProtocolErrorCode::ResourceLimitExceeded,
ProtocolErrorCode::Internal,
],
);
}
#[test]
fn call_operation_error_codes_are_exhaustive() {
assert_allowed_codes(
ProtocolInteraction::CallOperation,
&[
ProtocolErrorCode::InvalidRequest,
ProtocolErrorCode::NotFound,
ProtocolErrorCode::InterfaceMismatch,
ProtocolErrorCode::OperationNotFound,
ProtocolErrorCode::InvalidArguments,
ProtocolErrorCode::ValidatorRequired,
ProtocolErrorCode::ValidatorMismatch,
ProtocolErrorCode::InterfaceValidatorRequired,
ProtocolErrorCode::InterfaceValidatorMismatch,
ProtocolErrorCode::PermissionDenied,
ProtocolErrorCode::ResourceLimitExceeded,
ProtocolErrorCode::Internal,
ProtocolErrorCode::OperationOutcomeUnknown,
],
);
}
#[test]
fn invalid_success_payloads_remain_client_local_validation_failures() {
let fetch = FetchRequest {
path: "/articles/one".into(),
};
assert!(matches!(
fetch.validate_response(&object("other")).unwrap_err().kind,
ValidationErrorKind::ObjectNameMismatch { .. }
));
let interface_request = FetchInterfaceRequest {
interface: "catalog-v1".into(),
};
let mut invalid_descriptor = descriptor();
invalid_descriptor
.operations
.push(invalid_descriptor.operations[0].clone());
assert!(matches!(
FetchInterfaceResponse {
interface: interface_request.interface.clone(),
descriptor: invalid_descriptor,
validator: None,
}
.validate_for(&interface_request)
.unwrap_err()
.kind,
ValidationErrorKind::DuplicateName {
namespace: NameNamespace::Operation,
..
}
));
assert!(matches!(
CallOperationResponse {
result: Value::String("not a boolean".into()),
validator: None,
}
.validate_for(&descriptor(), "publish")
.unwrap_err()
.kind,
ValidationErrorKind::TypeMismatch {
expected: ValueKind::Boolean,
actual: ValueKind::String,
}
));
}
+1 -1
View File
@@ -150,7 +150,7 @@ pub struct DemoReport {
pub audit_status: String, pub audit_status: String,
/// Stable failure kind produced by the deliberate host/backend failure. /// Stable failure kind produced by the deliberate host/backend failure.
pub host_failure: CallErrorKind, pub host_failure: CallErrorKind,
/// A protocol-level failure built from the host failure. /// A protocol failure whose operation effects cannot be guaranteed absent.
pub protocol_failure: ProtocolError, pub protocol_failure: ProtocolError,
/// Authorization failure after a fully qualified valid request. /// Authorization failure after a fully qualified valid request.
pub permission_failure: ProtocolErrorCode, pub permission_failure: ProtocolErrorCode,
@@ -50,7 +50,10 @@ fn domain_host_permission_and_validator_failures_remain_distinct() {
assert!(matches!(report.rejected, PublishOutcome::Rejected(_))); assert!(matches!(report.rejected, PublishOutcome::Rejected(_)));
assert_eq!(report.host_failure, CallErrorKind::Implementation); assert_eq!(report.host_failure, CallErrorKind::Implementation);
assert_eq!(report.protocol_failure.code, ProtocolErrorCode::Internal); assert_eq!(
report.protocol_failure.code,
ProtocolErrorCode::OperationOutcomeUnknown
);
assert_eq!( assert_eq!(
report.permission_failure, report.permission_failure,
ProtocolErrorCode::PermissionDenied ProtocolErrorCode::PermissionDenied