use std::collections::HashSet;
use std::path::Path;
use serde::{Deserialize, Serialize};
use super::FoundationError;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProfilesFile {
pub schema_version: u32,
#[serde(default)]
pub profiles: Vec<Profile>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Profile {
pub id: String,
pub provider: String,
pub model: String,
#[serde(default)]
pub roles: Vec<String>,
pub credential: CredentialLocator,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct CredentialLocator {
pub service: String,
pub account: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ResolvedProfile {
pub profile: Profile,
pub source: super::CredentialSource,
}
pub fn read(path: &Path) -> Result<ProfilesFile, FoundationError> {
let raw = std::fs::read_to_string(path)?;
let parsed: ProfilesFile = serde_json::from_str(&raw)?;
parsed.validate()?;
Ok(parsed)
}
impl ProfilesFile {
pub fn validate(&self) -> Result<(), FoundationError> {
if self.schema_version != 1 {
return Err(FoundationError::UnsupportedSchema(self.schema_version));
}
let mut seen = HashSet::new();
for p in &self.profiles {
if p.id.is_empty() {
return Err(FoundationError::Parse("profile id is empty".to_string()));
}
if !seen.insert(p.id.clone()) {
return Err(FoundationError::DuplicateProfileId(p.id.clone()));
}
if p.provider.is_empty() {
return Err(FoundationError::Parse(format!(
"profile {} has empty provider",
p.id
)));
}
if p.model.is_empty() {
return Err(FoundationError::Parse(format!(
"profile {} has empty model",
p.id
)));
}
if p.roles.is_empty() {
return Err(FoundationError::Parse(format!(
"profile {} has no roles",
p.id
)));
}
for r in &p.roles {
if r.is_empty() {
return Err(FoundationError::Parse(format!(
"profile {} has empty role",
p.id
)));
}
}
p.credential.validate()?;
check_secret_keys_absent(p)?;
}
Ok(())
}
pub fn find(&self, id: &str) -> Option<&Profile> {
self.profiles.iter().find(|p| p.id == id)
}
pub fn for_role(&self, role: &str) -> Vec<&Profile> {
self.profiles
.iter()
.filter(|p| p.roles.iter().any(|r| r == role))
.collect()
}
}
impl CredentialLocator {
pub fn validate(&self) -> Result<(), FoundationError> {
if self.service.is_empty() {
return Err(FoundationError::Parse(
"credential.service is empty".to_string(),
));
}
if self.account.is_empty() {
return Err(FoundationError::Parse(
"credential.account is empty".to_string(),
));
}
Ok(())
}
}
pub fn resolve_profile(input: ResolveInput<'_>) -> Result<ResolvedProfile, FoundationError> {
if let Some(env) = input.explicit_environment_override
&& let Some(profile) = env_into_profile(env)
{
return Ok(ResolvedProfile {
profile,
source: super::CredentialSource::Environment,
});
}
if let Some(id) = input.explicit_profile {
let profile = input
.foundation_profiles
.find(id)
.ok_or_else(|| FoundationError::UnknownProfile(id.to_string()))?
.clone();
return Ok(ResolvedProfile {
profile,
source: super::CredentialSource::Profile,
});
}
if let Some(role) = input.requested_role {
let matches = input.foundation_profiles.for_role(role);
match matches.as_slice() {
[] => return Err(FoundationError::UnknownRole(role.to_string())),
[one] => {
return Ok(ResolvedProfile {
profile: (*one).clone(),
source: super::CredentialSource::Role,
});
}
_ => return Err(FoundationError::AmbiguousRole(role.to_string())),
}
}
if let Some(import) = input.compatibility_import {
return Ok(ResolvedProfile {
profile: import.profile.clone(),
source: super::CredentialSource::CompatibilityImport,
});
}
Err(FoundationError::UnknownProfile(
"no profile, role, environment override, or compatibility import provided".to_string(),
))
}
#[derive(Debug, Clone)]
pub struct ResolveInput<'a> {
pub explicit_profile: Option<&'a str>,
pub explicit_environment_override: Option<&'a EnvironmentOverride>,
pub requested_role: Option<&'a str>,
pub foundation_profiles: &'a ProfilesFile,
pub compatibility_import: Option<&'a CompatibilityImport>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct EnvironmentOverride {
pub provider: String,
pub model: String,
}
impl EnvironmentOverride {
pub fn from_env() -> Option<Self> {
let provider = std::env::var("OXICODE_PROVIDER").ok()?;
let model = std::env::var("OXICODE_MODEL").ok()?;
if provider.trim().is_empty() || model.trim().is_empty() {
return None;
}
Some(Self {
provider: provider.trim().to_string(),
model: model.trim().to_string(),
})
}
}
#[derive(Debug, Clone)]
pub struct CompatibilityImport {
pub profile: Profile,
}
fn env_into_profile(env: &EnvironmentOverride) -> Option<Profile> {
if env.provider.is_empty() || env.model.is_empty() {
return None;
}
Some(Profile {
id: "__env_override__".to_string(),
provider: env.provider.clone(),
model: env.model.clone(),
roles: vec![],
credential: CredentialLocator {
service: "__env__".to_string(),
account: "__env__".to_string(),
},
})
}
const SECRET_FIELD_NAMES: &[&str] = &[
"api_key",
"apikey",
"api-key",
"bearer_token",
"bearer",
"password",
"secret",
"secret_value",
"private_key",
"private-key",
"access_token",
"refresh_token",
"session_token",
"oauth_token",
];
fn check_secret_keys_absent(profile: &Profile) -> Result<(), FoundationError> {
let value = serde_json::to_value(profile).map_err(|e| FoundationError::Parse(e.to_string()))?;
if let Some(obj) = value.as_object() {
for k in obj.keys() {
let lower = k.to_ascii_lowercase();
if SECRET_FIELD_NAMES
.iter()
.any(|s| s.eq_ignore_ascii_case(&lower))
{
return Err(FoundationError::SecretNotAllowed(k.clone()));
}
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
fn pf(json: &str) -> ProfilesFile {
let f: ProfilesFile = serde_json::from_str(json).unwrap();
f.validate().unwrap();
f
}
fn profile(id: &str, provider: &str, model: &str, roles: &[&str]) -> Profile {
Profile {
id: id.to_string(),
provider: provider.to_string(),
model: model.to_string(),
roles: roles.iter().map(|s| s.to_string()).collect(),
credential: CredentialLocator {
service: "dev.oxi.foundation".to_string(),
account: id.to_string(),
},
}
}
#[test]
fn rejects_duplicate_profile_ids() {
let raw = r#"{
"schema_version": 1,
"profiles": [
{"id":"a","provider":"openai","model":"gpt-4o","roles":["x"],"credential":{"service":"s","account":"a"}},
{"id":"a","provider":"openai","model":"gpt-4","roles":["y"],"credential":{"service":"s","account":"a"}}
]
}"#;
let err = serde_json::from_str::<ProfilesFile>(raw)
.unwrap()
.validate()
.unwrap_err();
assert!(matches!(err, FoundationError::DuplicateProfileId(id) if id == "a"));
}
#[test]
fn rejects_secret_in_profile() {
let raw = r#"{
"schema_version": 1,
"profiles": [
{"id":"a","provider":"openai","model":"gpt-4o","roles":["x"], "api_key":"sk-xxx",
"credential":{"service":"s","account":"a"}}
]
}"#;
let result = serde_json::from_str::<ProfilesFile>(raw);
let err = match result {
Ok(file) => file.validate().unwrap_err(),
Err(e) => FoundationError::Parse(e.to_string()),
};
assert!(matches!(
err,
FoundationError::Parse(_) | FoundationError::SecretNotAllowed(_)
));
}
#[test]
fn resolve_profile_prefers_env_override() {
let f = pf(r#"{"schema_version":1,"profiles":[
{"id":"x","provider":"openai","model":"gpt-4o","roles":["coding.primary"],
"credential":{"service":"s","account":"x"}}
]}"#);
let env = EnvironmentOverride {
provider: "anthropic".to_string(),
model: "claude-sonnet".to_string(),
};
let resolved = resolve_profile(ResolveInput {
explicit_profile: Some("x"),
explicit_environment_override: Some(&env),
requested_role: None,
foundation_profiles: &f,
compatibility_import: None,
})
.unwrap();
assert_eq!(resolved.source, super::super::CredentialSource::Environment);
assert_eq!(resolved.profile.provider, "anthropic");
}
#[test]
fn resolve_profile_explicit_id() {
let f = pf(r#"{"schema_version":1,"profiles":[
{"id":"x","provider":"openai","model":"gpt-4o","roles":["coding.primary"],
"credential":{"service":"s","account":"x"}}
]}"#);
let resolved = resolve_profile(ResolveInput {
explicit_profile: Some("x"),
explicit_environment_override: None,
requested_role: None,
foundation_profiles: &f,
compatibility_import: None,
})
.unwrap();
assert_eq!(resolved.profile.id, "x");
assert_eq!(resolved.source, super::super::CredentialSource::Profile);
}
#[test]
fn resolve_profile_unknown_id_is_error() {
let f = pf(r#"{"schema_version":1,"profiles":[
{"id":"x","provider":"openai","model":"gpt-4o","roles":["coding.primary"],
"credential":{"service":"s","account":"x"}}
]}"#);
let err = resolve_profile(ResolveInput {
explicit_profile: Some("nope"),
explicit_environment_override: None,
requested_role: None,
foundation_profiles: &f,
compatibility_import: None,
})
.unwrap_err();
assert!(matches!(err, FoundationError::UnknownProfile(_)));
}
#[test]
fn resolve_profile_role_unique() {
let f = pf(r#"{"schema_version":1,"profiles":[
{"id":"a","provider":"openai","model":"gpt-4o","roles":["coding.primary"],
"credential":{"service":"s","account":"a"}}
]}"#);
let resolved = resolve_profile(ResolveInput {
explicit_profile: None,
explicit_environment_override: None,
requested_role: Some("coding.primary"),
foundation_profiles: &f,
compatibility_import: None,
})
.unwrap();
assert_eq!(resolved.source, super::super::CredentialSource::Role);
}
#[test]
fn resolve_profile_role_ambiguous() {
let f = pf(r#"{"schema_version":1,"profiles":[
{"id":"a","provider":"openai","model":"gpt-4o","roles":["coding.primary"],
"credential":{"service":"s","account":"a"}},
{"id":"b","provider":"anthropic","model":"claude","roles":["coding.primary"],
"credential":{"service":"s","account":"b"}}
]}"#);
let err = resolve_profile(ResolveInput {
explicit_profile: None,
explicit_environment_override: None,
requested_role: Some("coding.primary"),
foundation_profiles: &f,
compatibility_import: None,
})
.unwrap_err();
assert!(matches!(err, FoundationError::AmbiguousRole(_)));
}
#[test]
fn resolve_profile_role_unknown() {
let f = pf(r#"{"schema_version":1,"profiles":[
{"id":"a","provider":"openai","model":"gpt-4o","roles":["coding.primary"],
"credential":{"service":"s","account":"a"}}
]}"#);
let err = resolve_profile(ResolveInput {
explicit_profile: None,
explicit_environment_override: None,
requested_role: Some("nonexistent"),
foundation_profiles: &f,
compatibility_import: None,
})
.unwrap_err();
assert!(matches!(err, FoundationError::UnknownRole(_)));
}
#[test]
fn resolve_profile_no_inputs_is_error() {
let f = pf(r#"{"schema_version":1,"profiles":[
{"id":"a","provider":"openai","model":"gpt-4o","roles":["coding.primary"],
"credential":{"service":"s","account":"a"}}
]}"#);
let err = resolve_profile(ResolveInput {
explicit_profile: None,
explicit_environment_override: None,
requested_role: None,
foundation_profiles: &f,
compatibility_import: None,
})
.unwrap_err();
assert!(matches!(err, FoundationError::UnknownProfile(_)));
}
#[test]
fn resolve_profile_compatibility_import_only() {
let f = pf(r#"{"schema_version":1,"profiles":[]}"#);
let p = profile("legacy", "anthropic", "claude-sonnet", &["coding.primary"]);
let import = CompatibilityImport { profile: p.clone() };
let resolved = resolve_profile(ResolveInput {
explicit_profile: None,
explicit_environment_override: None,
requested_role: None,
foundation_profiles: &f,
compatibility_import: Some(&import),
})
.unwrap();
assert_eq!(
resolved.source,
super::super::CredentialSource::CompatibilityImport
);
assert_eq!(resolved.profile.id, "legacy");
}
}