use serde::{Deserialize, Serialize};
use serde_json::{json, Value};
use std::collections::HashMap;
#[derive(Debug, Deserialize)]
pub struct InsomniaExport {
#[serde(rename = "__export_format")]
pub export_format: Option<i32>,
#[serde(rename = "_type")]
pub export_type: Option<String>,
pub resources: Vec<InsomniaResource>,
}
#[derive(Debug, Deserialize)]
pub struct InsomniaResource {
#[serde(rename = "_id")]
pub id: String,
#[serde(rename = "_type")]
pub resource_type: String,
pub parent_id: Option<String>,
pub name: Option<String>,
pub url: Option<String>,
pub method: Option<String>,
pub headers: Option<Vec<InsomniaHeader>>,
pub body: Option<InsomniaBody>,
pub authentication: Option<InsomniaAuth>,
pub parameters: Option<Vec<InsomniaParameter>>,
pub data: Option<Value>, pub environment: Option<String>, }
#[derive(Debug, Deserialize)]
pub struct InsomniaHeader {
pub name: String,
pub value: String,
pub disabled: Option<bool>,
}
#[derive(Debug, Deserialize)]
pub struct InsomniaBody {
pub mime_type: Option<String>,
pub text: Option<String>,
pub params: Option<Vec<InsomniaParameter>>,
}
#[derive(Debug, Deserialize)]
pub struct InsomniaParameter {
pub name: String,
pub value: String,
pub disabled: Option<bool>,
}
#[derive(Debug, Deserialize)]
pub struct InsomniaAuth {
#[serde(rename = "type")]
pub auth_type: String,
pub disabled: Option<bool>,
pub username: Option<String>,
pub password: Option<String>,
pub token: Option<String>,
pub prefix: Option<String>,
pub key: Option<String>,
pub value: Option<String>,
}
#[derive(Debug, Serialize)]
pub struct MockForgeRoute {
pub method: String,
pub path: String,
pub headers: HashMap<String, String>,
pub body: Option<String>,
pub response: MockForgeResponse,
}
#[derive(Debug, Serialize)]
pub struct MockForgeResponse {
pub status: u16,
pub headers: HashMap<String, String>,
pub body: Value,
}
#[derive(Debug)]
pub struct InsomniaImportResult {
pub routes: Vec<MockForgeRoute>,
pub variables: HashMap<String, String>,
pub warnings: Vec<String>,
}
pub fn import_insomnia_export(content: &str, environment: Option<&str>) -> Result<InsomniaImportResult, String> {
let export: InsomniaExport = serde_json::from_str(content)
.map_err(|e| format!("Failed to parse Insomnia export: {}", e))?;
if let Some(format) = export.export_format {
if format < 3 {
return Err("Insomnia export format version 3 or higher is required".to_string());
}
}
let mut routes = Vec::new();
let mut variables = HashMap::new();
let mut warnings = Vec::new();
if let Some(env_name) = environment {
extract_environment_variables(&export.resources, env_name, &mut variables);
} else {
extract_environment_variables(&export.resources, "Base Environment", &mut variables);
}
for resource in &export.resources {
if resource.resource_type == "request" {
match convert_insomnia_request_to_route(resource, &variables) {
Ok(route) => routes.push(route),
Err(e) => warnings.push(format!("Failed to convert request '{}': {}", resource.name.as_deref().unwrap_or("unnamed"), e)),
}
}
}
Ok(InsomniaImportResult {
routes,
variables,
warnings,
})
}
fn extract_environment_variables(
resources: &[InsomniaResource],
env_name: &str,
variables: &mut HashMap<String, String>,
) {
for resource in resources {
if resource.resource_type == "environment" && resource.name.as_deref() == Some(env_name) {
if let Some(data) = &resource.data {
if let Some(obj) = data.as_object() {
for (key, value) in obj {
if let Some(str_value) = value.as_str() {
variables.insert(key.clone(), str_value.to_string());
} else if let Some(num_value) = value.as_f64() {
variables.insert(key.clone(), num_value.to_string());
} else if let Some(bool_value) = value.as_bool() {
variables.insert(key.clone(), bool_value.to_string());
}
}
}
}
}
}
}
fn convert_insomnia_request_to_route(
resource: &InsomniaResource,
variables: &HashMap<String, String>,
) -> Result<MockForgeRoute, String> {
let method = resource.method.as_deref()
.ok_or("Request missing method")?
.to_uppercase();
let raw_url = resource.url.as_deref()
.ok_or("Request missing URL")?;
let url = resolve_variables(raw_url, variables);
let path = extract_path_from_url(&url)?;
let mut headers = HashMap::new();
if let Some(resource_headers) = &resource.headers {
for header in resource_headers {
if !header.disabled.unwrap_or(false) && !header.name.is_empty() {
headers.insert(
header.name.clone(),
resolve_variables(&header.value, variables),
);
}
}
}
if let Some(auth) = &resource.authentication {
if !auth.disabled.unwrap_or(false) {
add_auth_headers(auth, &mut headers, variables);
}
}
let body = extract_request_body(resource, variables);
let response = generate_mock_response(&method);
Ok(MockForgeRoute {
method,
path,
headers,
body,
response,
})
}
fn extract_path_from_url(url: &str) -> Result<String, String> {
if let Ok(parsed_url) = url::Url::parse(url) {
Ok(parsed_url.path().to_string())
} else if url.starts_with('/') {
Ok(url.to_string())
} else {
Ok(format!("/{}", url))
}
}
fn add_auth_headers(
auth: &InsomniaAuth,
headers: &mut HashMap<String, String>,
variables: &HashMap<String, String>,
) {
match auth.auth_type.as_str() {
"bearer" => {
if let Some(token) = &auth.token {
let resolved_token = resolve_variables(token, variables);
headers.insert("Authorization".to_string(), format!("Bearer {}", resolved_token));
}
}
"basic" => {
if let (Some(username), Some(password)) = (&auth.username, &auth.password) {
let user = resolve_variables(username, variables);
let pass = resolve_variables(password, variables);
use base64::{Engine as _, engine::general_purpose};
let credentials = general_purpose::STANDARD.encode(format!("{}:{}", user, pass));
headers.insert("Authorization".to_string(), format!("Basic {}", credentials));
}
}
"apikey" => {
if let (Some(key), Some(value)) = (&auth.key, &auth.value) {
let resolved_key = resolve_variables(key, variables);
let resolved_value = resolve_variables(value, variables);
headers.insert(resolved_key, resolved_value);
}
}
_ => {
}
}
}
fn extract_request_body(resource: &InsomniaResource, variables: &HashMap<String, String>) -> Option<String> {
if let Some(body) = &resource.body {
if let Some(text) = &body.text {
return Some(resolve_variables(text, variables));
}
}
None
}
fn resolve_variables(input: &str, variables: &HashMap<String, String>) -> String {
let mut result = input.to_string();
for (key, value) in variables {
let pattern = format!("{{{{{}}}}}", key);
result = result.replace(&pattern, value);
}
result
}
fn generate_mock_response(method: &str) -> MockForgeResponse {
let mut headers = HashMap::new();
headers.insert("Content-Type".to_string(), "application/json".to_string());
let body = match method {
"GET" => json!({"message": "Mock GET response", "method": "GET"}),
"POST" => json!({"message": "Mock POST response", "method": "POST", "created": true}),
"PUT" => json!({"message": "Mock PUT response", "method": "PUT", "updated": true}),
"DELETE" => json!({"message": "Mock DELETE response", "method": "DELETE", "deleted": true}),
"PATCH" => json!({"message": "Mock PATCH response", "method": "PATCH", "patched": true}),
_ => json!({"message": "Mock response", "method": method}),
};
MockForgeResponse {
status: 200,
headers,
body,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_insomnia_export() {
let export_json = r#"{
"__export_format": 4,
"_type": "export",
"resources": [
{
"_id": "req_1",
"_type": "request",
"name": "Get Users",
"method": "GET",
"url": "{{baseUrl}}/users",
"headers": [
{"name": "Authorization", "value": "Bearer {{token}}"}
],
"authentication": {
"type": "bearer",
"token": "{{token}}"
}
},
{
"_id": "env_1",
"_type": "environment",
"name": "Base Environment",
"data": {
"baseUrl": "https://api.example.com",
"token": "test-token"
}
}
]
}"#;
let result = import_insomnia_export(export_json, Some("Base Environment")).unwrap();
assert_eq!(result.routes.len(), 1);
assert_eq!(result.routes[0].method, "GET");
assert_eq!(result.routes[0].path, "/users");
assert!(result.routes[0].headers.contains_key("Authorization"));
assert!(result.variables.contains_key("baseUrl"));
}
#[test]
fn test_insomnia_format_validation() {
let old_format = r#"{
"__export_format": 2,
"resources": []
}"#;
let result = import_insomnia_export(old_format, None);
assert!(result.is_err());
assert!(result.unwrap_err().contains("version 3 or higher"));
}
}