use super::{BodyShape, ScaffoldRequest, Todo};
use crate::error::TarnError;
use std::collections::BTreeMap;
pub fn scaffold_from_explicit(
method: &str,
url: &str,
) -> Result<(ScaffoldRequest, Vec<Todo>), TarnError> {
let method_upper = method.trim().to_ascii_uppercase();
if method_upper.is_empty() {
return Err(TarnError::Validation(
"tarn scaffold --method requires a non-empty value".into(),
));
}
if !method_upper
.chars()
.all(|c| c.is_ascii_alphabetic() || c == '-' || c == '_')
{
return Err(TarnError::Validation(format!(
"tarn scaffold --method received '{}' which is not a valid HTTP method",
method
)));
}
let trimmed_url = url.trim();
if trimmed_url.is_empty() {
return Err(TarnError::Validation(
"tarn scaffold --url requires a non-empty value".into(),
));
}
let step_name = format!("{} {}", method_upper, path_or_url(trimmed_url));
let file_name = default_file_name(&method_upper, trimmed_url);
let mut request = ScaffoldRequest::new(file_name, step_name);
request.method = method_upper.clone();
request.url = trimmed_url.to_string();
let mutating = matches!(method_upper.as_str(), "POST" | "PUT" | "PATCH" | "DELETE");
if mutating && !matches!(method_upper.as_str(), "DELETE") {
let mut headers = BTreeMap::new();
headers.insert("Content-Type".into(), "application/json".into());
request.headers = headers;
let mut placeholder = serde_json::Map::new();
placeholder.insert("field".into(), serde_json::Value::Null);
request.body = Some(BodyShape::Json(serde_json::Value::Object(placeholder)));
}
Ok((request, Vec::new()))
}
fn path_or_url(url: &str) -> String {
if let Some(idx) = url.find("://") {
let rest = &url[idx + 3..];
if let Some(slash) = rest.find('/') {
let path = &rest[slash..];
if !path.is_empty() {
return path.to_string();
}
}
}
url.to_string()
}
fn default_file_name(method: &str, url: &str) -> String {
format!("{} {}", method, path_or_url(url))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn explicit_post_seeds_content_type_and_body_placeholder() {
let (req, _) = scaffold_from_explicit("POST", "http://example.com/users").unwrap();
assert_eq!(req.method, "POST");
assert_eq!(req.url, "http://example.com/users");
assert_eq!(
req.headers.get("Content-Type").map(String::as_str),
Some("application/json")
);
assert!(matches!(req.body, Some(BodyShape::Json(_))));
}
#[test]
fn explicit_get_has_no_body_or_content_type() {
let (req, _) = scaffold_from_explicit("GET", "http://example.com/health").unwrap();
assert_eq!(req.method, "GET");
assert!(req.headers.is_empty());
assert!(req.body.is_none());
}
#[test]
fn explicit_delete_has_no_body_but_no_content_type() {
let (req, _) = scaffold_from_explicit("DELETE", "http://example.com/users/1").unwrap();
assert_eq!(req.method, "DELETE");
assert!(req.body.is_none());
assert!(req.headers.is_empty());
}
#[test]
fn explicit_normalizes_method_case() {
let (req, _) = scaffold_from_explicit("post", "http://example.com/x").unwrap();
assert_eq!(req.method, "POST");
}
#[test]
fn explicit_rejects_empty_method_and_url() {
assert!(scaffold_from_explicit("", "http://x/y").is_err());
assert!(scaffold_from_explicit("GET", " ").is_err());
}
#[test]
fn explicit_rejects_garbage_method() {
assert!(scaffold_from_explicit("GET /users", "http://x/y").is_err());
}
#[test]
fn explicit_step_name_prefers_path_over_full_url() {
let (req, _) = scaffold_from_explicit("GET", "http://example.com/api/v1/widgets").unwrap();
assert_eq!(req.step_name, "GET /api/v1/widgets");
}
}