pub mod brain;
pub mod brain_control;
pub mod compat_import;
pub mod compatibility;
pub mod credentials;
pub mod fixtures;
pub mod packages;
pub mod profiles;
pub mod migrate;
use std::path::{Path, PathBuf};
pub fn fetch_oxicode_home() -> Option<PathBuf> {
if let Ok(home) = std::env::var("OXICODE_HOME") {
return Some(PathBuf::from(home));
}
dirs::home_dir().map(|h| h.join(".oxicode"))
}
pub const FOUNDATION_ROOT_SUFFIX: &str = "oxi/foundation/v1";
pub mod files {
pub const FOUNDATION: &str = "foundation.json";
pub const PROFILES: &str = "profiles.json";
pub const PACKAGES_LOCK: &str = "packages.lock";
pub const PACKAGES_DIR: &str = "packages";
}
pub fn foundation_root() -> Option<PathBuf> {
if let Ok(home) = std::env::var("OXI_FOUNDATION_HOME") {
let trimmed = home.trim();
if !trimmed.is_empty() {
return Some(PathBuf::from(trimmed));
}
}
dirs::home_dir().map(|h| h.join(FOUNDATION_ROOT_SUFFIX))
}
pub fn foundation_present(root: &Path) -> bool {
root.is_dir() && root.join(files::FOUNDATION).is_file() && root.join(files::PROFILES).is_file()
}
pub fn discover(root: &Path) -> Result<FoundationSnapshot, FoundationError> {
let compatibility = compatibility::read(&root.join(files::FOUNDATION))?;
let profiles = profiles::read(&root.join(files::PROFILES))?;
let packages = packages::read(
&root.join(files::PACKAGES_LOCK),
&root.join(files::PACKAGES_DIR),
)?;
Ok(FoundationSnapshot {
root: root.to_path_buf(),
compatibility,
profiles,
packages,
})
}
#[derive(Debug, Clone)]
pub struct FoundationSnapshot {
pub root: PathBuf,
pub compatibility: compatibility::FoundationManifest,
pub profiles: profiles::ProfilesFile,
pub packages: packages::PackagesFile,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum FoundationError {
UnsupportedSchema(u32),
IncompatibleHost(String),
Parse(String),
SecretNotAllowed(String),
DuplicateProfileId(String),
UnsupportedRequirement(String),
DigestMismatch {
package: String,
expected: String,
actual: String,
},
TargetMismatch {
package: String,
targets: Vec<String>,
},
UnknownProfile(String),
UnknownRole(String),
AmbiguousRole(String),
KeychainUnavailable(String),
KeychainLocked(String),
KeychainNotFound { service: String, account: String },
BrainUnavailable(String),
Io(String),
}
impl std::fmt::Display for FoundationError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::UnsupportedSchema(v) => write!(f, "unsupported foundation schema_version {v}"),
Self::IncompatibleHost(s) => write!(f, "host compatibility check failed: {s}"),
Self::Parse(s) => write!(f, "foundation parse error: {s}"),
Self::SecretNotAllowed(s) => write!(f, "secret not allowed in foundation file: {s}"),
Self::DuplicateProfileId(id) => write!(f, "duplicate profile id: {id}"),
Self::UnsupportedRequirement(req) => {
write!(f, "unsupported package requirement: {req}")
}
Self::DigestMismatch {
package,
expected,
actual,
} => write!(
f,
"package {package} digest mismatch: expected {expected}, got {actual}"
),
Self::TargetMismatch { package, targets } => write!(
f,
"package {package} targets do not include `oxicode`: {targets:?}"
),
Self::UnknownProfile(id) => write!(f, "unknown profile id: {id}"),
Self::UnknownRole(r) => write!(f, "no profile matches requested role: {r}"),
Self::AmbiguousRole(r) => write!(f, "multiple profiles match role {r}"),
Self::KeychainUnavailable(s) => write!(f, "keychain unavailable: {s}"),
Self::KeychainLocked(s) => write!(f, "keychain locked: {s}"),
Self::KeychainNotFound { service, account } => {
write!(f, "keychain entry not found for {service}:{account}")
}
Self::BrainUnavailable(s) => write!(f, "brain daemon unavailable: {s}"),
Self::Io(s) => write!(f, "foundation I/O error: {s}"),
}
}
}
impl std::error::Error for FoundationError {}
impl From<std::io::Error> for FoundationError {
fn from(e: std::io::Error) -> Self {
Self::Io(e.to_string())
}
}
impl From<serde_json::Error> for FoundationError {
fn from(e: serde_json::Error) -> Self {
Self::Parse(e.to_string())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CredentialSource {
Environment,
Profile,
Role,
CompatibilityImport,
Unavailable,
}
impl std::fmt::Display for CredentialSource {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Environment => f.write_str("environment"),
Self::Profile => f.write_str("profile"),
Self::Role => f.write_str("role"),
Self::CompatibilityImport => f.write_str("compatibility_import"),
Self::Unavailable => f.write_str("unavailable"),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn foundation_root_honors_env_override() {
let tmp = tempfile::tempdir().unwrap();
let original = std::env::var("OXI_FOUNDATION_HOME").ok();
unsafe {
std::env::set_var("OXI_FOUNDATION_HOME", tmp.path());
}
let root = foundation_root().unwrap();
unsafe {
std::env::remove_var("OXI_FOUNDATION_HOME");
}
if let Some(value) = original {
unsafe {
std::env::set_var("OXI_FOUNDATION_HOME", value);
}
}
assert_eq!(root, tmp.path());
}
#[test]
fn foundation_present_detects_layout() {
let tmp = tempfile::tempdir().unwrap();
assert!(!foundation_present(tmp.path()));
std::fs::write(tmp.path().join(files::FOUNDATION), "{}").unwrap();
std::fs::write(tmp.path().join(files::PROFILES), "{}").unwrap();
assert!(foundation_present(tmp.path()));
}
#[test]
fn credential_source_display_roundtrip() {
assert_eq!(CredentialSource::Environment.to_string(), "environment");
assert_eq!(CredentialSource::Profile.to_string(), "profile");
assert_eq!(CredentialSource::Role.to_string(), "role");
assert_eq!(
CredentialSource::CompatibilityImport.to_string(),
"compatibility_import"
);
assert_eq!(CredentialSource::Unavailable.to_string(), "unavailable");
}
}