use ryo_analysis::SymbolId;
use serde::{Deserialize, Serialize};
use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct IntentId(pub u64);
impl IntentId {
pub fn new(id: u64) -> Self {
Self(id)
}
pub fn as_u64(self) -> u64 {
self.0
}
}
impl fmt::Display for IntentId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "I{:06}", self.0)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum LockError {
Blocked {
symbol: SymbolId,
blocking_intent: IntentId,
},
}
impl fmt::Display for LockError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Blocked {
symbol,
blocking_intent,
} => {
write!(
f,
"Symbol {:?} is locked by intent {}",
symbol, blocking_intent
)
}
}
}
}
impl std::error::Error for LockError {}
pub trait IntentLockQuery: Send + Sync {
fn is_locked(&self, symbols: &[SymbolId]) -> bool;
fn locked_by(&self, symbol: &SymbolId) -> Option<IntentId>;
fn locked_symbols(&self) -> Vec<SymbolId>;
}
pub trait IntentLockOps: IntentLockQuery {
type Guard<'a>
where
Self: 'a;
fn try_acquire(
&self,
intent_id: IntentId,
symbols: &[SymbolId],
) -> Result<Self::Guard<'_>, LockError>;
fn force_release(&self, intent_id: IntentId);
}
#[derive(Debug, Default)]
pub struct NoOpIntentLock;
impl IntentLockQuery for NoOpIntentLock {
fn is_locked(&self, _symbols: &[SymbolId]) -> bool {
false
}
fn locked_by(&self, _symbol: &SymbolId) -> Option<IntentId> {
None
}
fn locked_symbols(&self) -> Vec<SymbolId> {
vec![]
}
}
impl IntentLockOps for NoOpIntentLock {
type Guard<'a> = ();
fn try_acquire(
&self,
_intent_id: IntentId,
_symbols: &[SymbolId],
) -> Result<Self::Guard<'_>, LockError> {
Ok(())
}
fn force_release(&self, _intent_id: IntentId) {}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_intent_id_display() {
let id = IntentId::new(42);
assert_eq!(id.to_string(), "I000042");
}
#[test]
fn test_lock_error_display() {
let err = LockError::Blocked {
symbol: SymbolId::parse("100v1").unwrap(),
blocking_intent: IntentId::new(1),
};
assert!(err.to_string().contains("locked by intent"));
}
#[test]
fn test_noop_lock() {
let lock = NoOpIntentLock;
let sym = SymbolId::parse("100v1").unwrap();
assert!(!lock.is_locked(&[sym]));
assert!(lock.try_acquire(IntentId::new(1), &[sym]).is_ok());
}
}