use reqwest::header::HeaderValue;
use url::Url;
use super::ApiKey;
use crate::KitError;
use crate::error::ValidationViolation;
const DEFAULT_OPENAI_BASE_URL: &str = "https://api.openai.com/v1";
#[derive(Clone)]
pub struct OpenAiConfig {
api_key: ApiKey,
base_url: Url,
organization: Option<String>,
project: Option<String>,
}
impl OpenAiConfig {
#[must_use]
pub fn new(api_key: ApiKey) -> Self {
Self {
api_key,
base_url: Url::parse(DEFAULT_OPENAI_BASE_URL)
.expect("crate-owned OpenAI base URL should parse"),
organization: None,
project: None,
}
}
#[must_use]
pub fn with_base_url(mut self, base_url: Url) -> Self {
self.base_url = base_url;
self
}
pub fn with_organization(mut self, organization: impl Into<String>) -> Result<Self, KitError> {
self.organization = Some(Self::scope(
"credentials.organization",
organization.into(),
)?);
Ok(self)
}
pub fn with_project(mut self, project: impl Into<String>) -> Result<Self, KitError> {
self.project = Some(Self::scope("credentials.project", project.into())?);
Ok(self)
}
pub(crate) fn api_key(&self) -> &ApiKey {
&self.api_key
}
pub(crate) fn base_url(&self) -> &Url {
&self.base_url
}
pub(crate) fn organization(&self) -> Option<&str> {
self.organization.as_deref()
}
pub(crate) fn project(&self) -> Option<&str> {
self.project.as_deref()
}
fn scope(label: &'static str, value: String) -> Result<String, KitError> {
if value.is_empty() || value.trim() != value || HeaderValue::try_from(&value).is_err() {
Err(KitError::config(
ValidationViolation::InvalidFormat {
field: label,
rule: "nonblank, unpadded HTTP header value",
},
format!("{label} must be a nonblank, unpadded HTTP header value"),
))
} else {
Ok(value)
}
}
}