use crate::error::{ErrorData, Result};
use crate::LoadBalancerEndpoint;
use alien_error::AlienError;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
#[serde(rename_all = "lowercase")]
pub enum ExposeProtocol {
Http,
Tcp,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
#[serde(rename_all = "camelCase")]
pub struct PublicEndpoint {
pub name: String,
pub port: u16,
pub protocol: ExposeProtocol,
#[serde(skip_serializing_if = "Option::is_none")]
pub host_label: Option<String>,
#[serde(default)]
pub wildcard_subdomains: bool,
}
impl PublicEndpoint {
pub fn effective_host_label(&self) -> &str {
self.host_label.as_deref().unwrap_or(&self.name)
}
pub fn validate_for_resource(&self, resource_id: &str) -> Result<()> {
validate_endpoint_name(resource_id, &self.name)?;
if let Some(host_label) = &self.host_label {
validate_endpoint_host_label(resource_id, host_label)?;
}
Ok(())
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
#[serde(rename_all = "camelCase")]
pub struct WorkerPublicEndpoint {
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub host_label: Option<String>,
#[serde(default)]
pub wildcard_subdomains: bool,
}
impl WorkerPublicEndpoint {
pub fn effective_host_label(&self) -> &str {
self.host_label.as_deref().unwrap_or(&self.name)
}
pub fn validate_for_resource(&self, resource_id: &str) -> Result<()> {
validate_endpoint_name(resource_id, &self.name)?;
if let Some(host_label) = &self.host_label {
validate_endpoint_host_label(resource_id, host_label)?;
}
Ok(())
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
#[serde(rename_all = "camelCase")]
pub struct PublicEndpointOutput {
pub url: String,
pub host: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub wildcard_host: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub load_balancer_endpoint: Option<LoadBalancerEndpoint>,
}
pub fn validate_endpoint_name(resource_id: &str, name: &str) -> Result<()> {
let valid = !name.is_empty()
&& name.len() <= 63
&& !name.starts_with('-')
&& !name.ends_with('-')
&& name
.bytes()
.all(|b| b.is_ascii_lowercase() || b.is_ascii_digit() || b == b'-');
if !valid {
return Err(AlienError::new(ErrorData::InvalidResourceUpdate {
resource_id: resource_id.to_string(),
reason:
"public endpoint name must be a single lowercase DNS label: letters, numbers, hyphens, no dots, and no leading or trailing hyphen"
.to_string(),
}));
}
Ok(())
}
pub fn validate_endpoint_host_label(resource_id: &str, host_label: &str) -> Result<()> {
let valid = !host_label.is_empty()
&& host_label.len() <= 63
&& !host_label.starts_with('-')
&& !host_label.ends_with('-')
&& host_label
.bytes()
.all(|b| b.is_ascii_lowercase() || b.is_ascii_digit() || b == b'-');
if !valid {
return Err(AlienError::new(ErrorData::InvalidResourceUpdate {
resource_id: resource_id.to_string(),
reason:
"public endpoint hostLabel must be a single lowercase DNS label: letters, numbers, hyphens, no dots, and no leading or trailing hyphen"
.to_string(),
}));
}
Ok(())
}