use std::path::PathBuf;
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum Framework {
FastAPI,
Flask,
Django,
Express,
NestJS,
SpringBoot,
Laravel,
Actix,
Axum,
Gin,
OpenAPI, Unknown,
}
impl Framework {
pub fn as_str(&self) -> &str {
match self {
Framework::FastAPI => "FastAPI",
Framework::Flask => "Flask",
Framework::Django => "Django",
Framework::Express => "Express",
Framework::NestJS => "NestJS",
Framework::SpringBoot => "Spring Boot",
Framework::Laravel => "Laravel",
Framework::Actix => "Actix",
Framework::Axum => "Axum",
Framework::Gin => "Gin",
Framework::OpenAPI => "OpenAPI",
Framework::Unknown => "Unknown",
}
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum AuthRequirement {
None,
Bearer,
Basic,
ApiKey { header: String },
OAuth2,
Custom(String),
}
impl AuthRequirement {
pub fn as_str(&self) -> &str {
match self {
AuthRequirement::None => "None",
AuthRequirement::Bearer => "Bearer",
AuthRequirement::Basic => "Basic",
AuthRequirement::ApiKey { .. } => "API Key",
AuthRequirement::OAuth2 => "OAuth2",
AuthRequirement::Custom(s) => s,
}
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum ParameterLocation {
Path,
Query,
Header,
Cookie,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct Parameter {
pub name: String,
pub location: ParameterLocation,
pub required: bool,
pub param_type: String,
pub description: Option<String>,
pub default: Option<String>,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct BodySchema {
pub content_type: String,
pub schema_name: Option<String>,
pub required: bool,
pub example: Option<String>,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct DiscoveredEndpoint {
pub method: String,
pub path: String,
pub operation_id: Option<String>,
pub summary: Option<String>,
pub description: Option<String>,
pub source_file: Option<PathBuf>,
pub line_number: Option<usize>,
pub parameters: Vec<Parameter>,
pub body: Option<BodySchema>,
pub auth: AuthRequirement,
pub tags: Vec<String>,
pub deprecated: bool,
}
impl DiscoveredEndpoint {
pub fn new(method: impl Into<String>, path: impl Into<String>) -> Self {
DiscoveredEndpoint {
method: method.into().to_uppercase(),
path: path.into(),
operation_id: None,
summary: None,
description: None,
source_file: None,
line_number: None,
parameters: Vec::new(),
body: None,
auth: AuthRequirement::None,
tags: Vec::new(),
deprecated: false,
}
}
#[allow(dead_code)] pub fn display_title(&self) -> String {
self.summary.clone()
.or_else(|| self.operation_id.clone())
.unwrap_or_else(|| self.path.clone())
}
}
#[derive(Clone, Debug)]
#[allow(dead_code)] pub struct WorkspaceProject {
pub root: PathBuf,
pub framework: Framework,
pub base_url: Option<String>,
pub title: Option<String>,
pub version: Option<String>,
pub endpoints: Vec<DiscoveredEndpoint>,
}
impl WorkspaceProject {
pub fn new(root: PathBuf) -> Self {
WorkspaceProject {
root,
framework: Framework::Unknown,
base_url: None,
title: None,
version: None,
endpoints: Vec::new(),
}
}
#[allow(dead_code)] pub fn grouped_endpoints(&self) -> Vec<(String, Vec<&DiscoveredEndpoint>)> {
use std::collections::BTreeMap;
let mut groups: BTreeMap<String, Vec<&DiscoveredEndpoint>> = BTreeMap::new();
for endpoint in &self.endpoints {
let group = if !endpoint.tags.is_empty() {
endpoint.tags[0].clone()
} else {
endpoint.path
.trim_start_matches('/')
.split('/')
.next()
.unwrap_or("root")
.to_string()
};
groups.entry(group).or_default().push(endpoint);
}
groups.into_iter().collect()
}
}