use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum SchemaType {
#[serde(rename = "openapi")]
OpenAPI,
#[serde(rename = "asyncapi")]
AsyncAPI,
#[serde(rename = "grpc")]
GRPC,
#[serde(rename = "graphql")]
GraphQL,
#[serde(rename = "orpc")]
ORPC,
#[serde(rename = "thrift")]
Thrift,
#[serde(rename = "avro")]
Avro,
#[serde(rename = "custom")]
Custom,
}
impl SchemaType {
pub fn is_valid(&self) -> bool {
matches!(
self,
SchemaType::OpenAPI
| SchemaType::AsyncAPI
| SchemaType::GRPC
| SchemaType::GraphQL
| SchemaType::ORPC
| SchemaType::Thrift
| SchemaType::Avro
| SchemaType::Custom
)
}
pub fn as_str(&self) -> &'static str {
match self {
SchemaType::OpenAPI => "openapi",
SchemaType::AsyncAPI => "asyncapi",
SchemaType::GRPC => "grpc",
SchemaType::GraphQL => "graphql",
SchemaType::ORPC => "orpc",
SchemaType::Thrift => "thrift",
SchemaType::Avro => "avro",
SchemaType::Custom => "custom",
}
}
}
impl std::fmt::Display for SchemaType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.as_str())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum LocationType {
#[serde(rename = "http")]
HTTP,
#[serde(rename = "registry")]
Registry,
#[serde(rename = "inline")]
Inline,
}
impl LocationType {
pub fn is_valid(&self) -> bool {
matches!(
self,
LocationType::HTTP | LocationType::Registry | LocationType::Inline
)
}
pub fn as_str(&self) -> &'static str {
match self {
LocationType::HTTP => "http",
LocationType::Registry => "registry",
LocationType::Inline => "inline",
}
}
}
impl std::fmt::Display for LocationType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.as_str())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum Capability {
#[serde(rename = "rest")]
REST,
#[serde(rename = "grpc")]
GRPC,
#[serde(rename = "websocket")]
WebSocket,
#[serde(rename = "sse")]
SSE,
#[serde(rename = "graphql")]
GraphQL,
#[serde(rename = "mqtt")]
MQTT,
#[serde(rename = "amqp")]
AMQP,
}
impl Capability {
pub fn as_str(&self) -> &'static str {
match self {
Capability::REST => "rest",
Capability::GRPC => "grpc",
Capability::WebSocket => "websocket",
Capability::SSE => "sse",
Capability::GraphQL => "graphql",
Capability::MQTT => "mqtt",
Capability::AMQP => "amqp",
}
}
}
impl std::fmt::Display for Capability {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.as_str())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum InstanceStatus {
#[serde(rename = "starting")]
Starting,
#[serde(rename = "healthy")]
Healthy,
#[serde(rename = "degraded")]
Degraded,
#[serde(rename = "unhealthy")]
Unhealthy,
#[serde(rename = "draining")]
Draining,
#[serde(rename = "stopping")]
Stopping,
}
impl std::fmt::Display for InstanceStatus {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let s = match self {
InstanceStatus::Starting => "starting",
InstanceStatus::Healthy => "healthy",
InstanceStatus::Degraded => "degraded",
InstanceStatus::Unhealthy => "unhealthy",
InstanceStatus::Draining => "draining",
InstanceStatus::Stopping => "stopping",
};
write!(f, "{s}")
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum InstanceRole {
#[serde(rename = "primary")]
Primary,
#[serde(rename = "canary")]
Canary,
#[serde(rename = "blue")]
Blue,
#[serde(rename = "green")]
Green,
#[serde(rename = "shadow")]
Shadow,
}
impl std::fmt::Display for InstanceRole {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let s = match self {
InstanceRole::Primary => "primary",
InstanceRole::Canary => "canary",
InstanceRole::Blue => "blue",
InstanceRole::Green => "green",
InstanceRole::Shadow => "shadow",
};
write!(f, "{s}")
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum DeploymentStrategy {
#[serde(rename = "rolling")]
Rolling,
#[serde(rename = "canary")]
Canary,
#[serde(rename = "blue_green")]
BlueGreen,
#[serde(rename = "shadow")]
Shadow,
#[serde(rename = "recreate")]
Recreate,
}
impl std::fmt::Display for DeploymentStrategy {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let s = match self {
DeploymentStrategy::Rolling => "rolling",
DeploymentStrategy::Canary => "canary",
DeploymentStrategy::BlueGreen => "blue_green",
DeploymentStrategy::Shadow => "shadow",
DeploymentStrategy::Recreate => "recreate",
};
write!(f, "{s}")
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
#[derive(Default)]
pub enum MountStrategy {
#[serde(rename = "root")]
Root,
#[serde(rename = "instance")]
Instance,
#[serde(rename = "service")]
#[default]
Service,
#[serde(rename = "versioned")]
Versioned,
#[serde(rename = "custom")]
Custom,
#[serde(rename = "subdomain")]
Subdomain,
}
impl MountStrategy {
pub fn is_valid(&self) -> bool {
matches!(
self,
MountStrategy::Root
| MountStrategy::Instance
| MountStrategy::Service
| MountStrategy::Versioned
| MountStrategy::Custom
| MountStrategy::Subdomain
)
}
pub fn as_str(&self) -> &'static str {
match self {
MountStrategy::Root => "root",
MountStrategy::Instance => "instance",
MountStrategy::Service => "service",
MountStrategy::Versioned => "versioned",
MountStrategy::Custom => "custom",
MountStrategy::Subdomain => "subdomain",
}
}
}
impl std::fmt::Display for MountStrategy {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.as_str())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum AuthType {
#[serde(rename = "bearer")]
Bearer,
#[serde(rename = "apikey")]
APIKey,
#[serde(rename = "basic")]
Basic,
#[serde(rename = "mtls")]
MTLS,
#[serde(rename = "oauth2")]
OAuth2,
#[serde(rename = "oidc")]
OIDC,
#[serde(rename = "custom")]
Custom,
}
impl std::fmt::Display for AuthType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let s = match self {
AuthType::Bearer => "bearer",
AuthType::APIKey => "apikey",
AuthType::Basic => "basic",
AuthType::MTLS => "mtls",
AuthType::OAuth2 => "oauth2",
AuthType::OIDC => "oidc",
AuthType::Custom => "custom",
};
write!(f, "{s}")
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum CommunicationRouteType {
#[serde(rename = "control")]
Control,
#[serde(rename = "admin")]
Admin,
#[serde(rename = "management")]
Management,
#[serde(rename = "lifecycle.start")]
LifecycleStart,
#[serde(rename = "lifecycle.stop")]
LifecycleStop,
#[serde(rename = "lifecycle.reload")]
LifecycleReload,
#[serde(rename = "config.update")]
ConfigUpdate,
#[serde(rename = "config.query")]
ConfigQuery,
#[serde(rename = "event.poll")]
EventPoll,
#[serde(rename = "event.ack")]
EventAck,
#[serde(rename = "health.check")]
HealthCheck,
#[serde(rename = "status.query")]
StatusQuery,
#[serde(rename = "schema.query")]
SchemaQuery,
#[serde(rename = "schema.validate")]
SchemaValidate,
#[serde(rename = "metrics.query")]
MetricsQuery,
#[serde(rename = "tracing.export")]
TracingExport,
#[serde(rename = "custom")]
Custom,
}
impl std::fmt::Display for CommunicationRouteType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let s = match self {
CommunicationRouteType::Control => "control",
CommunicationRouteType::Admin => "admin",
CommunicationRouteType::Management => "management",
CommunicationRouteType::LifecycleStart => "lifecycle.start",
CommunicationRouteType::LifecycleStop => "lifecycle.stop",
CommunicationRouteType::LifecycleReload => "lifecycle.reload",
CommunicationRouteType::ConfigUpdate => "config.update",
CommunicationRouteType::ConfigQuery => "config.query",
CommunicationRouteType::EventPoll => "event.poll",
CommunicationRouteType::EventAck => "event.ack",
CommunicationRouteType::HealthCheck => "health.check",
CommunicationRouteType::StatusQuery => "status.query",
CommunicationRouteType::SchemaQuery => "schema.query",
CommunicationRouteType::SchemaValidate => "schema.validate",
CommunicationRouteType::MetricsQuery => "metrics.query",
CommunicationRouteType::TracingExport => "tracing.export",
CommunicationRouteType::Custom => "custom",
};
write!(f, "{s}")
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum WebhookEventType {
#[serde(rename = "schema.updated")]
SchemaUpdated,
#[serde(rename = "health.changed")]
HealthChanged,
#[serde(rename = "instance.scaling")]
InstanceScaling,
#[serde(rename = "maintenance.mode")]
MaintenanceMode,
#[serde(rename = "ratelimit.changed")]
RateLimitChanged,
#[serde(rename = "circuit.breaker.open")]
CircuitBreakerOpen,
#[serde(rename = "circuit.breaker.closed")]
CircuitBreakerClosed,
#[serde(rename = "config.updated")]
ConfigUpdated,
#[serde(rename = "traffic.shift")]
TrafficShift,
#[serde(rename = "routes.changing")]
RoutesChanging,
#[serde(rename = "routes.changed")]
RoutesChanged,
#[serde(rename = "routes.draining")]
RoutesDraining,
#[serde(rename = "gateway.shutdown")]
GatewayShutdown,
}
impl std::fmt::Display for WebhookEventType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let s = match self {
WebhookEventType::SchemaUpdated => "schema.updated",
WebhookEventType::HealthChanged => "health.changed",
WebhookEventType::InstanceScaling => "instance.scaling",
WebhookEventType::MaintenanceMode => "maintenance.mode",
WebhookEventType::RateLimitChanged => "ratelimit.changed",
WebhookEventType::CircuitBreakerOpen => "circuit.breaker.open",
WebhookEventType::CircuitBreakerClosed => "circuit.breaker.closed",
WebhookEventType::ConfigUpdated => "config.updated",
WebhookEventType::TrafficShift => "traffic.shift",
WebhookEventType::RoutesChanging => "routes.changing",
WebhookEventType::RoutesChanged => "routes.changed",
WebhookEventType::RoutesDraining => "routes.draining",
WebhookEventType::GatewayShutdown => "gateway.shutdown",
};
write!(f, "{s}")
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum CompatibilityMode {
#[serde(rename = "backward")]
Backward,
#[serde(rename = "forward")]
Forward,
#[serde(rename = "full")]
Full,
#[serde(rename = "none")]
None,
#[serde(rename = "backward_transitive")]
BackwardTransitive,
#[serde(rename = "forward_transitive")]
ForwardTransitive,
}
impl std::fmt::Display for CompatibilityMode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let s = match self {
CompatibilityMode::Backward => "backward",
CompatibilityMode::Forward => "forward",
CompatibilityMode::Full => "full",
CompatibilityMode::None => "none",
CompatibilityMode::BackwardTransitive => "backward_transitive",
CompatibilityMode::ForwardTransitive => "forward_transitive",
};
write!(f, "{s}")
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ChangeType {
#[serde(rename = "field_removed")]
FieldRemoved,
#[serde(rename = "field_type_changed")]
FieldTypeChanged,
#[serde(rename = "field_required")]
FieldRequired,
#[serde(rename = "endpoint_removed")]
EndpointRemoved,
#[serde(rename = "endpoint_changed")]
EndpointChanged,
#[serde(rename = "enum_value_removed")]
EnumValueRemoved,
#[serde(rename = "method_removed")]
MethodRemoved,
}
impl std::fmt::Display for ChangeType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let s = match self {
ChangeType::FieldRemoved => "field_removed",
ChangeType::FieldTypeChanged => "field_type_changed",
ChangeType::FieldRequired => "field_required",
ChangeType::EndpointRemoved => "endpoint_removed",
ChangeType::EndpointChanged => "endpoint_changed",
ChangeType::EnumValueRemoved => "enum_value_removed",
ChangeType::MethodRemoved => "method_removed",
};
write!(f, "{s}")
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum ChangeSeverity {
#[serde(rename = "critical")]
Critical,
#[serde(rename = "high")]
High,
#[serde(rename = "medium")]
Medium,
#[serde(rename = "low")]
Low,
}
impl std::fmt::Display for ChangeSeverity {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let s = match self {
ChangeSeverity::Critical => "critical",
ChangeSeverity::High => "high",
ChangeSeverity::Medium => "medium",
ChangeSeverity::Low => "low",
};
write!(f, "{s}")
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum DataSensitivity {
#[serde(rename = "public")]
Public,
#[serde(rename = "internal")]
Internal,
#[serde(rename = "confidential")]
Confidential,
#[serde(rename = "pii")]
PII,
#[serde(rename = "phi")]
PHI,
#[serde(rename = "pci")]
PCI,
}
impl std::fmt::Display for DataSensitivity {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let s = match self {
DataSensitivity::Public => "public",
DataSensitivity::Internal => "internal",
DataSensitivity::Confidential => "confidential",
DataSensitivity::PII => "pii",
DataSensitivity::PHI => "phi",
DataSensitivity::PCI => "pci",
};
write!(f, "{s}")
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum SizeHint {
#[serde(rename = "small")]
Small,
#[serde(rename = "medium")]
Medium,
#[serde(rename = "large")]
Large,
#[serde(rename = "xlarge")]
XLarge,
}
impl std::fmt::Display for SizeHint {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let s = match self {
SizeHint::Small => "small",
SizeHint::Medium => "medium",
SizeHint::Large => "large",
SizeHint::XLarge => "xlarge",
};
write!(f, "{s}")
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct SchemaManifest {
pub version: String,
pub service_name: String,
pub service_version: String,
pub instance_id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub instance: Option<InstanceMetadata>,
pub schemas: Vec<SchemaDescriptor>,
pub capabilities: Vec<String>,
pub endpoints: SchemaEndpoints,
#[serde(default)]
pub routing: RoutingConfig,
#[serde(skip_serializing_if = "Option::is_none")]
pub auth: Option<AuthConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
pub webhook: Option<WebhookConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
pub hints: Option<ServiceHints>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub route_table: Vec<RouteDescriptor>,
pub updated_at: i64,
pub checksum: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub routes_checksum: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct SchemaDescriptor {
#[serde(rename = "type")]
pub schema_type: SchemaType,
pub spec_version: String,
pub location: SchemaLocation,
pub content_type: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub inline_schema: Option<serde_json::Value>,
pub hash: String,
pub size: i64,
#[serde(skip_serializing_if = "Option::is_none")]
pub compatibility: Option<SchemaCompatibility>,
#[serde(skip_serializing_if = "Option::is_none")]
pub metadata: Option<ProtocolMetadata>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SchemaLocation {
#[serde(rename = "type")]
pub location_type: LocationType,
#[serde(skip_serializing_if = "Option::is_none")]
pub url: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub registry_path: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub headers: Option<HashMap<String, String>>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct RouteDescriptor {
pub path: String,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub methods: Vec<String>,
pub protocol: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub operation_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub timeout: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub rate_limit: Option<RateLimitConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cors: Option<CORSConfig>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub middleware: Vec<MiddlewareDeclaration>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cache: Option<CacheConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
pub metadata: Option<HashMap<String, serde_json::Value>>,
#[serde(default)]
pub public: bool,
#[serde(default)]
pub deprecated: bool,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct SchemaEndpoints {
pub health: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub metrics: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub openapi: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub asyncapi: Option<String>,
#[serde(default)]
pub grpc_reflection: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub graphql: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub documentation: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub changelog: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct InstanceMetadata {
pub address: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub region: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub zone: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub labels: Option<HashMap<String, String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub weight: Option<i32>,
pub status: InstanceStatus,
#[serde(skip_serializing_if = "Option::is_none")]
pub role: Option<InstanceRole>,
#[serde(skip_serializing_if = "Option::is_none")]
pub deployment: Option<DeploymentMetadata>,
pub started_at: i64,
#[serde(skip_serializing_if = "Option::is_none")]
pub expected_schema_checksum: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct DeploymentMetadata {
pub deployment_id: String,
pub strategy: DeploymentStrategy,
#[serde(skip_serializing_if = "Option::is_none")]
pub traffic_percent: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub stage: Option<String>,
pub deployed_at: i64,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
pub struct RoutingConfig {
#[serde(default = "default_mount_strategy")]
pub strategy: MountStrategy,
#[serde(skip_serializing_if = "Option::is_none")]
pub base_path: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub subdomain: Option<String>,
#[serde(default)]
pub rewrite: Vec<PathRewrite>,
#[serde(default)]
pub strip_prefix: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub priority: Option<i32>,
#[serde(default)]
pub tags: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub middleware: Vec<MiddlewareDeclaration>,
#[serde(skip_serializing_if = "Option::is_none")]
pub versioning: Option<APIVersioningConfig>,
}
fn default_mount_strategy() -> MountStrategy {
MountStrategy::Service
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct PathRewrite {
pub pattern: String,
pub replacement: String,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct AuthConfig {
pub schemes: Vec<AuthScheme>,
#[serde(default)]
pub required_scopes: Vec<String>,
#[serde(default)]
pub access_control: Vec<AccessRule>,
#[serde(skip_serializing_if = "Option::is_none")]
pub token_validation_url: Option<String>,
#[serde(default)]
pub public_routes: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct AuthScheme {
#[serde(rename = "type")]
pub auth_type: AuthType,
#[serde(skip_serializing_if = "Option::is_none")]
pub config: Option<HashMap<String, serde_json::Value>>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct AccessRule {
pub path: String,
pub methods: Vec<String>,
#[serde(default)]
pub roles: Vec<String>,
#[serde(default)]
pub permissions: Vec<String>,
#[serde(default)]
pub allow_anonymous: bool,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct WebhookConfig {
#[serde(skip_serializing_if = "Option::is_none")]
pub service_webhook: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub gateway_webhook: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub secret: Option<String>,
#[serde(default)]
pub subscribe_events: Vec<WebhookEventType>,
#[serde(default)]
pub publish_events: Vec<WebhookEventType>,
#[serde(skip_serializing_if = "Option::is_none")]
pub retry: Option<RetryConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
pub http_routes: Option<HTTPCommunicationRoutes>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct HTTPCommunicationRoutes {
#[serde(default)]
pub service_routes: Vec<CommunicationRoute>,
#[serde(default)]
pub gateway_routes: Vec<CommunicationRoute>,
#[serde(skip_serializing_if = "Option::is_none")]
pub polling: Option<PollingConfig>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct CommunicationRoute {
pub id: String,
pub path: String,
pub method: String,
#[serde(rename = "type")]
pub route_type: CommunicationRouteType,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub request_schema: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub response_schema: Option<serde_json::Value>,
pub auth_required: bool,
pub idempotent: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub timeout: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct PollingConfig {
pub interval: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub timeout: Option<String>,
#[serde(default)]
pub long_polling: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub long_polling_timeout: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct RetryConfig {
pub max_attempts: i32,
pub initial_delay: String,
pub max_delay: String,
pub multiplier: f64,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub retryable_status_codes: Vec<i32>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub retryable_methods: Vec<String>,
#[serde(default)]
pub retry_on_connection_error: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub per_attempt_timeout: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct SchemaCompatibility {
pub mode: CompatibilityMode,
#[serde(default)]
pub previous_versions: Vec<String>,
#[serde(default)]
pub breaking_changes: Vec<BreakingChange>,
#[serde(default)]
pub deprecations: Vec<Deprecation>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct BreakingChange {
#[serde(rename = "type")]
pub change_type: ChangeType,
pub path: String,
pub description: String,
pub severity: ChangeSeverity,
#[serde(skip_serializing_if = "Option::is_none")]
pub migration: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Deprecation {
pub path: String,
pub deprecated_at: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub removal_date: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub replacement: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub migration: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub reason: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ServiceHints {
#[serde(skip_serializing_if = "Option::is_none")]
pub recommended_timeout: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub expected_latency: Option<LatencyProfile>,
#[serde(skip_serializing_if = "Option::is_none")]
pub scaling: Option<ScalingProfile>,
#[serde(default)]
pub dependencies: Vec<ServiceDependency>,
#[serde(skip_serializing_if = "Option::is_none")]
pub rate_limit: Option<RateLimitConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
pub circuit_breaker: Option<CircuitBreakerConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cors: Option<CORSConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
pub retry_policy: Option<RetryConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
pub observability: Option<ObservabilityConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
pub graceful_shutdown: Option<GracefulShutdownConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cache: Option<CacheConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
pub load_balancing: Option<LoadBalancingConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
pub transformations: Option<TransformationConfig>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct LatencyProfile {
#[serde(skip_serializing_if = "Option::is_none")]
pub p50: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub p95: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub p99: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub p999: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ScalingProfile {
pub auto_scale: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub min_instances: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub max_instances: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub target_cpu: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub target_memory: Option<f64>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ServiceDependency {
pub service_name: String,
pub schema_type: SchemaType,
#[serde(skip_serializing_if = "Option::is_none")]
pub version_range: Option<String>,
pub critical: bool,
#[serde(default)]
pub used_operations: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct RouteMetadata {
pub operation_id: String,
pub path: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub method: Option<String>,
pub idempotent: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub timeout_hint: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cost: Option<i32>,
#[serde(default)]
pub cacheable: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub cache_ttl: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub sensitivity: Option<DataSensitivity>,
#[serde(skip_serializing_if = "Option::is_none")]
pub response_size: Option<SizeHint>,
#[serde(skip_serializing_if = "Option::is_none")]
pub rate_limit_hint: Option<i32>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ProtocolMetadata {
#[serde(skip_serializing_if = "Option::is_none")]
pub graphql: Option<GraphQLMetadata>,
#[serde(skip_serializing_if = "Option::is_none")]
pub grpc: Option<GRPCMetadata>,
#[serde(skip_serializing_if = "Option::is_none")]
pub openapi: Option<OpenAPIMetadata>,
#[serde(skip_serializing_if = "Option::is_none")]
pub asyncapi: Option<AsyncAPIMetadata>,
#[serde(skip_serializing_if = "Option::is_none")]
pub orpc: Option<ORPCMetadata>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct GraphQLMetadata {
#[serde(skip_serializing_if = "Option::is_none")]
pub federation: Option<GraphQLFederation>,
#[serde(default)]
pub subscriptions_enabled: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub subscription_protocol: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub complexity_limit: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub depth_limit: Option<i32>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct GraphQLFederation {
pub version: String,
pub subgraph_name: String,
#[serde(default)]
pub entities: Vec<FederatedEntity>,
#[serde(default)]
pub extends: Vec<String>,
#[serde(default)]
pub provides: Vec<ProvidesRelation>,
#[serde(default)]
pub requires: Vec<RequiresRelation>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct FederatedEntity {
pub type_name: String,
pub key_fields: Vec<String>,
pub fields: Vec<String>,
pub resolvable: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ProvidesRelation {
pub field: String,
pub fields: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct RequiresRelation {
pub field: String,
pub fields: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct GRPCMetadata {
pub reflection_enabled: bool,
pub packages: Vec<String>,
pub services: Vec<String>,
#[serde(default)]
pub grpc_web_enabled: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub grpc_web_protocol: Option<String>,
#[serde(default)]
pub server_streaming_enabled: bool,
#[serde(default)]
pub client_streaming_enabled: bool,
#[serde(default)]
pub bidirectional_streaming_enabled: bool,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct OpenAPIMetadata {
#[serde(skip_serializing_if = "Option::is_none")]
pub extensions: Option<HashMap<String, serde_json::Value>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub server_variables: Option<HashMap<String, ServerVariable>>,
#[serde(default)]
pub default_security: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub composition: Option<CompositionConfig>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ServerVariable {
pub default: String,
#[serde(default)]
pub enum_values: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct AsyncAPIMetadata {
pub protocol: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub channel_bindings: Option<HashMap<String, serde_json::Value>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub message_bindings: Option<HashMap<String, serde_json::Value>>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ORPCMetadata {
#[serde(default)]
pub batch_enabled: bool,
#[serde(default)]
pub streaming_procedures: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct CompositionConfig {
pub include_in_merged: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub component_prefix: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tag_prefix: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub operation_id_prefix: Option<String>,
pub conflict_strategy: ConflictStrategy,
pub preserve_extensions: bool,
#[serde(default)]
pub custom_servers: Vec<OpenAPIServer>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum ConflictStrategy {
#[serde(rename = "prefix")]
Prefix,
#[serde(rename = "error")]
Error,
#[serde(rename = "skip")]
Skip,
#[serde(rename = "overwrite")]
Overwrite,
#[serde(rename = "merge")]
Merge,
}
impl std::fmt::Display for ConflictStrategy {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let s = match self {
ConflictStrategy::Prefix => "prefix",
ConflictStrategy::Error => "error",
ConflictStrategy::Skip => "skip",
ConflictStrategy::Overwrite => "overwrite",
ConflictStrategy::Merge => "merge",
};
write!(f, "{s}")
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct OpenAPIServer {
pub url: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub variables: Option<HashMap<String, ServerVariable>>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct RateLimitConfig {
#[serde(skip_serializing_if = "Option::is_none")]
pub requests_per_second: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub burst_size: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub strategy: Option<RateLimitStrategy>,
#[serde(skip_serializing_if = "Option::is_none")]
pub key: Option<RateLimitKey>,
#[serde(skip_serializing_if = "Option::is_none")]
pub status_code: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub response_headers: Option<HashMap<String, String>>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum RateLimitStrategy {
FixedWindow,
SlidingWindow,
TokenBucket,
LeakyBucket,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum RateLimitKey {
Ip,
User,
ApiKey,
Global,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct CircuitBreakerConfig {
pub enabled: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub error_threshold_percent: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub minimum_requests: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub open_duration: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub half_open_requests: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub window_size: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub error_status_codes: Vec<i32>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct CORSConfig {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub allowed_origins: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub allowed_methods: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub allowed_headers: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub exposed_headers: Vec<String>,
#[serde(default)]
pub allow_credentials: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub max_age: Option<i32>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ObservabilityConfig {
#[serde(skip_serializing_if = "Option::is_none")]
pub tracing: Option<TracingConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
pub metric_labels: Option<HashMap<String, String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub log_level: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub sampling_rate: Option<f64>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct TracingConfig {
#[serde(skip_serializing_if = "Option::is_none")]
pub propagation_format: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub service_name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub trace_id_header: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub baggage_headers: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct GracefulShutdownConfig {
#[serde(skip_serializing_if = "Option::is_none")]
pub drain_timeout: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub shutdown_delay: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub health_failure_threshold: Option<i32>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct MiddlewareDeclaration {
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub order: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub config: Option<HashMap<String, serde_json::Value>>,
#[serde(default)]
pub required: bool,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct CacheConfig {
pub enabled: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub ttl: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub vary_headers: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub key_template: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub stale_while_revalidate: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub cacheable_statuses: Vec<i32>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct LoadBalancingConfig {
pub strategy: LoadBalancingStrategy,
#[serde(skip_serializing_if = "Option::is_none")]
pub sticky_session: Option<StickySessionConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
pub health_check: Option<LBHealthCheckConfig>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum LoadBalancingStrategy {
RoundRobin,
LeastConnections,
WeightedRoundRobin,
IpHash,
Random,
ConsistentHash,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct StickySessionConfig {
pub enabled: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub cookie_name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub ttl: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct LBHealthCheckConfig {
#[serde(skip_serializing_if = "Option::is_none")]
pub interval: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub timeout: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub unhealthy_threshold: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub healthy_threshold: Option<i32>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct APIVersioningConfig {
pub strategy: VersioningStrategy,
#[serde(skip_serializing_if = "Option::is_none")]
pub current_version: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub supported_versions: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub default_version: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub header_name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub query_param: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub deprecation_policy: Option<VersionDeprecationPolicy>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum VersioningStrategy {
UrlPath,
Header,
QueryParam,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct VersionDeprecationPolicy {
#[serde(skip_serializing_if = "Option::is_none")]
pub sunset_date: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub deprecation_date: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct TransformationConfig {
#[serde(skip_serializing_if = "Option::is_none")]
pub request_headers: Option<HashMap<String, String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub response_headers: Option<HashMap<String, String>>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub remove_request_headers: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub remove_response_headers: Vec<String>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_schema_type_serde() {
let schema_type = SchemaType::OpenAPI;
let json = serde_json::to_string(&schema_type).unwrap();
assert_eq!(json, "\"openapi\"");
let deserialized: SchemaType = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized, schema_type);
}
#[test]
fn test_schema_type_is_valid() {
assert!(SchemaType::OpenAPI.is_valid());
assert!(SchemaType::AsyncAPI.is_valid());
assert!(SchemaType::GRPC.is_valid());
}
#[test]
fn test_location_type_serde() {
let location = LocationType::HTTP;
let json = serde_json::to_string(&location).unwrap();
assert_eq!(json, "\"http\"");
}
#[test]
fn test_mount_strategy_default() {
let strategy = MountStrategy::default();
assert_eq!(strategy, MountStrategy::Service);
}
#[test]
fn test_schema_manifest_serde() {
let manifest = SchemaManifest {
version: "1.0.0".to_string(),
service_name: "test-service".to_string(),
service_version: "v1.0.0".to_string(),
instance_id: "instance-123".to_string(),
instance: None,
schemas: vec![],
capabilities: vec!["rest".to_string()],
endpoints: SchemaEndpoints {
health: "/health".to_string(),
..Default::default()
},
routing: RoutingConfig::default(),
auth: None,
webhook: None,
hints: None,
route_table: vec![],
updated_at: 1234567890,
checksum: "abc123".to_string(),
routes_checksum: None,
};
let json = serde_json::to_string(&manifest).unwrap();
let deserialized: SchemaManifest = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized.service_name, "test-service");
}
}