field_ios

In-the-field app for Radroots on iOS
git clone https://radroots.dev/git/field_ios.git
Log | Files | Refs | README | LICENSE

submission.rs (5453B)


      1 //! Stable action identity and exact saved source; no publication acknowledgement.
      2 
      3 use crate::{
      4     FfiComposerDraftRecord, FfiComposerScopeRecord, MOBILE_FFI_SCHEMA_VERSION, TeraAppError,
      5 };
      6 use tera_core::runtime::product_surface::{
      7     ComposerRevision, SubmissionCommandId, SubmissionReservationError,
      8     SubmissionReservationReceipt, SubmissionReservationRequest,
      9 };
     10 
     11 mod error;
     12 mod operation;
     13 mod page;
     14 mod renewal;
     15 mod retry;
     16 mod targets;
     17 pub use operation::*;
     18 pub use page::*;
     19 pub use renewal::FfiSubmissionUploadRenewal;
     20 pub use retry::*;
     21 pub use targets::*;
     22 
     23 #[derive(Clone, Debug, Eq, PartialEq, uniffi::Record)]
     24 pub struct FfiSubmissionCommandIdRecord {
     25     pub schema_version: u16,
     26     pub id: String,
     27 }
     28 
     29 /// Generate once for each intentional Submit and retain it for every retry.
     30 #[cfg_attr(not(coverage_nightly), uniffi::export)]
     31 pub fn submission_reserve_id() -> Result<FfiSubmissionCommandIdRecord, TeraAppError> {
     32     Ok(FfiSubmissionCommandIdRecord {
     33         schema_version: MOBILE_FFI_SCHEMA_VERSION,
     34         id: hex::encode(SubmissionCommandId::generate()?.as_bytes()),
     35     })
     36 }
     37 
     38 #[derive(Clone, Debug, Eq, PartialEq, uniffi::Record)]
     39 pub struct FfiSubmissionReservationRequest {
     40     pub schema_version: u16,
     41     pub command_id: String,
     42     pub scope: FfiComposerScopeRecord,
     43     pub composer_id: String,
     44     pub expected_revision: u64,
     45 }
     46 
     47 impl From<&SubmissionReservationRequest> for FfiSubmissionReservationRequest {
     48     fn from(value: &SubmissionReservationRequest) -> Self {
     49         Self {
     50             schema_version: MOBILE_FFI_SCHEMA_VERSION,
     51             command_id: hex::encode(value.command_id().as_bytes()),
     52             scope: value.scope().into(),
     53             composer_id: hex::encode(value.composer_id().as_bytes()),
     54             expected_revision: value.expected_revision().get(),
     55         }
     56     }
     57 }
     58 
     59 impl TryFrom<FfiSubmissionReservationRequest> for SubmissionReservationRequest {
     60     type Error = TeraAppError;
     61     fn try_from(value: FfiSubmissionReservationRequest) -> Result<Self, Self::Error> {
     62         if value.schema_version != MOBILE_FFI_SCHEMA_VERSION {
     63             return Err(SubmissionReservationError::UnsupportedSchema.into());
     64         }
     65         if value.command_id.len() != 32
     66             || !value
     67                 .command_id
     68                 .bytes()
     69                 .all(|byte| byte.is_ascii_digit() || (b'a'..=b'f').contains(&byte))
     70         {
     71             return Err(SubmissionReservationError::InvalidCommandId.into());
     72         }
     73         let command_id = SubmissionCommandId::new(crate::decode_id(
     74             &value.command_id,
     75             "submission_command_id_invalid",
     76         )?)?;
     77         Ok(Self::new(
     78             command_id,
     79             value.scope.try_into()?,
     80             crate::composer::id(&value.composer_id)?,
     81             ComposerRevision::new(value.expected_revision)?,
     82         ))
     83     }
     84 }
     85 
     86 /// Confirms only the committed reservation and its immutable historical composer.
     87 #[derive(Clone, Debug, Eq, PartialEq, uniffi::Record)]
     88 pub struct FfiSubmissionReservationReceipt {
     89     pub schema_version: u16,
     90     pub command_id: String,
     91     pub reservation_id: String,
     92     pub captured: FfiComposerDraftRecord,
     93     pub reserved_at_unix_ms: u64,
     94     pub replayed: bool,
     95 }
     96 
     97 impl From<&SubmissionReservationReceipt> for FfiSubmissionReservationReceipt {
     98     fn from(value: &SubmissionReservationReceipt) -> Self {
     99         Self {
    100             schema_version: MOBILE_FFI_SCHEMA_VERSION,
    101             command_id: hex::encode(value.request().command_id().as_bytes()),
    102             reservation_id: hex::encode(value.reservation_id().as_bytes()),
    103             captured: value.captured().into(),
    104             reserved_at_unix_ms: value.reserved_at_unix_ms(),
    105             replayed: value.is_replay(),
    106         }
    107     }
    108 }
    109 
    110 impl From<SubmissionReservationError> for TeraAppError {
    111     fn from(error: SubmissionReservationError) -> Self {
    112         use SubmissionReservationError as E;
    113         let (code, retryable, actions): (_, _, &[&str]) = match error {
    114             E::Source(error) => return error.into(),
    115             E::Lifecycle(error) => return tera_core::TeraAppError::from(error).into(),
    116             E::InvalidCommandId => return Self::invalid_argument("submission_command_id_invalid"),
    117             E::IdempotencyConflict => {
    118                 ("idempotency_conflict", false, &["restore_original_request"])
    119             }
    120             E::CorruptRecord => (
    121                 "submission_record_corrupt",
    122                 false,
    123                 &["preserve_local_work", "inspect_local_stores"],
    124             ),
    125             E::UnsupportedSchema => (
    126                 "submission_schema_unsupported",
    127                 false,
    128                 &["preserve_local_work", "update_app"],
    129             ),
    130             E::InvalidReceipt => ("submission_receipt_mismatch", true, &["retry_same_command"]),
    131             E::ClockUnavailable => ("operation_clock_unavailable", true, &["check_device_clock"]),
    132             E::Storage(radroots_storage::Error::SpaceInsufficient) => {
    133                 return Self::needs_space("submission");
    134             }
    135             E::Storage(_) => (
    136                 "submission_storage_failed",
    137                 true,
    138                 &["inspect_local_stores", "retry_same_command"],
    139             ),
    140         };
    141         Self::failure(
    142             code,
    143             "submission",
    144             retryable,
    145             actions,
    146             "The submission reservation could not be confirmed.",
    147         )
    148     }
    149 }