use std::fmt;
use crate::Error;
use crate::model::CompletionError;
#[derive(Clone)]
#[non_exhaustive]
pub struct SecretString(String);
impl SecretString {
pub fn new(secret: impl Into<String>) -> std::result::Result<SecretString, SecretError> {
let secret = secret.into();
if secret.is_empty() {
return Err(SecretError::Empty);
}
Ok(SecretString(secret))
}
pub(crate) fn disabled_placeholder() -> SecretString {
SecretString(String::new())
}
pub(crate) fn expose(&self) -> &str {
&self.0
}
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
#[non_exhaustive]
pub enum SecretError {
#[error("secret must not be empty")]
Empty,
}
impl From<SecretError> for CompletionError {
fn from(error: SecretError) -> CompletionError {
CompletionError::from(Error::Config {
message: "gateway bearer key is unusable".to_owned(),
source: Box::new(error),
})
}
}
impl fmt::Debug for SecretString {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("SecretString(<redacted>)")
}
}
impl fmt::Display for SecretString {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("<redacted>")
}
}
#[non_exhaustive]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct GatewayEndpoint {
pub(crate) url: String,
}
impl GatewayEndpoint {
pub fn new(url: &str) -> std::result::Result<GatewayEndpoint, CompletionError> {
let reject = |detail: String| CompletionError::from(Error::MissingEnv(detail));
let trimmed = url.trim();
let parsed = url::Url::parse(trimmed).map_err(|error| {
CompletionError::from(Error::Config {
message: format!("gateway URL is not a valid URL: {trimmed:?}"),
source: Box::new(error),
})
})?;
if !matches!(parsed.scheme(), "http" | "https") {
return Err(reject(format!(
"gateway URL must use the http or https scheme: {trimmed:?}"
)));
}
match parsed.host_str() {
None | Some("") => {
return Err(reject(format!("gateway URL names no host: {trimmed:?}")));
}
Some(_) => {}
}
if !parsed.username().is_empty() || parsed.password().is_some() {
return Err(reject(
"gateway URL must not embed credentials (user:pass@)".to_owned(),
));
}
if parsed.query().is_some() || parsed.fragment().is_some() {
return Err(reject(
"gateway URL must not carry a query or fragment".to_owned(),
));
}
Ok(GatewayEndpoint {
url: parsed.as_str().trim_end_matches('/').to_string(),
})
}
#[must_use]
pub fn url(&self) -> &str {
&self.url
}
}
impl TryFrom<&str> for GatewayEndpoint {
type Error = CompletionError;
fn try_from(url: &str) -> std::result::Result<GatewayEndpoint, CompletionError> {
GatewayEndpoint::new(url)
}
}