use std::collections::HashMap;
use crate::generator::ast::{
FieldDef, ParameterLocation, ParsedPath, PathParseError, PathSegment, TypeRef, tokens::FieldNameToken,
};
#[test]
fn parse_literal_segment() {
let params = HashMap::new();
let segment = PathSegment::parse("pets", ¶ms).unwrap();
assert!(matches!(segment, PathSegment::Literal(s) if s == "pets"));
}
#[test]
fn parse_single_param_unknown() {
let params = HashMap::new();
let segment = PathSegment::parse("{id}", ¶ms).unwrap();
assert!(matches!(segment, PathSegment::Param(_)));
}
#[test]
fn parse_single_param_known() {
let field = FieldNameToken::new("pet_id");
let params: HashMap<&str, &FieldNameToken> = [("id", &field)].into_iter().collect();
let segment = PathSegment::parse("{id}", ¶ms).unwrap();
let PathSegment::Param(token) = segment else {
panic!("expected Param segment");
};
assert_eq!(token.to_string(), "pet_id");
}
#[test]
fn parse_mixed_segment_prefix() {
let params = HashMap::new();
let segment = PathSegment::parse("v{version}", ¶ms).unwrap();
let PathSegment::Mixed {
format,
params: field_params,
} = segment
else {
panic!("expected Mixed segment");
};
assert_eq!(format, "v{}");
assert_eq!(field_params.len(), 1);
}
#[test]
fn parse_mixed_segment_suffix() {
let params = HashMap::new();
let segment = PathSegment::parse("{name}.json", ¶ms).unwrap();
let PathSegment::Mixed {
format,
params: field_params,
} = segment
else {
panic!("expected Mixed segment");
};
assert_eq!(format, "{}.json");
assert_eq!(field_params.len(), 1);
}
#[test]
fn parse_mixed_segment_both() {
let params = HashMap::new();
let segment = PathSegment::parse("v{version}.json", ¶ms).unwrap();
let PathSegment::Mixed {
format,
params: field_params,
} = segment
else {
panic!("expected Mixed segment");
};
assert_eq!(format, "v{}.json");
assert_eq!(field_params.len(), 1);
}
#[test]
fn parse_mixed_segment_adjacent_params() {
let params = HashMap::new();
let segment = PathSegment::parse("{a}{b}", ¶ms).unwrap();
let PathSegment::Mixed {
format,
params: field_params,
} = segment
else {
panic!("expected Mixed segment");
};
assert_eq!(format, "{}{}");
assert_eq!(field_params.len(), 2);
}
#[test]
fn parse_unclosed_brace_error() {
let params = HashMap::new();
let result = PathSegment::parse("{unclosed", ¶ms);
assert!(matches!(result, Err(PathParseError::UnclosedBrace { .. })));
}
#[test]
fn parse_empty_parameter_error() {
let params = HashMap::new();
let result = PathSegment::parse("{}", ¶ms);
assert!(matches!(result, Err(PathParseError::EmptyParameter { .. })));
}
#[test]
fn parse_unmatched_closing_brace_error() {
let params = HashMap::new();
let result = PathSegment::parse("foo}bar", ¶ms);
assert!(matches!(result, Err(PathParseError::UnmatchedClosingBrace { .. })));
}
#[test]
fn parse_nested_braces_error() {
let params = HashMap::new();
let result = PathSegment::parse("{outer{inner}}", ¶ms);
assert!(matches!(result, Err(PathParseError::NestedBraces { .. })));
}
#[test]
fn parse_empty_segment() {
let params = HashMap::new();
let segment = PathSegment::parse("", ¶ms).unwrap();
assert!(matches!(segment, PathSegment::Literal(s) if s.is_empty()));
}
#[test]
fn extract_template_params_simple() {
let params = ParsedPath::extract_template_params("/projects/{projectKey}/repos/{repositorySlug}").collect::<Vec<_>>();
assert_eq!(params, vec!["projectKey", "repositorySlug"]);
}
#[test]
fn extract_template_params_none() {
let params = ParsedPath::extract_template_params("/api/v1/status").collect::<Vec<_>>();
assert!(params.is_empty());
}
#[test]
fn extract_template_params_single() {
let params = ParsedPath::extract_template_params("/users/{id}").collect::<Vec<_>>();
assert_eq!(params, vec!["id"]);
}
#[test]
fn extract_template_params_adjacent() {
let params = ParsedPath::extract_template_params("/{a}{b}/{c}").collect::<Vec<_>>();
assert_eq!(params, vec!["a", "b", "c"]);
}
#[test]
fn extract_template_params_skips_empty() {
let params = ParsedPath::extract_template_params("/foo/{}/bar/{id}").collect::<Vec<_>>();
assert_eq!(params, vec!["id"]);
}
#[test]
fn extract_template_params_handles_unclosed() {
let params = ParsedPath::extract_template_params("/foo/{unclosed").collect::<Vec<_>>();
assert!(params.is_empty());
}
#[test]
fn parsed_path_empty() {
let path = ParsedPath::parse("/", &[]).unwrap();
assert!(path.segments.is_empty());
assert!(path.query_string.is_none());
}
#[test]
fn parsed_path_simple_literal() {
let path = ParsedPath::parse("/api/v1/pets", &[]).unwrap();
assert_eq!(path.segments.len(), 3);
assert!(matches!(&path.segments[0], PathSegment::Literal(s) if s == "api"));
assert!(matches!(&path.segments[1], PathSegment::Literal(s) if s == "v1"));
assert!(matches!(&path.segments[2], PathSegment::Literal(s) if s == "pets"));
assert!(path.query_string.is_none());
}
#[test]
fn parsed_path_returns_error_on_invalid_segment() {
let result = ParsedPath::parse("/valid/{unclosed/other", &[]);
assert!(result.is_err());
}
fn make_path_param(name: &str, original: &str) -> FieldDef {
FieldDef::builder()
.name(FieldNameToken::new(name))
.rust_type(TypeRef::new("String"))
.parameter_location(ParameterLocation::Path)
.original_name(original.to_string())
.build()
}
#[test]
fn to_axum_path_empty() {
let path = ParsedPath {
segments: vec![],
query_string: None,
};
assert_eq!(path.to_axum_path(), "/");
}
#[test]
fn to_axum_path_literal_only() {
let path = ParsedPath {
segments: vec![
PathSegment::Literal("api".to_string()),
PathSegment::Literal("v1".to_string()),
PathSegment::Literal("users".to_string()),
],
query_string: None,
};
assert_eq!(path.to_axum_path(), "/api/v1/users");
}
#[test]
fn to_axum_path_with_params() {
let path = ParsedPath {
segments: vec![
PathSegment::Literal("users".to_string()),
PathSegment::Param(FieldNameToken::new("user_id")),
PathSegment::Literal("posts".to_string()),
PathSegment::Param(FieldNameToken::new("post_id")),
],
query_string: None,
};
assert_eq!(path.to_axum_path(), "/users/{user_id}/posts/{post_id}");
}
#[test]
fn to_axum_path_with_mixed_segment() {
let path = ParsedPath {
segments: vec![
PathSegment::Literal("files".to_string()),
PathSegment::Mixed {
format: "v{}.json".to_string(),
params: vec![FieldNameToken::new("version")],
},
],
query_string: None,
};
assert_eq!(path.to_axum_path(), "/files/v{version}.json");
assert!(path.has_mixed_segments());
}
#[test]
fn to_axum_path_mixed_multiple_params() {
let path = ParsedPath {
segments: vec![PathSegment::Mixed {
format: "{}.{}".to_string(),
params: vec![FieldNameToken::new("major"), FieldNameToken::new("minor")],
}],
query_string: None,
};
assert_eq!(path.to_axum_path(), "/{major}.{minor}");
}
#[test]
fn has_mixed_segments_false() {
let path = ParsedPath {
segments: vec![
PathSegment::Literal("users".to_string()),
PathSegment::Param(FieldNameToken::new("id")),
],
query_string: None,
};
assert!(!path.has_mixed_segments());
}
#[test]
fn parse_and_convert_to_axum_sanitizes_names() {
let params = vec![make_path_param("user_id", "user-id")];
let path = ParsedPath::parse("/users/{user-id}", ¶ms).unwrap();
assert_eq!(path.to_axum_path(), "/users/{user_id}");
}
#[test]
fn parse_and_convert_mixed_sanitizes_names() {
let params = vec![make_path_param("api_version", "api-version")];
let path = ParsedPath::parse("/files/v{api-version}.json", ¶ms).unwrap();
assert_eq!(path.to_axum_path(), "/files/v{api_version}.json");
assert!(path.has_mixed_segments());
}
#[test]
fn parse_path_with_query_string() {
let path = ParsedPath::parse("/v1/messages?beta=true", &[]).unwrap();
assert_eq!(path.segments.len(), 2);
assert!(matches!(&path.segments[0], PathSegment::Literal(s) if s == "v1"));
assert!(matches!(&path.segments[1], PathSegment::Literal(s) if s == "messages"));
assert_eq!(path.query_string, Some("beta=true".to_string()));
}
#[test]
fn parse_path_with_query_string_and_param() {
let params = vec![make_path_param("batch_id", "batch_id")];
let path = ParsedPath::parse("/v1/batches/{batch_id}?beta=true", ¶ms).unwrap();
assert_eq!(path.segments.len(), 3);
assert!(matches!(&path.segments[0], PathSegment::Literal(s) if s == "v1"));
assert!(matches!(&path.segments[1], PathSegment::Literal(s) if s == "batches"));
assert!(matches!(&path.segments[2], PathSegment::Param(_)));
assert_eq!(path.query_string, Some("beta=true".to_string()));
}
#[test]
fn parse_path_with_multiple_query_params() {
let path = ParsedPath::parse("/api/endpoint?foo=bar&baz=qux", &[]).unwrap();
assert_eq!(path.segments.len(), 2);
assert_eq!(path.query_string, Some("foo=bar&baz=qux".to_string()));
}
#[test]
fn to_axum_path_ignores_query_string() {
let path = ParsedPath::parse("/v1/messages?beta=true", &[]).unwrap();
assert_eq!(path.to_axum_path(), "/v1/messages");
}