refactor: move firmware workspace under firmware
This commit is contained in:
1 parent
153b0bf262
commit
f03fc119a4
222 files changed
+56
-13
No files matched your search
@@ -0,0 +1,195 @@
|
||||
//! Stable typed outcomes for the Kaptan configuration microservice.
|
||||
|
||||
use kaptan_config::{ConfigState, ConfigViolation, FieldId};
|
||||
use kaptan_log::{EventHub, KaptanError, LogFlags, PublishOutcome};
|
||||
/// Operation reported in an authoritative result record.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum Operation {
|
||||
/// Query/read operation.
|
||||
Query,
|
||||
/// Begin transaction.
|
||||
Begin,
|
||||
/// Scalar write.
|
||||
Set,
|
||||
/// Whole-candidate validation.
|
||||
Validate,
|
||||
/// Durable commit.
|
||||
Commit,
|
||||
/// Transaction abort.
|
||||
Abort,
|
||||
/// CBOR snapshot import into the isolated transaction.
|
||||
Import,
|
||||
}
|
||||
|
||||
/// Stable result classification.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum ResultKind {
|
||||
/// The operation was accepted.
|
||||
Accepted,
|
||||
/// A full snapshot committed but requires restart before activation.
|
||||
CommittedRestartRequired,
|
||||
/// The operation was rejected without an active-state transition.
|
||||
Rejected,
|
||||
}
|
||||
|
||||
/// Compact protocol error code. Detail belongs in [`ConfigViolation`] when a
|
||||
/// configuration invariant was responsible.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, KaptanError)]
|
||||
pub enum ProtocolError {
|
||||
/// The request target did not match this component.
|
||||
BadTarget,
|
||||
/// Authorization was absent or insufficient.
|
||||
AuthorizationDenied,
|
||||
/// The optimistic durable generation did not match.
|
||||
GenerationConflict,
|
||||
/// The single transaction slot is occupied.
|
||||
SessionBusy,
|
||||
/// The session is unknown, expired, or belongs to a different principal.
|
||||
InvalidSession,
|
||||
/// Reusing one request ID with different content is forbidden.
|
||||
RequestConflict,
|
||||
/// The request was not based on the current transaction revision.
|
||||
StaleRevision,
|
||||
/// A command is invalid in the current lifecycle state.
|
||||
BadState,
|
||||
/// The field is not present in this schema.
|
||||
UnknownField(FieldId),
|
||||
/// The typed setter does not match the field descriptor.
|
||||
TypeMismatch(FieldId),
|
||||
/// A local or complete validation issue.
|
||||
Violation(ConfigViolation),
|
||||
/// Persistence failed after a complete validation pass.
|
||||
StorageFailure,
|
||||
/// A CBOR snapshot could not be decoded into this schema.
|
||||
CborMalformed,
|
||||
/// A CBOR snapshot exceeded the fixed import budget.
|
||||
CborTooLarge,
|
||||
/// The complete CBOR snapshot did not match the supplied CRC-32C.
|
||||
BlobCrc,
|
||||
/// The uploaded CBOR blob was prepared for a different schema.
|
||||
SchemaMismatch,
|
||||
/// This deployment does not expose bounded CBOR transaction import.
|
||||
ImportUnavailable,
|
||||
/// The adapter reached an impossible internal state.
|
||||
Internal,
|
||||
}
|
||||
|
||||
/// Authoritative completion for one request.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub struct ConfigReply {
|
||||
/// Client request identity.
|
||||
pub request_id: u32,
|
||||
/// The current session, or zero when none exists.
|
||||
pub session_id: u32,
|
||||
/// Server-assigned import transfer identity, or zero when not importing.
|
||||
pub transfer_id: u32,
|
||||
/// The transaction revision, or zero when no transaction exists.
|
||||
pub revision: u32,
|
||||
/// Latest committed generation, or zero for the factory profile.
|
||||
pub generation: u64,
|
||||
/// Completed operation.
|
||||
pub operation: Operation,
|
||||
/// High-level result.
|
||||
pub result: ResultKind,
|
||||
/// Exact typed failure, if rejected.
|
||||
pub error: Option<ProtocolError>,
|
||||
/// Current lifecycle state after handling the request.
|
||||
pub state: ConfigState,
|
||||
}
|
||||
|
||||
impl ConfigReply {
|
||||
/// Stable primary field for the wire response, or the reserved sentinel.
|
||||
pub const fn primary_field(self) -> u16 {
|
||||
match self.error {
|
||||
Some(ProtocolError::Violation(violation)) => match violation.primary() {
|
||||
Some(field) => field.get(),
|
||||
None => u16::MAX,
|
||||
},
|
||||
Some(ProtocolError::UnknownField(field)) | Some(ProtocolError::TypeMismatch(field)) => {
|
||||
field.get()
|
||||
}
|
||||
_ => u16::MAX,
|
||||
}
|
||||
}
|
||||
|
||||
/// Stable related field for the wire response, or the reserved sentinel.
|
||||
pub const fn related_field(self) -> u16 {
|
||||
match self.error {
|
||||
Some(ProtocolError::Violation(violation)) => match violation.related_field() {
|
||||
Some(field) => field.get(),
|
||||
None => u16::MAX,
|
||||
},
|
||||
_ => u16::MAX,
|
||||
}
|
||||
}
|
||||
|
||||
/// Stable validation-rule code, or zero when no validation rule applies.
|
||||
pub const fn validation_rule(self) -> u16 {
|
||||
match self.error {
|
||||
Some(ProtocolError::Violation(violation)) => violation.rule().code(),
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Enqueues a critical diagnostic companion for a rejected structured
|
||||
/// result. The `KAPTAN_CONFIG_RESULT` remains the correlated,
|
||||
/// field/rule-precise authority; this sends only the derive-generated
|
||||
/// high-level `ProtocolError` display identity through `KAPTAN_ERROR`.
|
||||
///
|
||||
/// The helper deliberately carries no request/session/generation fields
|
||||
/// into the error channel. A composition owner may call it after it has
|
||||
/// emitted or retained the authoritative result.
|
||||
pub fn publish_rejection<const CRITICAL: usize, const BEST_EFFORT: usize>(
|
||||
self,
|
||||
hub: &mut EventHub<CRITICAL, BEST_EFFORT>,
|
||||
) -> Option<PublishOutcome> {
|
||||
if self.result != ResultKind::Rejected {
|
||||
return None;
|
||||
}
|
||||
self.error
|
||||
.as_ref()
|
||||
.map(|error| hub.publish_error(LogFlags::NONE, error))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use kaptan_log::{EventHub, LogFilter, PublishOutcome, RecordChannel};
|
||||
|
||||
use super::{ConfigReply, Operation, ProtocolError, ResultKind};
|
||||
use kaptan_config::ConfigState;
|
||||
|
||||
#[test]
|
||||
fn rejected_result_enqueues_only_its_high_level_error_identity() {
|
||||
let reply = ConfigReply {
|
||||
request_id: 42,
|
||||
session_id: 7,
|
||||
transfer_id: 0,
|
||||
revision: 3,
|
||||
generation: 9,
|
||||
operation: Operation::Set,
|
||||
result: ResultKind::Rejected,
|
||||
error: Some(ProtocolError::BadState),
|
||||
state: ConfigState::Editing,
|
||||
};
|
||||
let mut hub = EventHub::<1, 1>::new(LogFilter::default());
|
||||
assert_eq!(
|
||||
reply.publish_rejection(&mut hub),
|
||||
Some(PublishOutcome::Queued)
|
||||
);
|
||||
let record = hub.peek().expect("rejection is retained");
|
||||
assert_eq!(record.channel(), RecordChannel::Error);
|
||||
assert_eq!(
|
||||
record.error_path().unwrap().as_str(),
|
||||
"kaptan_config_mavlink::protocol::ProtocolError::BadState"
|
||||
);
|
||||
assert!(record.additional_information().unwrap().is_empty());
|
||||
|
||||
let accepted = ConfigReply {
|
||||
result: ResultKind::Accepted,
|
||||
error: None,
|
||||
..reply
|
||||
};
|
||||
assert_eq!(accepted.publish_rejection(&mut hub), None);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user