use connectrpc::{ConnectError, ErrorCode, ErrorDetail};
use polyc_proto::proto::polychrome::state::v1 as pb;
use polyc_state::{
error::StateError,
immutable::ObjectError,
projection::{
ProjectionCatalogError, ProjectionGeneration, ProjectionKey,
artifact::{ArtifactAdmissionError, ManifestVerdict},
},
};
use crate::{
error::{TransportFallback, decode_detail, from_connect_error as state_from_connect},
wire::Kernel,
};
pub const PROJECTION_ERROR_DETAIL_TYPE: &str = "polychrome.state.v1.ProjectionCatalogErrorDetail";
fn key(
value: &ProjectionKey,
) -> buffa::MessageField<pb::ProjectionKey, buffa::Inline<pb::ProjectionKey>> {
buffa::MessageField::some(pb::ProjectionKey::from(Kernel(value)))
}
impl From<Kernel<&ProjectionCatalogError>> for pb::ProjectionCatalogErrorDetail {
fn from(value: Kernel<&ProjectionCatalogError>) -> Self {
use pb::__buffa::oneof::projection_catalog_error_detail::Outcome;
let unknown = buffa::UnknownFields::default;
let outcome = match value.0 {
ProjectionCatalogError::State(error) => {
Outcome::from(pb::StateErrorDetail::from(Kernel(error)))
}
ProjectionCatalogError::Object(error) => {
Outcome::from(pb::ObjectErrorDetail::from(Kernel(error.as_ref())))
}
ProjectionCatalogError::StalePublisher { key: projection } => {
Outcome::StalePublisher(Box::new(pb::ProjectionKeyDetail {
key: key(projection),
__buffa_unknown_fields: unknown(),
}))
}
ProjectionCatalogError::GenerationConflict {
key: projection,
expected,
current,
} => Outcome::from(pb::ProjectionGenerationConflictDetail {
key: key(projection),
expected: expected.get(),
current: current.get(),
__buffa_unknown_fields: unknown(),
}),
ProjectionCatalogError::NonMonotonic {
key: projection,
current,
requested,
} => Outcome::from(pb::ProjectionNonMonotonicDetail {
key: key(projection),
current: current.get(),
requested: requested.get(),
__buffa_unknown_fields: unknown(),
}),
ProjectionCatalogError::CursorRegression { key: projection } => {
Outcome::CursorRegression(Box::new(pb::ProjectionKeyDetail {
key: key(projection),
__buffa_unknown_fields: unknown(),
}))
}
ProjectionCatalogError::UnknownManifest {
key: projection,
generation,
} => Outcome::UnknownManifest(Box::new(pb::ProjectionManifestDetail {
key: key(projection),
generation: generation.get(),
__buffa_unknown_fields: unknown(),
})),
ProjectionCatalogError::ManifestConflict {
key: projection,
generation,
} => Outcome::ManifestConflict(Box::new(pb::ProjectionManifestDetail {
key: key(projection),
generation: generation.get(),
__buffa_unknown_fields: unknown(),
})),
ProjectionCatalogError::Artifact(error) => match error.as_ref() {
ArtifactAdmissionError::Malformed(error) => {
Outcome::from(pb::StateErrorDetail::from(Kernel(error)))
}
error => Outcome::from(artifact_detail(error)),
},
};
Self {
outcome: Some(outcome),
__buffa_unknown_fields: unknown(),
}
}
}
fn artifact_detail(value: &ArtifactAdmissionError) -> pb::ProjectionArtifactDetail {
use pb::__buffa::oneof::projection_artifact_detail::Refusal;
let unknown = buffa::UnknownFields::default;
let refusal = match value {
ArtifactAdmissionError::Malformed(_) => {
unreachable!("malformed artifacts use the State error detail")
}
ArtifactAdmissionError::ContentMismatch { object } => {
Refusal::from(pb::ProjectionArtifactContentMismatchDetail {
object: object.clone(),
__buffa_unknown_fields: unknown(),
})
}
ArtifactAdmissionError::Signature { verdict } => {
Refusal::from(pb::ProjectionArtifactSignatureDetail {
verdict: verdict_of(*verdict).into(),
__buffa_unknown_fields: unknown(),
})
}
ArtifactAdmissionError::Disagreement { field } => {
Refusal::from(pb::ProjectionArtifactDisagreementDetail {
field: field.clone(),
__buffa_unknown_fields: unknown(),
})
}
ArtifactAdmissionError::ProtectionUnavailable {
classification,
declared,
} => Refusal::from(pb::ProjectionArtifactProtectionDetail {
classification: pb::ObjectClassification::from(Kernel(*classification)).into(),
declared_protection: pb::ObjectAtRestProtection::from(Kernel(*declared)).into(),
__buffa_unknown_fields: unknown(),
}),
};
pb::ProjectionArtifactDetail {
refusal: Some(refusal),
__buffa_unknown_fields: unknown(),
}
}
impl TryFrom<pb::ProjectionArtifactDetail> for Kernel<ArtifactAdmissionError> {
type Error = pb::ProjectionArtifactDetail;
fn try_from(value: pb::ProjectionArtifactDetail) -> Result<Self, Self::Error> {
use pb::__buffa::oneof::projection_artifact_detail::Refusal;
let pb::ProjectionArtifactDetail {
refusal,
__buffa_unknown_fields,
} = value;
let Some(refusal) = refusal else {
return Err(pb::ProjectionArtifactDetail {
refusal: None,
__buffa_unknown_fields,
});
};
let rejected = || pb::ProjectionArtifactDetail {
refusal: Some(refusal.clone()),
__buffa_unknown_fields: buffa::UnknownFields::default(),
};
let error = match refusal.clone() {
Refusal::ContentMismatch(detail) => {
let pb::ProjectionArtifactContentMismatchDetail {
object,
__buffa_unknown_fields: _,
} = *detail;
ArtifactAdmissionError::ContentMismatch { object }
}
Refusal::Signature(detail) => {
let pb::ProjectionArtifactSignatureDetail {
verdict,
__buffa_unknown_fields: _,
} = *detail;
ArtifactAdmissionError::Signature {
verdict: decoded_verdict(verdict).ok_or_else(rejected)?,
}
}
Refusal::Disagreement(detail) => {
let pb::ProjectionArtifactDisagreementDetail {
field,
__buffa_unknown_fields: _,
} = *detail;
ArtifactAdmissionError::Disagreement { field }
}
Refusal::ProtectionUnavailable(detail) => {
let pb::ProjectionArtifactProtectionDetail {
classification,
declared_protection,
__buffa_unknown_fields: _,
} = *detail;
ArtifactAdmissionError::ProtectionUnavailable {
classification: crate::immutable::wire::classification(
"classification",
classification,
)
.map_err(|_| rejected())?,
declared: crate::immutable::wire::protection(
"declared_protection",
declared_protection,
)
.map_err(|_| rejected())?,
}
}
};
Ok(Self(error))
}
}
const fn verdict_of(value: ManifestVerdict) -> pb::ProjectionManifestVerdict {
match value {
ManifestVerdict::Verified => {
pb::ProjectionManifestVerdict::PROJECTION_MANIFEST_VERDICT_VERIFIED
}
ManifestVerdict::Invalid => {
pb::ProjectionManifestVerdict::PROJECTION_MANIFEST_VERDICT_INVALID
}
ManifestVerdict::Untrusted => {
pb::ProjectionManifestVerdict::PROJECTION_MANIFEST_VERDICT_UNTRUSTED
}
}
}
fn decoded_verdict(
value: buffa::EnumValue<pb::ProjectionManifestVerdict>,
) -> Option<ManifestVerdict> {
match crate::wire::known("verdict", value).ok()? {
pb::ProjectionManifestVerdict::PROJECTION_MANIFEST_VERDICT_VERIFIED => {
Some(ManifestVerdict::Verified)
}
pb::ProjectionManifestVerdict::PROJECTION_MANIFEST_VERDICT_INVALID => {
Some(ManifestVerdict::Invalid)
}
pb::ProjectionManifestVerdict::PROJECTION_MANIFEST_VERDICT_UNTRUSTED => {
Some(ManifestVerdict::Untrusted)
}
pb::ProjectionManifestVerdict::PROJECTION_MANIFEST_VERDICT_UNSPECIFIED => None,
}
}
fn decoded_key(
value: buffa::MessageField<pb::ProjectionKey, buffa::Inline<pb::ProjectionKey>>,
) -> Result<ProjectionKey, pb::ProjectionCatalogErrorDetail> {
value
.into_option()
.and_then(|value| Kernel::<ProjectionKey>::try_from(value).ok())
.map(Kernel::into_inner)
.ok_or_else(pb::ProjectionCatalogErrorDetail::default)
}
impl TryFrom<pb::ProjectionCatalogErrorDetail> for Kernel<ProjectionCatalogError> {
type Error = pb::ProjectionCatalogErrorDetail;
fn try_from(value: pb::ProjectionCatalogErrorDetail) -> Result<Self, Self::Error> {
use pb::__buffa::oneof::projection_catalog_error_detail::Outcome;
let pb::ProjectionCatalogErrorDetail {
outcome,
__buffa_unknown_fields,
} = value;
let Some(outcome) = outcome else {
return Err(pb::ProjectionCatalogErrorDetail {
outcome: None,
__buffa_unknown_fields,
});
};
let value = pb::ProjectionCatalogErrorDetail {
outcome: Some(outcome.clone()),
__buffa_unknown_fields,
};
let error = match outcome {
Outcome::State(detail) => ProjectionCatalogError::State(
Kernel::<StateError>::try_from(*detail)
.map_err(|_| value.clone())?
.into_inner(),
),
Outcome::Object(detail) => ProjectionCatalogError::Object(Box::new(
Kernel::<ObjectError>::try_from(*detail)
.map_err(|_| value.clone())?
.into_inner(),
)),
Outcome::StalePublisher(detail) => {
let pb::ProjectionKeyDetail {
key,
__buffa_unknown_fields: _,
} = *detail;
ProjectionCatalogError::StalePublisher {
key: decoded_key(key).map_err(|_| value.clone())?,
}
}
Outcome::GenerationConflict(detail) => {
let pb::ProjectionGenerationConflictDetail {
key,
expected,
current,
__buffa_unknown_fields: _,
} = *detail;
ProjectionCatalogError::GenerationConflict {
key: decoded_key(key).map_err(|_| value.clone())?,
expected: ProjectionGeneration::new(expected),
current: ProjectionGeneration::new(current),
}
}
Outcome::NonMonotonic(detail) => {
let pb::ProjectionNonMonotonicDetail {
key,
current,
requested,
__buffa_unknown_fields: _,
} = *detail;
ProjectionCatalogError::NonMonotonic {
key: decoded_key(key).map_err(|_| value.clone())?,
current: ProjectionGeneration::new(current),
requested: ProjectionGeneration::new(requested),
}
}
Outcome::CursorRegression(detail) => {
let pb::ProjectionKeyDetail {
key,
__buffa_unknown_fields: _,
} = *detail;
ProjectionCatalogError::CursorRegression {
key: decoded_key(key).map_err(|_| value.clone())?,
}
}
Outcome::UnknownManifest(detail) => {
let pb::ProjectionManifestDetail {
key,
generation,
__buffa_unknown_fields: _,
} = *detail;
ProjectionCatalogError::UnknownManifest {
key: decoded_key(key).map_err(|_| value.clone())?,
generation: ProjectionGeneration::new(generation),
}
}
Outcome::ManifestConflict(detail) => {
let pb::ProjectionManifestDetail {
key,
generation,
__buffa_unknown_fields: _,
} = *detail;
ProjectionCatalogError::ManifestConflict {
key: decoded_key(key).map_err(|_| value.clone())?,
generation: ProjectionGeneration::new(generation),
}
}
Outcome::Artifact(detail) => ProjectionCatalogError::Artifact(Box::new(
Kernel::<ArtifactAdmissionError>::try_from(*detail)
.map_err(|_| value.clone())?
.into_inner(),
)),
};
Ok(Self(error))
}
}
#[must_use]
pub fn to_connect_error(error: &ProjectionCatalogError) -> ConnectError {
let code = match error {
ProjectionCatalogError::State(error) => crate::error::code_for(error),
ProjectionCatalogError::Object(error) => object_code(error),
ProjectionCatalogError::UnknownManifest { .. } => ErrorCode::NotFound,
ProjectionCatalogError::StalePublisher { .. }
| ProjectionCatalogError::GenerationConflict { .. }
| ProjectionCatalogError::NonMonotonic { .. }
| ProjectionCatalogError::CursorRegression { .. }
| ProjectionCatalogError::ManifestConflict { .. }
| ProjectionCatalogError::Artifact(_) => ErrorCode::FailedPrecondition,
};
ConnectError::new(code, error.to_string()).with_detail(ErrorDetail::from_message(
PROJECTION_ERROR_DETAIL_TYPE,
&pb::ProjectionCatalogErrorDetail::from(Kernel(error)),
))
}
const fn object_code(error: &ObjectError) -> ErrorCode {
match error {
ObjectError::State(state) => crate::error::code_for(state),
ObjectError::UnknownGeneration { .. } => ErrorCode::NotFound,
ObjectError::StaleGeneration { .. }
| ObjectError::NotMonotonic { .. }
| ObjectError::Retained { .. }
| ObjectError::CurrentGeneration { .. }
| ObjectError::DescriptorConflict { .. }
| ObjectError::ProtectionUnavailable { .. } => ErrorCode::FailedPrecondition,
}
}
#[must_use]
pub fn from_connect_error(
error: &ConnectError,
fallback: &TransportFallback,
) -> ProjectionCatalogError {
for detail in &error.details {
if detail.type_url != PROJECTION_ERROR_DETAIL_TYPE {
continue;
}
let Some(encoded) = detail.value.as_deref() else {
continue;
};
let Ok(bytes) = decode_detail(encoded) else {
continue;
};
let Ok(message) =
<pb::ProjectionCatalogErrorDetail as buffa::Message>::decode_from_slice(&bytes)
else {
continue;
};
if let Ok(typed) = Kernel::<ProjectionCatalogError>::try_from(message) {
return typed.into_inner();
}
}
ProjectionCatalogError::State(state_from_connect(error, fallback))
}
#[cfg(test)]
mod tests {
use super::*;
use polyc_state::{
error::StateError,
id::PartitionId,
immutable::{AtRestProtection, Classification, Generation, ObjectId},
projection::{FamilyId, ProjectionKey, artifact::ManifestVerdict},
};
fn key() -> ProjectionKey {
ProjectionKey::new(
FamilyId::new("conversation-core/v1"),
PartitionId::new("p1"),
)
}
#[test]
fn every_catalog_outcome_round_trips_as_typed_detail() {
let outcomes = vec![
ProjectionCatalogError::State(StateError::Denied {
family: polyc_state::projection::family(),
}),
ProjectionCatalogError::Object(Box::new(ObjectError::UnknownGeneration {
object: ObjectId::new("projection/object"),
generation: Generation::new(2),
})),
ProjectionCatalogError::StalePublisher { key: key() },
ProjectionCatalogError::GenerationConflict {
key: key(),
expected: ProjectionGeneration::new(1),
current: ProjectionGeneration::new(2),
},
ProjectionCatalogError::NonMonotonic {
key: key(),
current: ProjectionGeneration::new(2),
requested: ProjectionGeneration::new(1),
},
ProjectionCatalogError::CursorRegression { key: key() },
ProjectionCatalogError::UnknownManifest {
key: key(),
generation: ProjectionGeneration::new(3),
},
ProjectionCatalogError::ManifestConflict {
key: key(),
generation: ProjectionGeneration::new(3),
},
ProjectionCatalogError::State(StateError::Malformed {
field: "manifest".to_owned(),
reason: "not canonical".to_owned(),
}),
ProjectionCatalogError::Artifact(Box::new(ArtifactAdmissionError::ContentMismatch {
object: "projection/object".to_owned(),
})),
ProjectionCatalogError::Artifact(Box::new(ArtifactAdmissionError::Signature {
verdict: ManifestVerdict::Untrusted,
})),
ProjectionCatalogError::Artifact(Box::new(ArtifactAdmissionError::Disagreement {
field: "manifest_namespace".to_owned(),
})),
ProjectionCatalogError::Artifact(Box::new(
ArtifactAdmissionError::ProtectionUnavailable {
classification: Classification::Confidential,
declared: AtRestProtection::None,
},
)),
];
for expected in outcomes {
let wire = pb::ProjectionCatalogErrorDetail::from(Kernel(&expected));
let actual = Kernel::<ProjectionCatalogError>::try_from(wire)
.expect("decode typed projection outcome")
.into_inner();
assert_eq!(actual, expected);
}
}
}