igc-net 0.3.0

igc-net protocol rust library — publish and add metadata to IGC flight files
Documentation
use crate::id::{Blake3Hex, PilotId};

use super::record::{PilotAuthDidRecord, PrivateAccessRotationRecord};

#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum FlightGovernanceStatus {
    Pending,
    Approved,
    Contested,
    Rejected,
    Superseded,
    Revoked,
    Deleted,
}

#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct FlightGovernanceState {
    pub raw_igc_hash: Blake3Hex,
    pub owner_pilot_id: Option<PilotId>,
    pub status: FlightGovernanceStatus,
    pub baseline_ready: bool,
    pub recorded_at: String,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PilotAuthDidStateStatus {
    Absent,
    Tentative,
    Authoritative,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PilotAuthDidState {
    pub pilot_id: PilotId,
    pub authoritative: Option<PilotAuthDidRecord>,
    pub tentative_record_ids: Vec<Blake3Hex>,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PrivateAccessRotationStateStatus {
    Absent,
    Tentative,
    Authoritative,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PrivateAccessRotationState {
    pub pilot_id: PilotId,
    pub authoritative: Option<PrivateAccessRotationRecord>,
    pub tentative_record_ids: Vec<Blake3Hex>,
}

impl FlightGovernanceStatus {
    pub fn blocks_serving(self) -> bool {
        matches!(
            self,
            Self::Contested | Self::Rejected | Self::Superseded | Self::Revoked | Self::Deleted
        )
    }
}

impl FlightGovernanceState {
    pub fn approved_owner(
        raw_igc_hash: Blake3Hex,
        owner_pilot_id: PilotId,
        recorded_at: impl Into<String>,
    ) -> Self {
        Self {
            raw_igc_hash,
            owner_pilot_id: Some(owner_pilot_id),
            status: FlightGovernanceStatus::Approved,
            baseline_ready: true,
            recorded_at: recorded_at.into(),
        }
    }

    pub fn serving_blocked(&self) -> bool {
        self.status.blocks_serving()
    }

    pub fn restricted_serving_ready_for(&self, pilot_id: &PilotId) -> bool {
        self.baseline_ready
            && self.status == FlightGovernanceStatus::Approved
            && self.owner_pilot_id.as_ref() == Some(pilot_id)
    }
}

impl PilotAuthDidState {
    pub fn absent(pilot_id: PilotId) -> Self {
        Self {
            pilot_id,
            authoritative: None,
            tentative_record_ids: Vec::new(),
        }
    }

    pub fn status(&self) -> PilotAuthDidStateStatus {
        if self.authoritative.is_some() {
            PilotAuthDidStateStatus::Authoritative
        } else if self.tentative_record_ids.is_empty() {
            PilotAuthDidStateStatus::Absent
        } else {
            PilotAuthDidStateStatus::Tentative
        }
    }

    pub fn requires_catch_up(&self) -> bool {
        !self.tentative_record_ids.is_empty()
    }

    pub fn is_high_trust_authoritative(&self) -> bool {
        self.authoritative.is_some() && !self.requires_catch_up()
    }

    pub fn known_record_ids(&self) -> Vec<Blake3Hex> {
        let mut record_ids = self.tentative_record_ids.clone();
        if let Some(record) = &self.authoritative {
            record_ids.push(record.record_id.clone());
        }
        record_ids.sort();
        record_ids.dedup();
        record_ids
    }
}

impl PrivateAccessRotationState {
    pub fn absent(pilot_id: PilotId) -> Self {
        Self {
            pilot_id,
            authoritative: None,
            tentative_record_ids: Vec::new(),
        }
    }

    pub fn status(&self) -> PrivateAccessRotationStateStatus {
        if self.authoritative.is_some() {
            PrivateAccessRotationStateStatus::Authoritative
        } else if self.tentative_record_ids.is_empty() {
            PrivateAccessRotationStateStatus::Absent
        } else {
            PrivateAccessRotationStateStatus::Tentative
        }
    }

    pub fn requires_catch_up(&self) -> bool {
        !self.tentative_record_ids.is_empty()
    }

    pub fn is_authoritative(&self) -> bool {
        self.authoritative.is_some() && !self.requires_catch_up()
    }
}