feat: add routing profile scheduling policies

This commit is contained in:
fawney19
2026-05-18 11:03:49 +08:00
parent a2f91b4108
commit 92813e6122
124 changed files with 11681 additions and 578 deletions

View File

@@ -8,6 +8,7 @@ description = "Shared data contracts and repository traits for Aether Rust servi
[dependencies]
aether-ai-formats.workspace = true
aether-routing-core.workspace = true
async-trait.workspace = true
chrono.workspace = true
serde.workspace = true

View File

@@ -6,6 +6,7 @@ pub mod global_models;
pub mod pool_scores;
pub mod provider_catalog;
pub mod quota;
pub mod routing_profiles;
pub mod settlement;
pub mod usage;
pub mod video_tasks;

View File

@@ -0,0 +1,9 @@
mod types;
pub use aether_routing_core::{RoutingGroupBindingSubject, RoutingGroupConfig, RoutingGroupRecord};
pub use types::{
CreateRoutingGroupBindingRecord, CreateRoutingGroupRecord, CreateRoutingGroupVersionRecord,
RoutingGroupBindingQuery, RoutingGroupLookupKey, RoutingGroupReadRepository,
RoutingGroupWriteRepository, StoredRoutingGroup, StoredRoutingGroupBinding,
StoredRoutingGroupVersion, UpdateRoutingGroupBindingRecord, UpdateRoutingGroupRecord,
};

View File

@@ -0,0 +1,238 @@
use async_trait::async_trait;
use serde_json::Value;
use aether_routing_core::RoutingGroupBindingSubject;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct StoredRoutingGroup {
pub id: String,
pub name: String,
pub description: Option<String>,
pub enabled: bool,
pub is_system_default: bool,
pub config_json: Value,
pub version: i64,
pub created_at: i64,
pub updated_at: i64,
pub published_at: Option<i64>,
}
impl StoredRoutingGroup {
pub fn new(record: CreateRoutingGroupRecord) -> Result<Self, crate::DataLayerError> {
validate_non_empty(&record.id, "routing_groups.id")?;
validate_non_empty(&record.name, "routing_groups.name")?;
validate_config_object(&record.config_json, "routing_groups.config_json")?;
Ok(Self {
id: record.id,
name: record.name,
description: record.description,
enabled: record.enabled,
is_system_default: record.is_system_default,
config_json: record.config_json,
version: record.version.max(1),
created_at: record.created_at,
updated_at: record.updated_at,
published_at: record.published_at,
})
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CreateRoutingGroupRecord {
pub id: String,
pub name: String,
pub description: Option<String>,
pub enabled: bool,
pub is_system_default: bool,
pub config_json: Value,
pub version: i64,
pub created_at: i64,
pub updated_at: i64,
pub published_at: Option<i64>,
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct UpdateRoutingGroupRecord {
pub name: Option<String>,
pub description: Option<Option<String>>,
pub enabled: Option<bool>,
pub is_system_default: Option<bool>,
pub config_json: Option<Value>,
pub version: Option<i64>,
pub updated_at: i64,
pub published_at: Option<Option<i64>>,
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct UpdateRoutingGroupBindingRecord {
pub group_id: Option<String>,
pub subject_type: Option<RoutingGroupBindingSubject>,
pub subject_id: Option<String>,
pub is_default: Option<bool>,
pub allow_explicit_select: Option<bool>,
pub updated_at: i64,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct StoredRoutingGroupBinding {
pub id: String,
pub group_id: String,
pub subject_type: RoutingGroupBindingSubject,
pub subject_id: String,
pub is_default: bool,
pub allow_explicit_select: bool,
pub created_at: i64,
pub updated_at: i64,
}
impl StoredRoutingGroupBinding {
pub fn new(record: CreateRoutingGroupBindingRecord) -> Result<Self, crate::DataLayerError> {
validate_non_empty(&record.id, "routing_group_bindings.id")?;
validate_non_empty(&record.group_id, "routing_group_bindings.group_id")?;
validate_non_empty(&record.subject_id, "routing_group_bindings.subject_id")?;
Ok(Self {
id: record.id,
group_id: record.group_id,
subject_type: record.subject_type,
subject_id: record.subject_id,
is_default: record.is_default,
allow_explicit_select: record.allow_explicit_select,
created_at: record.created_at,
updated_at: record.updated_at,
})
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CreateRoutingGroupBindingRecord {
pub id: String,
pub group_id: String,
pub subject_type: RoutingGroupBindingSubject,
pub subject_id: String,
pub is_default: bool,
pub allow_explicit_select: bool,
pub created_at: i64,
pub updated_at: i64,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct StoredRoutingGroupVersion {
pub id: String,
pub group_id: String,
pub version: i64,
pub config_json: Value,
pub created_at: i64,
pub created_by: Option<String>,
}
impl StoredRoutingGroupVersion {
pub fn new(record: CreateRoutingGroupVersionRecord) -> Result<Self, crate::DataLayerError> {
validate_non_empty(&record.id, "routing_group_versions.id")?;
validate_non_empty(&record.group_id, "routing_group_versions.group_id")?;
validate_config_object(&record.config_json, "routing_group_versions.config_json")?;
Ok(Self {
id: record.id,
group_id: record.group_id,
version: record.version.max(1),
config_json: record.config_json,
created_at: record.created_at,
created_by: record.created_by,
})
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CreateRoutingGroupVersionRecord {
pub id: String,
pub group_id: String,
pub version: i64,
pub config_json: Value,
pub created_at: i64,
pub created_by: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum RoutingGroupLookupKey<'a> {
Id(&'a str),
Name(&'a str),
SystemDefault,
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct RoutingGroupBindingQuery {
pub group_id: Option<String>,
pub subject_type: Option<RoutingGroupBindingSubject>,
pub subject_id: Option<String>,
}
#[async_trait]
pub trait RoutingGroupReadRepository: Send + Sync {
async fn list_routing_groups(&self) -> Result<Vec<StoredRoutingGroup>, crate::DataLayerError>;
async fn find_routing_group(
&self,
lookup: RoutingGroupLookupKey<'_>,
) -> Result<Option<StoredRoutingGroup>, crate::DataLayerError>;
async fn list_routing_group_bindings(
&self,
query: &RoutingGroupBindingQuery,
) -> Result<Vec<StoredRoutingGroupBinding>, crate::DataLayerError>;
async fn list_routing_group_versions(
&self,
group_id: &str,
) -> Result<Vec<StoredRoutingGroupVersion>, crate::DataLayerError>;
}
#[async_trait]
pub trait RoutingGroupWriteRepository: Send + Sync {
async fn create_routing_group(
&self,
record: CreateRoutingGroupRecord,
) -> Result<StoredRoutingGroup, crate::DataLayerError>;
async fn update_routing_group(
&self,
id: &str,
patch: UpdateRoutingGroupRecord,
) -> Result<Option<StoredRoutingGroup>, crate::DataLayerError>;
async fn delete_routing_group(&self, id: &str) -> Result<bool, crate::DataLayerError>;
async fn create_routing_group_binding(
&self,
record: CreateRoutingGroupBindingRecord,
) -> Result<StoredRoutingGroupBinding, crate::DataLayerError>;
async fn delete_routing_group_binding(&self, id: &str) -> Result<bool, crate::DataLayerError>;
async fn update_routing_group_binding(
&self,
id: &str,
patch: UpdateRoutingGroupBindingRecord,
) -> Result<Option<StoredRoutingGroupBinding>, crate::DataLayerError>;
async fn create_routing_group_version(
&self,
record: CreateRoutingGroupVersionRecord,
) -> Result<StoredRoutingGroupVersion, crate::DataLayerError>;
}
fn validate_non_empty(value: &str, field: &str) -> Result<(), crate::DataLayerError> {
if value.trim().is_empty() {
return Err(crate::DataLayerError::InvalidInput(format!(
"{field} is empty"
)));
}
Ok(())
}
fn validate_config_object(value: &Value, field: &str) -> Result<(), crate::DataLayerError> {
if !value.is_object() {
return Err(crate::DataLayerError::InvalidInput(format!(
"{field} must be a JSON object"
)));
}
Ok(())
}