feat: 实现配置加载系统与多级目录递归扫描
- 定义支持“一接口一文件”与“一文件多接口”的数据模型 - 实现基于路径首段的 HashMap 索引构建逻辑 - 新增集成测试 tests/integration_test.rs,验证 YAML 解析与目录递归加载 - 优化 Cargo.toml 配置,解决连字符项目名引用问题
This commit is contained in:
74
src/config.rs
Normal file
74
src/config.rs
Normal file
@@ -0,0 +1,74 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// 顶层包装:支持单对象或数组,自动打平
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum MockSource {
|
||||
/// 对应“一接口一文件”模式
|
||||
Single(MockRule),
|
||||
/// 对应“一文件多接口”模式
|
||||
Multiple(Vec<MockRule>),
|
||||
}
|
||||
|
||||
impl MockSource {
|
||||
/// 将不同的解析模式统一转化为列表,供 Loader 构建索引
|
||||
pub fn flatten(self) -> Vec<MockRule> {
|
||||
match self {
|
||||
Self::Single(rule) => vec![rule],
|
||||
Self::Multiple(rules) => rules,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 核心 Mock 规则定义
|
||||
#[derive(Debug, Deserialize,Serialize, Clone,PartialEq)]
|
||||
pub struct MockRule {
|
||||
pub id: String,
|
||||
pub request: RequestMatcher,
|
||||
pub response: MockResponse,
|
||||
pub settings: Option<MockSettings>,
|
||||
}
|
||||
|
||||
/// 请求匹配条件
|
||||
#[derive(Debug, Deserialize,Serialize, Clone,PartialEq)]
|
||||
pub struct RequestMatcher {
|
||||
pub method: String,
|
||||
pub path: String,
|
||||
/// 选填:只有请求包含这些参数时才匹配
|
||||
pub query_params: Option<HashMap<String, String>>,
|
||||
/// 选填:只有请求包含这些 Header 时才匹配
|
||||
pub headers: Option<HashMap<String, String>>,
|
||||
}
|
||||
|
||||
/// 响应内容定义
|
||||
#[derive(Debug, Deserialize,Serialize, Clone,PartialEq)]
|
||||
pub struct MockResponse {
|
||||
pub status: u16,
|
||||
pub headers: Option<HashMap<String, String>>,
|
||||
/// 统一字段:支持 Inline 文本或 file:// 前缀的路径
|
||||
pub body: String,
|
||||
}
|
||||
|
||||
/// 模拟器行为设置
|
||||
#[derive(Debug, Deserialize,Serialize, Clone,PartialEq)]
|
||||
pub struct MockSettings {
|
||||
/// 模拟网络延迟(毫秒)
|
||||
pub delay_ms: Option<u64>,
|
||||
}
|
||||
|
||||
impl MockResponse {
|
||||
/// 辅助方法:判断是否为文件协议
|
||||
pub fn is_file_protocol(&self) -> bool {
|
||||
self.body.starts_with("file://")
|
||||
}
|
||||
|
||||
/// 获取去掉协议前缀后的纯物理路径
|
||||
pub fn get_file_path(&self) -> Option<&str> {
|
||||
if self.is_file_protocol() {
|
||||
Some(&self.body[7..]) // 截取 "file://" 之后的内容
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
3
src/lib.rs
Normal file
3
src/lib.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
// 声明模块并设为 pub,这样 tests/ 目录才能看到它们
|
||||
pub mod config;
|
||||
pub mod loader;
|
||||
58
src/loader.rs
Normal file
58
src/loader.rs
Normal file
@@ -0,0 +1,58 @@
|
||||
use std::collections::HashMap;
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
use walkdir::WalkDir; // 需在 Cargo.toml 添加 walkdir 依赖
|
||||
|
||||
use crate::config::{MockRule, MockSource}; // 假设 config.rs 中定义了这两个类型
|
||||
|
||||
pub struct MockLoader;
|
||||
|
||||
impl MockLoader {
|
||||
/// 递归扫描指定目录并构建索引表
|
||||
pub fn load_all_from_dir(dir: &Path) -> HashMap<String, Vec<MockRule>> {
|
||||
let mut index: HashMap<String, Vec<MockRule>> = HashMap::new();
|
||||
|
||||
// 1. 使用 walkdir 递归遍历目录,不限层级
|
||||
for entry in WalkDir::new(dir)
|
||||
.into_iter()
|
||||
.filter_map(|e| e.ok())
|
||||
.filter(|e| e.path().extension().map_or(false, |ext| ext == "yaml" || ext == "yml"))
|
||||
{
|
||||
if let Some(rules) = Self::parse_yaml_file(entry.path()) {
|
||||
for rule in rules {
|
||||
// 2. 提取路径首段作为索引 Key
|
||||
let key = Self::extract_first_segment(&rule.request.path);
|
||||
|
||||
// 3. 将规则插入到对应的索引桶中
|
||||
index.entry(key).or_insert_with(Vec::new).push(rule);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
println!("Successfully loaded {} segments from {:?}", index.len(), dir);
|
||||
index
|
||||
}
|
||||
|
||||
/// 解析单个 YAML 文件,支持单接口和多接口模式
|
||||
fn parse_yaml_file(path: &Path) -> Option<Vec<MockRule>> {
|
||||
let content = fs::read_to_string(path).ok()?;
|
||||
|
||||
// 利用 serde_yaml 的反序列化能力处理 MockSource 枚举
|
||||
match serde_yaml::from_str::<MockSource>(&content) {
|
||||
Ok(source) => Some(source.flatten()), // 统一打平为 Vec<MockRule>
|
||||
Err(e) => {
|
||||
eprintln!("Failed to parse YAML at {:?}: {}", path, e);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 提取路径的第一级作为 Key(例如 "/api/v1/login" -> "api")
|
||||
fn extract_first_segment(path: &str) -> String {
|
||||
path.trim_start_matches('/')
|
||||
.split('/')
|
||||
.next()
|
||||
.unwrap_or("root") // 如果是根路径 "/",则归类到 "root"
|
||||
.to_string()
|
||||
}
|
||||
}
|
||||
12
src/main.rs
12
src/main.rs
@@ -1,3 +1,9 @@
|
||||
fn main() {
|
||||
println!("Hello, mock-server!");
|
||||
}
|
||||
// 使用项目名(下划线形式)引用 lib 中的内容
|
||||
use mock_server::loader::MockLoader;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
// 你的启动逻辑...
|
||||
let index = MockLoader::load_all_from_dir(std::path::Path::new("./mocks"));
|
||||
println!("服务启动中...");
|
||||
}
|
||||
Reference in New Issue
Block a user