feat(proxy): add proxy service module with configuration handling

- Introduced a new `proxy` module in the service layer.
- Created `ProxyServiceTrait` for managing proxy configurations.
- Implemented various configuration types including access rules, cache zones, limit rules, and more.
- Added rendering logic for Nginx configuration components.
- Developed a repository implementation for fetching and merging proxy configurations from the database.
- Enhanced error handling with `ProxyServiceError` for better clarity on configuration issues.
This commit is contained in:
GW_MC
2026-06-21 11:07:13 +00:00
parent 2dbab85c87
commit 9c07fce607
18 changed files with 1170 additions and 0 deletions

View File

@@ -6,6 +6,7 @@ use crate::{connector::agent::AgentConnectorTrait, service::certificate::Certifi
pub mod agent; pub mod agent;
pub mod certificate; pub mod certificate;
pub mod proxy;
pub async fn start_master_server( pub async fn start_master_server(
settings: crate::config::settings::Settings, settings: crate::config::settings::Settings,

View File

@@ -0,0 +1,12 @@
use crate::service::proxy::types::{ProxyConfig, ProxyServiceResult};
pub(crate) mod nginx;
pub(crate) mod repo;
pub mod service;
pub mod types;
#[async_trait::async_trait]
pub trait ProxyServiceTrait: Send + Sync + 'static {
async fn get_proxy_config(&self, proxy_id: uuid::Uuid) -> ProxyServiceResult<ProxyConfig>;
}

View File

@@ -0,0 +1,7 @@
use crate::db::entities::access_rule::Model as AccessRule;
impl std::fmt::Display for AccessRule {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{} {};", self.r#type.clone(), self.ip_cidr.clone())
}
}

View File

@@ -0,0 +1,11 @@
use crate::db::entities::cache_zone::Model as CacheZone;
impl std::fmt::Display for CacheZone {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"proxy_cache_path {} levels=1:2 keys_zone={}:{};",
self.path, self.name, self.size_limit
)
}
}

View File

@@ -0,0 +1,19 @@
use crate::db::entities::limit_rule::Model as LimitRule;
pub struct LimitRuleRender {
pub rule: LimitRule,
pub zone_name: String,
}
impl std::fmt::Display for LimitRuleRender {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "limit_req zone={}", self.zone_name)?;
if let Some(burst) = self.rule.burst {
write!(f, " burst={}", burst)?;
}
if self.rule.nodelay.unwrap_or(false) {
write!(f, " nodelay")?;
}
write!(f, ";")
}
}

View File

@@ -0,0 +1,11 @@
use crate::db::entities::limit_zone::Model as LimitZone;
impl std::fmt::Display for LimitZone {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"limit_req_zone {} zone={}:{};",
self.key, self.name, self.rate
)
}
}

View File

@@ -0,0 +1,44 @@
use crate::db::entities::location_block::Model as LocationBlock;
pub struct LocationBlockRender {
pub block: LocationBlock,
pub upstream_name: Option<String>,
pub access_rules: Vec<String>,
pub rewrite_rules: Vec<String>,
pub proxy_setting: Option<String>,
pub limit_rules: Vec<String>,
}
impl std::fmt::Display for LocationBlockRender {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(f, " location {} {{", self.block.path_pattern)?;
if let Some(ref upstream) = self.upstream_name {
writeln!(f, " proxy_pass http://{};", upstream)?;
writeln!(f, " proxy_set_header Host $host;")?;
writeln!(f, " proxy_set_header X-Real-IP $remote_addr;")?;
}
if let Some(ref settings) = self.proxy_setting {
for line in settings.lines() {
if !line.is_empty() {
writeln!(f, "{}", line)?;
}
}
}
for rule in &self.access_rules {
writeln!(f, " {}", rule)?;
}
for rule in &self.rewrite_rules {
writeln!(f, " {}", rule)?;
}
for rule in &self.limit_rules {
writeln!(f, " {}", rule)?;
}
writeln!(f, " }}")
}
}

View File

@@ -0,0 +1,21 @@
use crate::db::entities::log_setting::Model as LogSetting;
impl std::fmt::Display for LogSetting {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if let Some(ref path) = self.access_log_path {
if let Some(ref level) = self.log_level {
writeln!(f, " access_log {} {};", path, level)?;
} else {
writeln!(f, " access_log {};", path)?;
}
}
if let Some(ref path) = self.error_log_path {
if let Some(ref level) = self.log_level {
writeln!(f, " error_log {} {};", path, level)?;
} else {
writeln!(f, " error_log {};", path)?;
}
}
Ok(())
}
}

View File

@@ -0,0 +1,11 @@
mod access_rule;
mod cache_zone;
mod limit_rule;
mod limit_zone;
mod location_block;
mod log_setting;
mod proxy_setting;
mod rewrite_rule;
mod server_block;
mod ssl_certificate;
mod upstream;

View File

@@ -0,0 +1,35 @@
use crate::db::entities::proxy_setting::Model as ProxySetting;
pub struct ProxySettingRender {
pub setting: ProxySetting,
pub cache_zone_name: Option<String>,
}
impl ProxySettingRender {
fn render_timeout(value: Option<i32>, directive: &str) -> Option<String> {
value.map(|v| format!(" {} {}s;", directive, v))
}
}
impl std::fmt::Display for ProxySettingRender {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if let Some(line) = Self::render_timeout(self.setting.read_timeout, "proxy_read_timeout")
{
writeln!(f, "{}", line)?;
}
if let Some(line) =
Self::render_timeout(self.setting.connect_timeout, "proxy_connect_timeout")
{
writeln!(f, "{}", line)?;
}
if let Some(buffer) = self.setting.buffer_size {
writeln!(f, " proxy_buffer_size {};", buffer)?;
}
if self.setting.cache_enabled.unwrap_or(false) {
if let Some(ref zone_name) = self.cache_zone_name {
writeln!(f, " proxy_cache {};", zone_name)?;
}
}
Ok(())
}
}

View File

@@ -0,0 +1,11 @@
use crate::db::entities::rewrite_rule::Model as RewriteRule;
impl std::fmt::Display for RewriteRule {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "rewrite {} {}", self.pattern, self.replacement)?;
if let Some(ref flag) = self.flag {
write!(f, " {}", flag)?;
}
write!(f, ";")
}
}

View File

@@ -0,0 +1,49 @@
use crate::db::entities::server_block::Model as ServerBlock;
pub struct ServerBlockRender {
pub block: ServerBlock,
pub ssl_cert: Option<String>,
pub locations: Vec<String>,
pub access_rules: Vec<String>,
pub log_setting: Option<String>,
}
impl std::fmt::Display for ServerBlockRender {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(f, "server {{")?;
if self.block.ssl_enabled.unwrap_or(false) {
writeln!(f, " listen {} ssl;", self.block.listen_port)?;
} else {
writeln!(f, " listen {};", self.block.listen_port)?;
}
if let Some(ref names) = self.block.server_name {
if !names.is_empty() {
writeln!(f, " server_name {};", names.join(" "))?;
}
}
if let Some(ref cert) = self.ssl_cert {
writeln!(f, "{}", cert)?;
}
if let Some(ref log) = self.log_setting {
for line in log.lines() {
if !line.is_empty() {
writeln!(f, "{}", line)?;
}
}
}
for rule in &self.access_rules {
writeln!(f, " {}", rule)?;
}
for loc in &self.locations {
writeln!(f, "{}", loc)?;
}
write!(f, "}}")
}
}

View File

@@ -0,0 +1,8 @@
use crate::db::entities::ssl_certificate::Model as SslCertificate;
impl std::fmt::Display for SslCertificate {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(f, " ssl_certificate {};", self.cert_path)?;
writeln!(f, " ssl_certificate_key {};", self.key_path)
}
}

View File

@@ -0,0 +1,7 @@
use crate::db::entities::upstream::Model as Upstream;
impl std::fmt::Display for Upstream {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, " server {}:{};", self.target_host, self.target_port)
}
}

View File

@@ -0,0 +1 @@
pub mod config;

View File

@@ -0,0 +1,444 @@
use std::collections::HashMap;
use sea_orm::{DatabaseConnection, prelude::*};
use crate::service::proxy::types::{
Mergeable, OverrideRef, ProxyConfig, ProxyServiceError, ProxyServiceResult, ProxyType,
};
#[async_trait::async_trait]
pub trait ProxyRepo: Send + Sync + 'static {
// get the raw config for the given proxy_id. This should return the config of the given proxy_id without merging it with its parent configs (if any).
async fn get_proxy_raw_config(&self, proxy_id: uuid::Uuid) -> ProxyServiceResult<ProxyConfig>;
// get the raw config for the given proxy_id. This should return the config of the given proxy_id and all its parent configs (if any) without merging them. The returned vector is ordered from the leaf (given proxy_id) down to the root config (most specific to least specific).
async fn get_proxy_raw_configs(
&self,
proxy_id: uuid::Uuid,
) -> ProxyServiceResult<Vec<ProxyConfig>>;
// get the merged config for the given proxy_id. This should merge the config of the given proxy_id with its parent configs (if any) and return the final merged config.
async fn get_merged_proxy_config(
&self,
proxy_id: uuid::Uuid,
) -> ProxyServiceResult<ProxyConfig>;
}
pub(crate) struct ProxyRepoImpl {
db: DatabaseConnection,
}
impl ProxyRepoImpl {
pub fn new(db: DatabaseConnection) -> Self {
Self { db }
}
}
#[async_trait::async_trait]
impl ProxyRepo for ProxyRepoImpl {
async fn get_proxy_raw_configs(
&self,
proxy_config_id: uuid::Uuid,
) -> ProxyServiceResult<Vec<ProxyConfig>> {
let mut proxy_config_id_frontier = vec![proxy_config_id];
let mut visited = std::collections::HashSet::new();
let mut configs: Vec<ProxyConfig> = Vec::new();
while let Some(current_id) = proxy_config_id_frontier.pop() {
if visited.contains(&current_id) {
continue;
}
visited.insert(current_id);
//
let config = self.get_proxy_raw_config(current_id).await?;
//
if let Some(parent_ids) = &config.parent_config_id {
proxy_config_id_frontier.extend(parent_ids);
}
configs.push(config);
}
Ok(configs)
}
async fn get_proxy_raw_config(&self, proxy_id: uuid::Uuid) -> ProxyServiceResult<ProxyConfig> {
let proxy_entity = crate::db::entities::proxy_config::Entity::find_by_id(proxy_id)
.one(&self.db)
.await?
.ok_or(ProxyServiceError::ConfigNotFound)?;
// Resolve parent configs from config_inheritance
let parent_config_id = {
let inheritance = crate::db::entities::config_inheritance::Entity::find()
.filter(crate::db::entities::config_inheritance::Column::ChildConfigId.eq(proxy_id))
.all(&self.db)
.await?;
if inheritance.is_empty() {
None
} else {
Some(
inheritance
.into_iter()
.map(|ci| ci.parent_config_id)
.collect(),
)
}
};
// Direct children of proxy_config
let server_blocks = crate::db::entities::server_block::Entity::find()
.filter(crate::db::entities::server_block::Column::ConfigId.eq(proxy_id))
.all(&self.db)
.await?;
let upstreams = crate::db::entities::upstream::Entity::find()
.filter(crate::db::entities::upstream::Column::ConfigId.eq(proxy_id))
.all(&self.db)
.await?;
// Children of server_blocks
let server_block_ids: Vec<uuid::Uuid> = server_blocks.iter().map(|sb| sb.id).collect();
let (location_blocks, log_settings, server_access_rules) = if server_block_ids.is_empty() {
(vec![], vec![], vec![])
} else {
(
crate::db::entities::location_block::Entity::find()
.filter(
crate::db::entities::location_block::Column::ServerId
.is_in(server_block_ids.clone()),
)
.all(&self.db)
.await?,
crate::db::entities::log_setting::Entity::find()
.filter(
crate::db::entities::log_setting::Column::ServerId
.is_in(server_block_ids.clone()),
)
.all(&self.db)
.await?,
crate::db::entities::access_rule::Entity::find()
.filter(
crate::db::entities::access_rule::Column::ServerId.is_in(server_block_ids),
)
.all(&self.db)
.await?,
)
};
// Children of location_blocks
let location_block_ids: Vec<uuid::Uuid> = location_blocks.iter().map(|lb| lb.id).collect();
let (location_access_rules, limit_rules, proxy_settings, rewrite_rules) =
if location_block_ids.is_empty() {
(vec![], vec![], vec![], vec![])
} else {
(
crate::db::entities::access_rule::Entity::find()
.filter(
crate::db::entities::access_rule::Column::LocationId
.is_in(location_block_ids.clone()),
)
.all(&self.db)
.await?,
crate::db::entities::limit_rule::Entity::find()
.filter(
crate::db::entities::limit_rule::Column::LocationId
.is_in(location_block_ids.clone()),
)
.all(&self.db)
.await?,
crate::db::entities::proxy_setting::Entity::find()
.filter(
crate::db::entities::proxy_setting::Column::LocationId
.is_in(location_block_ids.clone()),
)
.all(&self.db)
.await?,
crate::db::entities::rewrite_rule::Entity::find()
.filter(
crate::db::entities::rewrite_rule::Column::LocationId
.is_in(location_block_ids),
)
.all(&self.db)
.await?,
)
};
// Collect referenced IDs for zone and cert lookups
let limit_zone_ids: Vec<uuid::Uuid> = limit_rules.iter().map(|lr| lr.zone_id).collect();
let cache_zone_ids: Vec<uuid::Uuid> = proxy_settings
.iter()
.filter_map(|ps| ps.cache_zone)
.collect();
let ssl_cert_ids: Vec<uuid::Uuid> = server_blocks
.iter()
.filter_map(|sb| sb.ssl_cert_id)
.collect();
let limit_zones = if limit_zone_ids.is_empty() {
vec![]
} else {
crate::db::entities::limit_zone::Entity::find()
.filter(crate::db::entities::limit_zone::Column::Id.is_in(limit_zone_ids))
.all(&self.db)
.await?
};
let cache_zones = if cache_zone_ids.is_empty() {
vec![]
} else {
crate::db::entities::cache_zone::Entity::find()
.filter(crate::db::entities::cache_zone::Column::Id.is_in(cache_zone_ids))
.all(&self.db)
.await?
};
let ssl_certificates = if ssl_cert_ids.is_empty() {
vec![]
} else {
crate::db::entities::ssl_certificate::Entity::find()
.filter(crate::db::entities::ssl_certificate::Column::Id.is_in(ssl_cert_ids))
.all(&self.db)
.await?
};
// ── Group child IDs by parent ────────────────────────────────────────
// location_block IDs grouped by server_id
let loc_block_ids_by_server: HashMap<uuid::Uuid, Vec<OverrideRef>> = {
let mut map: HashMap<_, Vec<_>> = HashMap::new();
for lb in &location_blocks {
map.entry(lb.server_id).or_default().push(OverrideRef {
id: lb.id,
override_of_id: lb.override_of_id,
});
}
map
};
// log_setting IDs grouped by server_id
let log_setting_ids_by_server: HashMap<uuid::Uuid, Vec<OverrideRef>> = {
let mut map: HashMap<_, Vec<_>> = HashMap::new();
for ls in &log_settings {
map.entry(ls.server_id).or_default().push(OverrideRef {
id: ls.id,
override_of_id: ls.override_of_id,
});
}
map
};
// server-level access_rule IDs grouped by server_id
let server_ar_ids_by_server: HashMap<uuid::Uuid, Vec<OverrideRef>> = {
let mut map: HashMap<_, Vec<_>> = HashMap::new();
for ar in &server_access_rules {
if let Some(sid) = ar.server_id {
map.entry(sid).or_default().push(OverrideRef {
id: ar.id,
override_of_id: ar.override_of_id,
});
}
}
map
};
// location-level access_rule IDs grouped by location_id
let loc_ar_ids_by_location: HashMap<uuid::Uuid, Vec<OverrideRef>> = {
let mut map: HashMap<_, Vec<_>> = HashMap::new();
for ar in &location_access_rules {
if let Some(lid) = ar.location_id {
map.entry(lid).or_default().push(OverrideRef {
id: ar.id,
override_of_id: ar.override_of_id,
});
}
}
map
};
// limit_rule IDs grouped by location_id
let lr_ids_by_location: HashMap<uuid::Uuid, Vec<OverrideRef>> = {
let mut map: HashMap<_, Vec<_>> = HashMap::new();
for lr in &limit_rules {
map.entry(lr.location_id).or_default().push(OverrideRef {
id: lr.id,
override_of_id: lr.override_of_id,
});
}
map
};
// proxy_setting IDs grouped by location_id
let ps_ids_by_location: HashMap<uuid::Uuid, Vec<OverrideRef>> = {
let mut map: HashMap<_, Vec<_>> = HashMap::new();
for ps in &proxy_settings {
map.entry(ps.location_id).or_default().push(OverrideRef {
id: ps.id,
override_of_id: ps.override_of_id,
});
}
map
};
// rewrite_rule IDs grouped by location_id
let rr_ids_by_location: HashMap<uuid::Uuid, Vec<OverrideRef>> = {
let mut map: HashMap<_, Vec<_>> = HashMap::new();
for rr in &rewrite_rules {
map.entry(rr.location_id).or_default().push(OverrideRef {
id: rr.id,
override_of_id: rr.override_of_id,
});
}
map
};
// location_block IDs grouped by proxy_pass_upstream_id (reverse FK)
let loc_block_ids_by_upstream: HashMap<uuid::Uuid, Vec<OverrideRef>> = {
let mut map: HashMap<_, Vec<_>> = HashMap::new();
for lb in &location_blocks {
if let Some(up_id) = lb.proxy_pass_upstream_id {
map.entry(up_id).or_default().push(OverrideRef {
id: lb.id,
override_of_id: lb.override_of_id,
});
}
}
map
};
// ── Build child-ID tuples for each config type ───────────────────────
let location_block_tuples: Vec<(
crate::db::entities::location_block::Model,
Vec<OverrideRef>,
Vec<OverrideRef>,
Vec<OverrideRef>,
Vec<OverrideRef>,
)> = location_blocks
.into_iter()
.map(|lb| {
let ar_ids = loc_ar_ids_by_location
.get(&lb.id)
.cloned()
.unwrap_or_default();
let lr_ids = lr_ids_by_location.get(&lb.id).cloned().unwrap_or_default();
let ps_ids = ps_ids_by_location.get(&lb.id).cloned().unwrap_or_default();
let rr_ids = rr_ids_by_location.get(&lb.id).cloned().unwrap_or_default();
(lb, ar_ids, lr_ids, ps_ids, rr_ids)
})
.collect();
let server_block_tuples: Vec<(
crate::db::entities::server_block::Model,
Vec<OverrideRef>,
Vec<OverrideRef>,
Vec<OverrideRef>,
Vec<OverrideRef>,
)> = server_blocks
.into_iter()
.map(|sb| {
let ar_ids = server_ar_ids_by_server
.get(&sb.id)
.cloned()
.unwrap_or_default();
let lb_ids = loc_block_ids_by_server
.get(&sb.id)
.cloned()
.unwrap_or_default();
let ls_ids = log_setting_ids_by_server
.get(&sb.id)
.cloned()
.unwrap_or_default();
let sc_ids: Vec<OverrideRef> = sb
.ssl_cert_id
.into_iter()
.map(|id| OverrideRef {
id,
override_of_id: None,
})
.collect();
(sb, ar_ids, lb_ids, ls_ids, sc_ids)
})
.collect();
let upstream_tuples: Vec<(crate::db::entities::upstream::Model, Vec<OverrideRef>)> =
upstreams
.into_iter()
.map(|u| {
let lb_ids = loc_block_ids_by_upstream
.get(&u.id)
.cloned()
.unwrap_or_default();
(u, lb_ids)
})
.collect();
// Combine server-level and location-level access rules into one flat list
let all_access_rules: Vec<crate::db::entities::access_rule::Model> = {
let mut ars =
Vec::with_capacity(server_access_rules.len() + location_access_rules.len());
ars.extend(server_access_rules);
ars.extend(location_access_rules);
ars
};
Ok(ProxyConfig {
id: proxy_entity.id,
name: proxy_entity.name,
r#type: ProxyType::Nginx,
description: proxy_entity.description,
parent_config_id,
server_blocks: server_block_tuples
.into_iter()
.map(|m| (m.0.id, m.into()))
.collect(),
upstreams: upstream_tuples
.into_iter()
.map(|m| (m.0.id, m.into()))
.collect(),
location_blocks: location_block_tuples
.into_iter()
.map(|m| (m.0.id, m.into()))
.collect(),
access_rules: all_access_rules
.into_iter()
.map(|m| (m.id, m.into()))
.collect(),
cache_zones: cache_zones.into_iter().map(|m| (m.id, m.into())).collect(),
limit_rules: limit_rules.into_iter().map(|m| (m.id, m.into())).collect(),
limit_zones: limit_zones.into_iter().map(|m| (m.id, m.into())).collect(),
log_settings: log_settings.into_iter().map(|m| (m.id, m.into())).collect(),
proxy_settings: proxy_settings
.into_iter()
.map(|m| (m.id, m.into()))
.collect(),
rewrite_rules: rewrite_rules
.into_iter()
.map(|m| (m.id, m.into()))
.collect(),
ssl_certificates: ssl_certificates
.into_iter()
.map(|m| (m.id, m.into()))
.collect(),
})
}
async fn get_merged_proxy_config(
&self,
proxy_id: uuid::Uuid,
) -> ProxyServiceResult<ProxyConfig> {
let configs = self.get_proxy_raw_configs(proxy_id).await?;
// configs is ordered [leaf, ..., root] (most specific first)
// self.merge(other) means self overrides other
// So start with leaf and merge each ancestor into it
let mut iter = configs.into_iter();
let mut merged = iter.next().ok_or(ProxyServiceError::ConfigNotFound)?;
for config in iter {
merged.merge(config);
}
Ok(merged)
}
}

View File

@@ -0,0 +1,478 @@
use std::collections::HashMap;
#[derive(Debug)]
pub enum ProxyServiceError {
ConfigNotFound,
InvalidConfig,
DatabaseError(sea_orm::DbErr),
}
impl From<sea_orm::DbErr> for ProxyServiceError {
fn from(err: sea_orm::DbErr) -> Self {
ProxyServiceError::DatabaseError(err)
}
}
pub type ProxyServiceResult<T> = Result<T, ProxyServiceError>;
pub enum ProxyType {
Nginx,
}
pub trait Mergeable<T> {
// merge with other, self overrides other
fn merge(&mut self, other: T);
}
#[derive(Debug, Clone)]
pub struct OverrideRef {
pub id: uuid::Uuid,
pub override_of_id: Option<uuid::Uuid>,
}
pub fn merge_override_vecs(
mut child: Vec<OverrideRef>,
parent: Vec<OverrideRef>,
) -> Vec<OverrideRef> {
let overridden_ids: std::collections::HashSet<uuid::Uuid> = child
.iter()
.filter_map(|r| r.override_of_id)
.collect();
for item in parent {
if !overridden_ids.contains(&item.id) {
child.push(item);
}
}
child
}
pub trait Overridable {
fn override_of_id(&self) -> Option<uuid::Uuid>;
}
pub struct ProxyConfig {
pub id: uuid::Uuid,
pub name: String,
pub r#type: ProxyType,
pub description: Option<String>,
pub parent_config_id: Option<Vec<uuid::Uuid>>,
//
pub server_blocks: HashMap<uuid::Uuid, ServerBlockConfig>,
pub upstreams: HashMap<uuid::Uuid, UpstreamConfig>,
pub access_rules: HashMap<uuid::Uuid, AccessRuleConfig>,
pub cache_zones: HashMap<uuid::Uuid, CacheZoneConfig>,
pub limit_rules: HashMap<uuid::Uuid, LimitRuleConfig>,
pub limit_zones: HashMap<uuid::Uuid, LimitZoneConfig>,
pub location_blocks: HashMap<uuid::Uuid, LocationBlockConfig>,
pub log_settings: HashMap<uuid::Uuid, LogSettingConfig>,
pub proxy_settings: HashMap<uuid::Uuid, ProxySettingConfig>,
pub rewrite_rules: HashMap<uuid::Uuid, RewriteRuleConfig>,
pub ssl_certificates: HashMap<uuid::Uuid, SslCertificateConfig>,
}
impl Mergeable<ProxyConfig> for ProxyConfig {
fn merge(&mut self, other: ProxyConfig) {
use std::collections::HashSet;
macro_rules! merge_overridable_field {
($field:ident) => {
let overridden: HashSet<uuid::Uuid> = self.$field
.values()
.filter_map(|v| v.override_of_id())
.collect();
for (id, value) in other.$field {
if !overridden.contains(&id) && !self.$field.contains_key(&id) {
self.$field.insert(id, value);
}
}
};
}
// server_blocks: merge matching entries (field-level), handle overrides
{
let overridden: HashSet<uuid::Uuid> = self.server_blocks
.values()
.filter_map(|sb| sb.override_of_id)
.collect();
for (id, block) in other.server_blocks {
if let Some(self_block) = self.server_blocks.get_mut(&id) {
self_block.merge(block);
} else if !overridden.contains(&id) {
self.server_blocks.insert(id, block);
}
}
}
merge_overridable_field!(upstreams);
merge_overridable_field!(access_rules);
merge_overridable_field!(cache_zones);
merge_overridable_field!(limit_rules);
merge_overridable_field!(limit_zones);
merge_overridable_field!(location_blocks);
merge_overridable_field!(log_settings);
merge_overridable_field!(proxy_settings);
merge_overridable_field!(rewrite_rules);
// ssl_certificates: simple merge (no override_of_id)
for (id, cert) in other.ssl_certificates {
self.ssl_certificates.entry(id).or_insert(cert);
}
}
}
pub struct ServerBlockConfig {
pub id: uuid::Uuid,
pub server_name: Option<Vec<String>>,
pub listen_port: i32,
pub ssl_enabled: Option<bool>,
pub override_of_id: Option<uuid::Uuid>,
//
pub access_rules: Vec<OverrideRef>,
pub location_blocks: Vec<OverrideRef>,
pub log_settings: Vec<OverrideRef>,
pub ssl_certificates: Vec<OverrideRef>,
}
impl
From<(
crate::db::entities::server_block::Model,
Vec<OverrideRef>,
Vec<OverrideRef>,
Vec<OverrideRef>,
Vec<OverrideRef>,
)> for ServerBlockConfig
{
fn from(
(model, access_rules, location_blocks, log_settings, ssl_certificates): (
crate::db::entities::server_block::Model,
Vec<OverrideRef>,
Vec<OverrideRef>,
Vec<OverrideRef>,
Vec<OverrideRef>,
),
) -> Self {
ServerBlockConfig {
id: model.id,
server_name: model.server_name,
listen_port: model.listen_port,
ssl_enabled: model.ssl_enabled,
override_of_id: model.override_of_id,
access_rules,
location_blocks,
log_settings,
ssl_certificates,
}
}
}
impl Mergeable<ServerBlockConfig> for ServerBlockConfig {
fn merge(&mut self, other: ServerBlockConfig) {
if let Some(server_name) = other.server_name {
self.server_name = Some(server_name);
}
self.listen_port = other.listen_port;
if let Some(ssl_enabled) = other.ssl_enabled {
self.ssl_enabled = Some(ssl_enabled);
}
self.override_of_id = other.override_of_id;
self.access_rules = merge_override_vecs(
std::mem::take(&mut self.access_rules),
other.access_rules,
);
self.location_blocks = merge_override_vecs(
std::mem::take(&mut self.location_blocks),
other.location_blocks,
);
self.log_settings = merge_override_vecs(
std::mem::take(&mut self.log_settings),
other.log_settings,
);
self.ssl_certificates = merge_override_vecs(
std::mem::take(&mut self.ssl_certificates),
other.ssl_certificates,
);
}
}
pub struct UpstreamConfig {
pub id: uuid::Uuid,
pub name: String,
pub target_host: String,
pub target_port: i32,
pub metadata: Option<serde_json::Value>,
pub override_of_id: Option<uuid::Uuid>,
//
pub location_blocks: Vec<OverrideRef>,
}
impl From<(crate::db::entities::upstream::Model, Vec<OverrideRef>)> for UpstreamConfig {
fn from(
(model, location_blocks): (crate::db::entities::upstream::Model, Vec<OverrideRef>),
) -> Self {
UpstreamConfig {
id: model.id,
name: model.name,
target_host: model.target_host,
target_port: model.target_port,
metadata: model.metadata,
override_of_id: model.override_of_id,
location_blocks,
}
}
}
pub struct AccessRuleConfig {
pub id: uuid::Uuid,
pub r#type: String,
pub ip_cidr: String,
pub description: Option<String>,
pub priority: i32,
pub override_of_id: Option<uuid::Uuid>,
}
impl From<crate::db::entities::access_rule::Model> for AccessRuleConfig {
fn from(model: crate::db::entities::access_rule::Model) -> Self {
AccessRuleConfig {
id: model.id,
r#type: model.r#type,
ip_cidr: model.ip_cidr,
description: model.description,
priority: model.priority,
override_of_id: model.override_of_id,
}
}
}
pub struct CacheZoneConfig {
pub id: uuid::Uuid,
pub name: String,
pub size: String,
pub override_of_id: Option<uuid::Uuid>,
}
impl From<crate::db::entities::cache_zone::Model> for CacheZoneConfig {
fn from(model: crate::db::entities::cache_zone::Model) -> Self {
CacheZoneConfig {
id: model.id,
name: model.name,
size: model.size_limit,
override_of_id: model.override_of_id,
}
}
}
pub struct LimitRuleConfig {
pub id: uuid::Uuid,
pub location_id: uuid::Uuid,
pub zone_id: uuid::Uuid,
pub burst: Option<i32>,
pub nodelay: Option<bool>,
pub is_deleted: bool,
pub override_of_id: Option<uuid::Uuid>,
}
impl From<crate::db::entities::limit_rule::Model> for LimitRuleConfig {
fn from(model: crate::db::entities::limit_rule::Model) -> Self {
LimitRuleConfig {
id: model.id,
location_id: model.location_id,
zone_id: model.zone_id,
burst: model.burst,
nodelay: model.nodelay,
is_deleted: model.is_deleted,
override_of_id: model.override_of_id,
}
}
}
pub struct LimitZoneConfig {
pub id: uuid::Uuid,
pub name: String,
pub key: String,
pub rate: String,
pub override_of_id: Option<uuid::Uuid>,
}
impl From<crate::db::entities::limit_zone::Model> for LimitZoneConfig {
fn from(model: crate::db::entities::limit_zone::Model) -> Self {
LimitZoneConfig {
id: model.id,
name: model.name,
key: model.key,
rate: model.rate,
override_of_id: model.override_of_id,
}
}
}
pub struct LocationBlockConfig {
pub id: uuid::Uuid,
pub server_id: uuid::Uuid,
pub path_pattern: String,
pub proxy_pass_upstream_id: Option<uuid::Uuid>,
pub metadata: Option<serde_json::Value>,
pub override_of_id: Option<uuid::Uuid>,
//
pub access_rules: Vec<OverrideRef>,
pub limit_rules: Vec<OverrideRef>,
pub proxy_settings: Vec<OverrideRef>,
pub rewrite_rules: Vec<OverrideRef>,
}
impl
From<(
crate::db::entities::location_block::Model,
Vec<OverrideRef>,
Vec<OverrideRef>,
Vec<OverrideRef>,
Vec<OverrideRef>,
)> for LocationBlockConfig
{
fn from(
(model, access_rules, limit_rules, proxy_settings, rewrite_rules): (
crate::db::entities::location_block::Model,
Vec<OverrideRef>,
Vec<OverrideRef>,
Vec<OverrideRef>,
Vec<OverrideRef>,
),
) -> Self {
LocationBlockConfig {
id: model.id,
server_id: model.server_id,
path_pattern: model.path_pattern,
proxy_pass_upstream_id: model.proxy_pass_upstream_id,
metadata: model.metadata,
override_of_id: model.override_of_id,
access_rules,
limit_rules,
proxy_settings,
rewrite_rules,
}
}
}
pub struct LogSettingConfig {
pub id: uuid::Uuid,
pub access_log_path: Option<String>,
pub error_log_path: Option<String>,
pub log_level: Option<String>,
pub override_of_id: Option<uuid::Uuid>,
}
impl From<crate::db::entities::log_setting::Model> for LogSettingConfig {
fn from(model: crate::db::entities::log_setting::Model) -> Self {
LogSettingConfig {
id: model.id,
access_log_path: model.access_log_path,
error_log_path: model.error_log_path,
log_level: model.log_level,
override_of_id: model.override_of_id,
}
}
}
pub struct SslCertificateConfig {
pub id: uuid::Uuid,
pub name: String,
pub cert_path: String,
pub key_path: String,
pub expiry_date: chrono::DateTime<chrono::Utc>,
}
impl From<crate::db::entities::ssl_certificate::Model> for SslCertificateConfig {
fn from(model: crate::db::entities::ssl_certificate::Model) -> Self {
SslCertificateConfig {
id: model.id,
name: model.name,
cert_path: model.cert_path,
key_path: model.key_path,
expiry_date: model.expiry_date.and_utc(),
}
}
}
pub struct ProxySettingConfig {
pub id: uuid::Uuid,
pub location_id: uuid::Uuid,
pub read_timeout: Option<i32>,
pub connect_timeout: Option<i32>,
pub buffer_size: Option<i32>,
pub cache_enabled: Option<bool>,
pub cache_zone: Option<uuid::Uuid>,
pub override_of_id: Option<uuid::Uuid>,
}
impl From<crate::db::entities::proxy_setting::Model> for ProxySettingConfig {
fn from(model: crate::db::entities::proxy_setting::Model) -> Self {
ProxySettingConfig {
id: model.id,
location_id: model.location_id,
read_timeout: model.read_timeout,
connect_timeout: model.connect_timeout,
buffer_size: model.buffer_size,
cache_enabled: model.cache_enabled,
cache_zone: model.cache_zone,
override_of_id: model.override_of_id,
}
}
}
pub struct RewriteRuleConfig {
pub id: uuid::Uuid,
pub location_id: uuid::Uuid,
pub pattern: String,
pub replacement: String,
pub flag: Option<String>,
pub priority: i32,
pub override_of_id: Option<uuid::Uuid>,
}
impl From<crate::db::entities::rewrite_rule::Model> for RewriteRuleConfig {
fn from(model: crate::db::entities::rewrite_rule::Model) -> Self {
RewriteRuleConfig {
id: model.id,
location_id: model.location_id,
pattern: model.pattern,
replacement: model.replacement,
flag: model.flag,
priority: model.priority,
override_of_id: model.override_of_id,
}
}
}
// ── Overridable implementations ──
impl Overridable for ServerBlockConfig {
fn override_of_id(&self) -> Option<uuid::Uuid> { self.override_of_id }
}
impl Overridable for UpstreamConfig {
fn override_of_id(&self) -> Option<uuid::Uuid> { self.override_of_id }
}
impl Overridable for AccessRuleConfig {
fn override_of_id(&self) -> Option<uuid::Uuid> { self.override_of_id }
}
impl Overridable for CacheZoneConfig {
fn override_of_id(&self) -> Option<uuid::Uuid> { self.override_of_id }
}
impl Overridable for LimitRuleConfig {
fn override_of_id(&self) -> Option<uuid::Uuid> { self.override_of_id }
}
impl Overridable for LimitZoneConfig {
fn override_of_id(&self) -> Option<uuid::Uuid> { self.override_of_id }
}
impl Overridable for LocationBlockConfig {
fn override_of_id(&self) -> Option<uuid::Uuid> { self.override_of_id }
}
impl Overridable for LogSettingConfig {
fn override_of_id(&self) -> Option<uuid::Uuid> { self.override_of_id }
}
impl Overridable for ProxySettingConfig {
fn override_of_id(&self) -> Option<uuid::Uuid> { self.override_of_id }
}
impl Overridable for RewriteRuleConfig {
fn override_of_id(&self) -> Option<uuid::Uuid> { self.override_of_id }
}