1use crate::Engine;
9use crate::error::{Result, SconeError};
10
11#[derive(Debug, Clone)]
13pub struct ScopedSpace {
14 id: i64,
15 name: String,
16}
17
18impl ScopedSpace {
19 pub fn name(&self) -> &str {
20 &self.name
21 }
22
23 pub(crate) fn id(&self) -> i64 {
24 self.id
25 }
26}
27
28pub fn resolve(engine: &mut Engine, name: &str, create: bool) -> Result<ScopedSpace> {
33 if name.is_empty()
34 || name.len() > 64
35 || !name
36 .chars()
37 .all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '-' || c == '_')
38 {
39 return Err(SconeError::InvalidInput(format!(
40 "space name must be 1..=64 chars of [a-z0-9-_], got {name:?}"
41 )));
42 }
43 let conn = engine.conn_mut();
44 if create {
45 conn.execute("INSERT OR IGNORE INTO spaces (name) VALUES (?1)", [name])?;
46 }
47 let id = conn
48 .query_row("SELECT id FROM spaces WHERE name = ?1", [name], |r| {
49 r.get(0)
50 })
51 .map_err(|e| match e {
52 rusqlite::Error::QueryReturnedNoRows => SconeError::NotFound(format!("space {name:?}")),
53 other => SconeError::Db(other),
54 })?;
55 Ok(ScopedSpace {
56 id,
57 name: name.to_owned(),
58 })
59}