use std::collections::HashMap;
use chrono::{DateTime, Utc};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use utoipa::ToSchema;
use uuid::Uuid;
use crate::McpClientTransport;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, JsonSchema, ToSchema)]
#[serde(rename_all = "snake_case")]
pub enum ConnectionAuthType {
OAuth2,
Secret,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, JsonSchema, ToSchema)]
#[serde(rename_all = "snake_case")]
pub enum ConnectionStatus {
Connected,
Disconnected,
Expired,
NeedsSetup,
Partial,
Pending,
Error,
}
impl std::fmt::Display for ConnectionStatus {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ConnectionStatus::Connected => write!(f, "connected"),
ConnectionStatus::Disconnected => write!(f, "disconnected"),
ConnectionStatus::Expired => write!(f, "expired"),
ConnectionStatus::NeedsSetup => write!(f, "needs_setup"),
ConnectionStatus::Partial => write!(f, "partial"),
ConnectionStatus::Pending => write!(f, "pending"),
ConnectionStatus::Error => write!(f, "error"),
}
}
}
impl std::str::FromStr for ConnectionStatus {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"connected" => Ok(ConnectionStatus::Connected),
"disconnected" => Ok(ConnectionStatus::Disconnected),
"expired" => Ok(ConnectionStatus::Expired),
"needs_setup" => Ok(ConnectionStatus::NeedsSetup),
"partial" => Ok(ConnectionStatus::Partial),
"pending" => Ok(ConnectionStatus::Pending),
"error" => Ok(ConnectionStatus::Error),
_ => Err(anyhow::anyhow!("unknown connection status: {}", s)),
}
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, JsonSchema, ToSchema)]
#[serde(rename_all = "snake_case")]
pub enum AuthScope {
Public,
Workspace,
User,
}
impl std::fmt::Display for AuthScope {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Public => write!(f, "public"),
Self::Workspace => write!(f, "workspace"),
Self::User => write!(f, "user"),
}
}
}
impl std::str::FromStr for AuthScope {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"public" => Ok(Self::Public),
"workspace" => Ok(Self::Workspace),
"user" => Ok(Self::User),
_ => Err(anyhow::anyhow!("unknown auth_scope: {}", s)),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, ToSchema, PartialEq)]
pub struct CustomField {
pub key: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub label: Option<String>,
#[serde(default)]
pub is_secret: bool,
#[serde(default = "default_required")]
pub required: bool,
}
fn default_required() -> bool {
true
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, ToSchema, PartialEq)]
pub struct OAuthMetadata {
pub issuer: String,
pub authorization_endpoint: String,
pub token_endpoint: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub registration_endpoint: Option<String>,
#[serde(default)]
pub scopes_supported: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, ToSchema, PartialEq)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum ConnectionAuth {
None,
Oauth {
provider: String,
#[serde(default)]
scopes: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
oauth_metadata: Option<OAuthMetadata>,
},
Custom {
#[serde(default)]
fields: Vec<CustomField>,
},
DistriNative,
}
impl ConnectionAuth {
pub fn provider_name(&self) -> &str {
match self {
Self::None => "none",
Self::Oauth { provider, .. } => provider.as_str(),
Self::Custom { .. } => "custom",
Self::DistriNative => "distri",
}
}
pub fn is_oauth(&self) -> bool {
matches!(self, Self::Oauth { .. })
}
pub fn is_custom(&self) -> bool {
matches!(self, Self::Custom { .. })
}
pub fn is_distri_native(&self) -> bool {
matches!(self, Self::DistriNative)
}
pub fn custom_fields(&self) -> &[CustomField] {
match self {
Self::Custom { fields } => fields,
_ => &[],
}
}
pub fn custom_required_fields(&self) -> Vec<&CustomField> {
match self {
Self::Custom { fields } => fields.iter().filter(|f| f.required).collect(),
_ => vec![],
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, ToSchema)]
pub struct ConnectionToken {
pub access_token: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub refresh_token: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub expires_at: Option<DateTime<Utc>>,
#[serde(default = "default_token_type")]
pub token_type: String,
#[serde(default)]
pub scopes: Vec<String>,
}
fn default_token_type() -> String {
"Bearer".to_string()
}
impl ConnectionToken {
pub fn is_expired(&self) -> bool {
self.expires_at.map(|exp| exp < Utc::now()).unwrap_or(false)
}
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, ToSchema, PartialEq)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum ConnectionKind {
Default {
#[serde(default, skip_serializing_if = "Option::is_none")]
skill_content: Option<String>,
},
Mcp {
#[serde(flatten)]
mcp: McpConnectionSpec,
},
}
impl Default for ConnectionKind {
fn default() -> Self {
Self::Default {
skill_content: None,
}
}
}
impl ConnectionKind {
pub fn is_mcp(&self) -> bool {
matches!(self, Self::Mcp { .. })
}
pub fn as_mcp(&self) -> Option<&McpConnectionSpec> {
match self {
Self::Mcp { mcp } => Some(mcp),
_ => None,
}
}
pub fn skill_content(&self) -> Option<&str> {
match self {
Self::Default { skill_content } => skill_content.as_deref(),
Self::Mcp { .. } => None,
}
}
pub fn kind_str(&self) -> &'static str {
match self {
Self::Default { .. } => "default",
Self::Mcp { .. } => "mcp",
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, ToSchema, PartialEq)]
#[serde(rename_all = "snake_case")]
pub struct McpConnectionSpec {
pub transport: McpClientTransport,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub tool_filter: Option<McpToolFilter>,
#[serde(default = "default_true")]
pub enabled: bool,
}
fn default_true() -> bool {
true
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema, ToSchema, PartialEq)]
#[serde(rename_all = "snake_case")]
pub struct McpToolFilter {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub include: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub exclude: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, ToSchema)]
pub struct Connection {
pub id: Uuid,
pub workspace_id: Uuid,
pub skill_id: Uuid,
pub name: String,
pub status: ConnectionStatus,
pub config: serde_json::Value,
pub connected_by: Option<Uuid>,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
pub auth_scope: AuthScope,
pub auth: ConnectionAuth,
#[serde(default)]
pub kind: ConnectionKind,
#[serde(default)]
pub is_system: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, ToSchema)]
pub struct NewConnection {
pub workspace_id: Uuid,
pub skill_id: Uuid,
pub name: String,
pub status: ConnectionStatus,
pub config: serde_json::Value,
pub connected_by: Option<Uuid>,
pub auth_scope: AuthScope,
pub auth: ConnectionAuth,
#[serde(default)]
pub kind: ConnectionKind,
#[serde(default)]
pub is_system: bool,
}
impl Connection {
pub fn is_mcp(&self) -> bool {
self.kind.is_mcp()
}
pub fn mcp_spec(&self) -> Option<&McpConnectionSpec> {
self.kind.as_mcp()
}
}
#[derive(Debug, Clone, Deserialize)]
pub struct VerifyRequest {
pub url: String,
pub method: String,
#[serde(default)]
pub headers: HashMap<String, String>,
}
impl Connection {
pub fn verify_request(&self) -> Option<VerifyRequest> {
self.config
.get("verify_request")
.and_then(|v| serde_json::from_value(v.clone()).ok())
}
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, ToSchema, Default)]
pub struct ConnectionRequirement {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub provider: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub connection_id: Option<Uuid>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub scopes: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub env_var: Option<String>,
#[serde(default)]
pub required: bool,
}