kcode_k1_web_code_runtime/
lib.rs1use kcode_k1_transaction_id::TxId;
2use kcode_k1_web_coding::{K1WebCoding, WebCodingConfig, WebCodingError, WebCodingRevisions};
3use kcode_k1_web_podman::WebPodmanConfig;
4use kcode_k1_web_projection::K1WebProjection;
5use std::collections::HashMap;
6use std::fmt::{Display, Formatter, Write};
7use std::path::PathBuf;
8use std::sync::{Arc, Mutex};
9
10type UserSlot = Arc<Mutex<Option<K1WebCoding>>>;
11
12#[derive(Clone)]
13pub struct RuntimeRevisions {
14 pub boot: String,
15 pub schema: String,
16 pub route: String,
17 pub harness: String,
18 pub check_policy: String,
19}
20
21#[derive(Clone)]
22pub struct RuntimeConfig {
23 cache_root: PathBuf,
24 projection_root: PathBuf,
25 revisions: RuntimeRevisions,
26 podman: WebPodmanConfig,
27}
28
29impl RuntimeConfig {
30 pub fn new(
31 cache_root: PathBuf,
32 projection_root: PathBuf,
33 revisions: RuntimeRevisions,
34 podman: WebPodmanConfig,
35 ) -> Self {
36 Self {
37 cache_root,
38 projection_root,
39 revisions,
40 podman,
41 }
42 }
43}
44
45#[derive(Debug)]
46pub enum RuntimeError {
47 Coding(WebCodingError),
48 LockPoisoned(String),
49}
50
51impl Display for RuntimeError {
52 fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
53 match self {
54 Self::Coding(error) => Display::fmt(error, formatter),
55 Self::LockPoisoned(message) => formatter.write_str(message),
56 }
57 }
58}
59
60impl std::error::Error for RuntimeError {
61 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
62 match self {
63 Self::Coding(error) => Some(error),
64 Self::LockPoisoned(_) => None,
65 }
66 }
67}
68
69impl From<WebCodingError> for RuntimeError {
70 fn from(error: WebCodingError) -> Self {
71 Self::Coding(error)
72 }
73}
74
75struct Inner {
76 config: RuntimeConfig,
77 projection: Arc<K1WebProjection>,
78 slots: Mutex<HashMap<[u8; 12], UserSlot>>,
79}
80
81#[derive(Clone)]
82pub struct K1WebCodeRuntime {
83 inner: Arc<Inner>,
84}
85
86impl K1WebCodeRuntime {
87 pub fn new(config: RuntimeConfig, projection: Arc<K1WebProjection>) -> Self {
88 Self {
89 inner: Arc::new(Inner {
90 config,
91 projection,
92 slots: Mutex::new(HashMap::new()),
93 }),
94 }
95 }
96
97 pub fn with_user<T>(
98 &self,
99 user: [u8; 12],
100 operation: impl FnOnce(&mut K1WebCoding) -> Result<T, WebCodingError>,
101 ) -> Result<T, RuntimeError> {
102 let slot = {
103 let mut slots = self.inner.slots.lock().map_err(|_| {
104 RuntimeError::LockPoisoned("runtime slot map mutex poisoned".to_owned())
105 })?;
106 slots
107 .entry(user)
108 .or_insert_with(|| Arc::new(Mutex::new(None)))
109 .clone()
110 };
111
112 let mut coding = slot
113 .lock()
114 .map_err(|_| RuntimeError::LockPoisoned("user coding mutex poisoned".to_owned()))?;
115 if coding.is_none() {
116 *coding = Some(self.open_user(user)?);
117 }
118 operation(coding.as_mut().expect("coding was initialized")).map_err(RuntimeError::Coding)
119 }
120
121 fn open_user(&self, user: [u8; 12]) -> Result<K1WebCoding, WebCodingError> {
122 let revisions = &self.inner.config.revisions;
123 let config = WebCodingConfig::new(
124 WebCodingRevisions {
125 boot: revisions.boot.clone(),
126 schema: revisions.schema.clone(),
127 route: revisions.route.clone(),
128 harness: revisions.harness.clone(),
129 check_policy: revisions.check_policy.clone(),
130 },
131 self.inner.config.projection_root.clone(),
132 self.inner.config.podman.clone(),
133 )?;
134 K1WebCoding::open(
135 self.inner.config.cache_root.join(cache_directory(user)),
136 TxId::from_bytes(user),
137 config,
138 self.inner.projection.clone(),
139 )
140 }
141}
142
143fn cache_directory(user: [u8; 12]) -> String {
144 let mut directory = String::with_capacity(24);
145 for byte in user {
146 write!(&mut directory, "{byte:02x}").expect("writing to String cannot fail");
147 }
148 directory
149}
150
151#[cfg(test)]
152mod tests {
153 use super::cache_directory;
154
155 #[test]
156 fn cache_directory_is_exact_lowercase_user_hex() {
157 assert_eq!(
158 cache_directory([
159 0x00, 0x01, 0x0a, 0x10, 0x2f, 0x7f, 0x80, 0xab, 0xcd, 0xef, 0xf0, 0xff,
160 ]),
161 "00010a102f7f80abcdeff0ff"
162 );
163 }
164}