use serde::Deserialize;
use std::collections::BTreeMap;
use std::path::{Path, PathBuf};
use thiserror::Error;
#[derive(Debug, Error)]
pub enum RegistryError {
#[error("cannot read sim registry {path}: {source}")]
Io {
path: String,
source: std::io::Error,
},
#[error("malformed sim registry {path}: {detail}")]
Malformed {
path: String,
detail: String,
},
#[error(
"unknown device ref {device_ref:?} — pass an explicit UDID or one of \
the recorded aliases: {}",
known.join(", ")
)]
UnknownDevice {
device_ref: String,
known: Vec<String>,
},
}
#[derive(Debug, Clone, Deserialize)]
pub struct RegisteredSim {
#[serde(rename = "deviceName")]
pub device_name: String,
pub udid: String,
pub runtime: String,
#[serde(rename = "deviceType")]
pub device_type: String,
#[serde(default)]
pub locale: Option<String>,
}
#[derive(Debug, Deserialize)]
struct RegistryFile {
#[allow(dead_code)]
version: u32,
sims: BTreeMap<String, RegisteredSim>,
}
#[derive(Debug)]
pub struct SimRegistry {
sims: BTreeMap<String, RegisteredSim>,
}
pub fn is_udid(s: &str) -> bool {
let bytes = s.as_bytes();
if bytes.len() != 36 {
return false;
}
for (i, b) in bytes.iter().enumerate() {
match i {
8 | 13 | 18 | 23 => {
if *b != b'-' {
return false;
}
}
_ => {
if !b.is_ascii_hexdigit() {
return false;
}
}
}
}
true
}
impl SimRegistry {
pub fn load(path: &Path) -> Result<Self, RegistryError> {
let text = std::fs::read_to_string(path).map_err(|source| RegistryError::Io {
path: path.display().to_string(),
source,
})?;
let file: RegistryFile =
serde_json::from_str(&text).map_err(|e| RegistryError::Malformed {
path: path.display().to_string(),
detail: e.to_string(),
})?;
Ok(Self { sims: file.sims })
}
pub fn discover(start: &Path) -> Option<PathBuf> {
let mut dir = Some(start);
while let Some(d) = dir {
let candidate = d.join(".smix/sims.json");
if candidate.is_file() {
return Some(candidate);
}
dir = d.parent();
}
None
}
pub fn resolve(&self, device_ref: &str) -> Result<String, RegistryError> {
if is_udid(device_ref) {
return Ok(device_ref.to_ascii_uppercase());
}
if let Some(sim) = self.sims.get(device_ref) {
return Ok(sim.udid.to_ascii_uppercase());
}
if let Some(sim) = self.sims.values().find(|s| s.device_name == device_ref) {
return Ok(sim.udid.to_ascii_uppercase());
}
let mut known: Vec<String> = Vec::with_capacity(self.sims.len() * 2);
for (alias, sim) in &self.sims {
known.push(alias.clone());
known.push(sim.device_name.clone());
}
Err(RegistryError::UnknownDevice {
device_ref: device_ref.to_string(),
known,
})
}
pub fn sims(&self) -> &BTreeMap<String, RegisteredSim> {
&self.sims
}
pub fn lookup(&self, device_ref: &str) -> Option<&RegisteredSim> {
if let Some(sim) = self.sims.get(device_ref) {
return Some(sim);
}
self.sims
.values()
.find(|sim| sim.device_name == device_ref || sim.udid.eq_ignore_ascii_case(device_ref))
}
}