Skip to content

Commit 564cd3d

Browse files
committed
Fix coverage
1 parent bfef913 commit 564cd3d

20 files changed

Lines changed: 409 additions & 136 deletions

crates/vespera/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ pub mod openapi {
2020
pub use vespera_core::openapi::OpenApi;
2121

2222
// Re-export macros from vespera_macro
23-
pub use vespera_macro::{export_app, route, schema, schema_type, vespera, Schema};
23+
pub use vespera_macro::{Schema, export_app, route, schema, schema_type, vespera};
2424

2525
// Re-export serde_json for merge feature (runtime spec merging)
2626
pub use serde_json;

crates/vespera_macro/src/parser/operation.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -487,7 +487,7 @@ mod tests {
487487

488488
#[test]
489489
fn test_single_path_param_with_single_type() {
490-
// Test line 55: Path<T> with single type (not tuple) and exactly ONE path param
490+
// Test: Path<T> with single type
491491
// This exercises the branch: path_params.len() == 1 with non-tuple type
492492
let op = build("fn get(Path(id): Path<i32>) -> String", "/users/{id}", None);
493493

@@ -515,23 +515,23 @@ mod tests {
515515

516516
#[test]
517517
fn test_non_path_extractor_with_query() {
518-
// Test lines 85, 89: non-Path extractor handling
518+
// Test: non-Path extractor handling
519519
// When input is Query<T>, it should NOT be treated as Path
520520
let op = build(
521521
"fn search(Query(params): Query<QueryParams>) -> String",
522522
"/search",
523523
None,
524524
);
525525

526-
// Query params should be extended to parameters (line 89)
526+
// Test: Query params should be extended to parameters
527527
// But QueryParams is not in known_schemas/struct_definitions so it won't appear
528528
// The key is that it doesn't treat Query as a Path extractor (line 85 returns false)
529529
assert!(op.request_body.is_none()); // Query is not a body
530530
}
531531

532532
#[test]
533533
fn test_non_path_extractor_with_state() {
534-
// Test lines 85, 89: State<T> should be ignored (not Path)
534+
// Test: State<T> should be ignored
535535
let op = build(
536536
"fn handler(State(state): State<AppState>) -> String",
537537
"/handler",
@@ -586,7 +586,7 @@ mod tests {
586586

587587
#[test]
588588
fn test_multiple_path_params_with_single_type() {
589-
// Test line 57-60: multiple path params but single type - uses type for all
589+
// Test: multiple path params but single type
590590
let op = build(
591591
"fn get(Path(id): Path<String>) -> String",
592592
"/shops/{shop_id}/items/{item_id}",
@@ -634,7 +634,7 @@ mod tests {
634634

635635
#[test]
636636
fn test_non_path_extractor_generates_params_and_extends() {
637-
// Test lines 85, 89: non-Path extractor that DOES generate params
637+
// Test: non-Path extractor that generates params
638638
// Query<T> where T is a known struct generates query parameters
639639
let sig: syn::Signature = syn::parse_str("fn search(Query(params): Query<SearchParams>, TypedHeader(auth): TypedHeader<Authorization>) -> String").unwrap();
640640

crates/vespera_macro/src/parser/parameters.rs

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -580,6 +580,24 @@ mod tests {
580580
vec![vec![ParameterLocation::Header]],
581581
"header_custom"
582582
)]
583+
#[case(
584+
"fn test(input: Form<User>) {}",
585+
vec![],
586+
vec![vec![]],
587+
"form_body"
588+
)]
589+
#[case(
590+
"fn test(upload: TypedMultipart<UploadRequest>) {}",
591+
vec![],
592+
vec![vec![]],
593+
"typed_multipart_body"
594+
)]
595+
#[case(
596+
"fn test(multipart: Multipart) {}",
597+
vec![],
598+
vec![vec![]],
599+
"raw_multipart_body"
600+
)]
583601
fn test_parse_function_parameter_cases(
584602
#[case] func_src: &str,
585603
#[case] path_params: Vec<String>,
@@ -901,7 +919,7 @@ mod tests {
901919
};
902920
let ty = Type::Path(type_path);
903921

904-
// This MUST hit line 209 because path.segments.is_empty() is true
922+
// Tests: path.segments.is_empty() is true
905923
assert!(!is_known_type(&ty, &known_schemas, &struct_definitions));
906924
}
907925

@@ -941,7 +959,7 @@ mod tests {
941959
};
942960
let ty = Type::Path(type_path);
943961

944-
// This MUST hit line 245 because path.segments.is_empty() is true
962+
// Tests: path.segments.is_empty() is true
945963
let result = parse_query_struct_to_parameters(&ty, &known_schemas, &struct_definitions);
946964
assert!(
947965
result.is_none(),

crates/vespera_macro/src/parser/request_body.rs

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -113,16 +113,15 @@ pub fn parse_request_body(
113113
}
114114

115115
// Raw Multipart extractor (untyped) → multipart/form-data with generic object schema
116-
if ident_str == "Multipart"
117-
&& matches!(segment.arguments, syn::PathArguments::None)
116+
if ident_str == "Multipart" && matches!(segment.arguments, syn::PathArguments::None)
118117
{
119118
let mut content = BTreeMap::new();
120119
content.insert(
121120
"multipart/form-data".to_string(),
122121
MediaType {
123-
schema: Some(SchemaRef::Inline(Box::new(
124-
Schema::new(SchemaType::Object),
125-
))),
122+
schema: Some(SchemaRef::Inline(Box::new(Schema::new(
123+
SchemaType::Object,
124+
)))),
126125
example: None,
127126
examples: None,
128127
},
@@ -188,6 +187,11 @@ mod tests {
188187
#[case::str("fn test(just_str: &str) {}", true, "str")]
189188
#[case::i32("fn test(just_i32: i32) {}", false, "i32")]
190189
#[case::vec_string("fn test(just_vec_string: Vec<String>) {}", false, "vec_string")]
190+
#[case::typed_multipart(
191+
"fn test(TypedMultipart(req): TypedMultipart<UploadRequest>) {}",
192+
true,
193+
"typed_multipart"
194+
)]
191195
#[case::multipart_raw("fn test(multipart: Multipart) {}", true, "multipart_raw")]
192196
#[case::self_ref("fn test(&self) {}", false, "self_ref")]
193197
fn test_parse_request_body_cases(

crates/vespera_macro/src/parser/response.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -464,7 +464,7 @@ mod tests {
464464
#[test]
465465
fn test_extract_result_types_ref_to_non_path() {
466466
// Test line 43: &(Tuple) - Reference to non-Path type
467-
// This hits the else branch at line 42-43
467+
// Tests: else branch
468468
let ty: syn::Type = syn::parse_str("&(i32, String)").unwrap();
469469
let result = extract_result_types(&ty);
470470
// The Reference's elem is a Tuple, not a Path, so line 39 condition fails
@@ -487,7 +487,7 @@ mod tests {
487487
};
488488
let ty = syn::Type::Path(type_path);
489489

490-
// This MUST hit line 48 because path.segments.is_empty() is true
490+
// Tests: path.segments.is_empty() is true
491491
let result = extract_result_types(&ty);
492492
assert!(
493493
result.is_none(),
@@ -518,7 +518,7 @@ mod tests {
518518
elem: Box::new(inner_ty),
519519
});
520520

521-
// This goes through line 38-41 (reference to path), then hits line 48
521+
// Tests: reference to path then empty segments
522522
let result = extract_result_types(&ty);
523523
assert!(
524524
result.is_none(),
@@ -535,7 +535,7 @@ mod tests {
535535
// Note: This doesn't actually work because is_keyword_type_by_type_path
536536
// checks for Result type, but ref to Result is different
537537
// The important thing is the code doesn't panic
538-
// This exercises lines 38-41 even if result is None
538+
// Tests: exercises reference path even if result is None
539539
}
540540

541541
#[test]
@@ -606,7 +606,7 @@ mod tests {
606606
// Should have 200 and 400 responses
607607
assert!(responses.contains_key("200"));
608608
let ok_response = responses.get("200").unwrap();
609-
// Headers should be None (line 95)
609+
// Headers should be None
610610
assert!(ok_response.headers.is_none());
611611
}
612612
}

crates/vespera_macro/src/parser/schema/enum_schema.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1582,7 +1582,7 @@ mod tests {
15821582
});
15831583
}
15841584

1585-
// Edge case: Empty struct variant (lines 275, 280 - empty properties/required)
1585+
// Edge case: Empty struct variant (empty properties/required)
15861586
#[test]
15871587
fn test_externally_tagged_empty_struct_variant() {
15881588
let enum_item: syn::ItemEnum = syn::parse_str(
@@ -1621,7 +1621,7 @@ mod tests {
16211621
});
16221622
}
16231623

1624-
// Edge case: Internally tagged enum with tuple variant (line 468 - continue/skip)
1624+
// Edge case: Internally tagged enum with tuple variant
16251625
#[test]
16261626
fn test_internally_tagged_skips_tuple_variant() {
16271627
let enum_item: syn::ItemEnum = syn::parse_str(
@@ -1654,7 +1654,7 @@ mod tests {
16541654
});
16551655
}
16561656

1657-
// Edge case: Untagged enum with tuple variant referencing a known schema (line 338)
1657+
// Edge case: Untagged enum with tuple variant referencing a known schema
16581658
#[test]
16591659
fn test_untagged_tuple_variant_with_known_schema_ref() {
16601660
let enum_item: syn::ItemEnum = syn::parse_str(
@@ -1704,7 +1704,7 @@ mod tests {
17041704
}
17051705
}
17061706

1707-
// Edge case: Untagged enum with multi-field tuple variant (lines 592, 600-611)
1707+
// Edge case: Untagged enum with multi-field tuple variant
17081708
#[test]
17091709
fn test_untagged_multi_field_tuple_variant() {
17101710
let enum_item: syn::ItemEnum = syn::parse_str(

crates/vespera_macro/src/parser/schema/serde_attrs.rs

Lines changed: 13 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1492,7 +1492,7 @@ mod tests {
14921492
assert!(skip_if_result);
14931493
}
14941494

1495-
/// Test extract_rename_all fallback parsing (lines 44-47)
1495+
/// Test extract_rename_all fallback parsing
14961496
#[test]
14971497
fn test_extract_rename_all_fallback_manual_parsing() {
14981498
let tokens = quote!(rename_all = "kebab-case");
@@ -1638,7 +1638,7 @@ mod tests {
16381638
}
16391639

16401640
/// Test extract_field_rename - ensure rename_all is not matched as rename
1641-
/// This tests the word boundary logic at lines 168-181
1641+
/// Test the word boundary logic
16421642
#[test]
16431643
fn test_extract_field_rename_fallback_avoids_rename_all() {
16441644
let tokens: TokenStream = "some::rename_all = \"camelCase\"".parse().unwrap();
@@ -1718,8 +1718,7 @@ mod tests {
17181718
/// Test extract_field_rename with form_data but no field_name key
17191719
#[test]
17201720
fn test_extract_field_rename_form_data_no_field_name() {
1721-
let struct_src =
1722-
r#"struct Foo { #[form_data(limit = "10MiB")] field: i32 }"#;
1721+
let struct_src = r#"struct Foo { #[form_data(limit = "10MiB")] field: i32 }"#;
17231722
let item: syn::ItemStruct = syn::parse_str(struct_src).unwrap();
17241723
if let syn::Fields::Named(fields) = &item.fields {
17251724
let field = fields.named.first().unwrap();
@@ -1731,32 +1730,26 @@ mod tests {
17311730
/// Test extract_rename_all falls back to #[try_from_multipart(rename_all = "...")]
17321731
#[test]
17331732
fn test_extract_rename_all_try_from_multipart_fallback() {
1734-
let item: syn::ItemStruct = syn::parse_str(
1735-
r#"#[try_from_multipart(rename_all = "camelCase")] struct Foo;"#,
1736-
)
1737-
.unwrap();
1733+
let item: syn::ItemStruct =
1734+
syn::parse_str(r#"#[try_from_multipart(rename_all = "camelCase")] struct Foo;"#)
1735+
.unwrap();
17381736
let result = extract_rename_all(&item.attrs);
17391737
assert_eq!(result.as_deref(), Some("camelCase"));
17401738
}
17411739

17421740
/// Test serde rename_all takes priority over try_from_multipart rename_all
17431741
#[test]
17441742
fn test_extract_rename_all_serde_over_try_from_multipart() {
1745-
let item: syn::ItemStruct = syn::parse_str(
1746-
r#"#[serde(rename_all = "snake_case")] #[try_from_multipart(rename_all = "camelCase")] struct Foo;"#,
1747-
)
1748-
.unwrap();
1743+
let item: syn::ItemStruct = syn::parse_str(r#"#[serde(rename_all = "snake_case")] #[try_from_multipart(rename_all = "camelCase")] struct Foo;"#).unwrap();
17491744
let result = extract_rename_all(&item.attrs);
17501745
assert_eq!(result.as_deref(), Some("snake_case"));
17511746
}
17521747

17531748
/// Test extract_rename_all with try_from_multipart but no rename_all key
17541749
#[test]
17551750
fn test_extract_rename_all_try_from_multipart_no_rename_all() {
1756-
let item: syn::ItemStruct = syn::parse_str(
1757-
r#"#[try_from_multipart(strict)] struct Foo;"#,
1758-
)
1759-
.unwrap();
1751+
let item: syn::ItemStruct =
1752+
syn::parse_str(r#"#[try_from_multipart(strict)] struct Foo;"#).unwrap();
17601753
let result = extract_rename_all(&item.attrs);
17611754
assert_eq!(result, None);
17621755
}
@@ -2047,7 +2040,7 @@ mod tests {
20472040
}
20482041
}
20492042

2050-
/// Test extract_tag with non-list serde attribute (line 524)
2043+
/// Test extract_tag with non-list serde attribute
20512044
/// When require_list() fails, extract_tag should continue to next attribute
20522045
#[test]
20532046
fn test_extract_tag_non_list_attr_continues() {
@@ -2064,15 +2057,15 @@ mod tests {
20642057
assert_eq!(result.as_deref(), Some("type"));
20652058
}
20662059

2067-
/// Test extract_tag with only non-list serde attribute returns None (line 524)
2060+
/// Test extract_tag with only non-list serde attribute returns None
20682061
#[test]
20692062
fn test_extract_tag_only_non_list_attr_returns_none() {
20702063
let path_attr = create_path_only_serde_attr();
20712064
let result = extract_tag(&[path_attr]);
20722065
assert_eq!(result, None);
20732066
}
20742067

2075-
/// Test extract_content with non-list serde attribute (line 574)
2068+
/// Test extract_content with non-list serde attribute
20762069
/// When require_list() fails, extract_content should continue to next attribute
20772070
#[test]
20782071
fn test_extract_content_non_list_attr_continues() {
@@ -2089,7 +2082,7 @@ mod tests {
20892082
assert_eq!(result.as_deref(), Some("data"));
20902083
}
20912084

2092-
/// Test extract_content with only non-list serde attribute returns None (line 574)
2085+
/// Test extract_content with only non-list serde attribute returns None
20932086
#[test]
20942087
fn test_extract_content_only_non_list_attr_returns_none() {
20952088
let path_attr = create_path_only_serde_attr();

crates/vespera_macro/src/parser/schema/type_schema.rs

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -232,15 +232,15 @@ pub(crate) fn parse_type_to_schema_ref_with_schemas(
232232
format: Some("duration".to_string()),
233233
..Schema::string()
234234
})),
235-
// File upload types (axum_typed_multipart / tempfile)
236-
// FieldData<NamedTempFile> → string with binary format
237-
"FieldData" | "NamedTempFile" => SchemaRef::Inline(Box::new(Schema {
238-
format: Some("binary".to_string()),
239-
..Schema::string()
240-
})),
241-
// Standard library types that should not be referenced
242-
// Note: HashMap and BTreeMap are handled above in generic types
243-
"Vec" | "Option" | "Result" | "Json" | "Path" | "Query" | "Header" => {
235+
// File upload types (axum_typed_multipart / tempfile)
236+
// FieldData<NamedTempFile> → string with binary format
237+
"FieldData" | "NamedTempFile" => SchemaRef::Inline(Box::new(Schema {
238+
format: Some("binary".to_string()),
239+
..Schema::string()
240+
})),
241+
// Standard library types that should not be referenced
242+
// Note: HashMap and BTreeMap are handled above in generic types
243+
"Vec" | "Option" | "Result" | "Json" | "Path" | "Query" | "Header" => {
244244
// These are not schema types, return object schema
245245
SchemaRef::Inline(Box::new(Schema::new(SchemaType::Object)))
246246
}
@@ -755,7 +755,7 @@ mod tests {
755755
}
756756
}
757757

758-
// Tests for date/time types from chrono crate (lines 205-215)
758+
// Tests for date/time types from chrono crate
759759
#[rstest]
760760
#[case("DateTime", "date-time")]
761761
#[case("NaiveDateTime", "date-time")]
@@ -790,7 +790,7 @@ mod tests {
790790
}
791791
}
792792

793-
// Tests for date/time types from time crate (lines 218-228)
793+
// Tests for date/time types from time crate
794794
#[rstest]
795795
#[case("OffsetDateTime", "date-time")]
796796
#[case("PrimitiveDateTime", "date-time")]
@@ -822,7 +822,7 @@ mod tests {
822822
}
823823
}
824824

825-
// Test for Duration type (line 231-233)
825+
// Test for Duration type
826826
#[test]
827827
fn test_parse_type_to_schema_ref_duration() {
828828
let ty: Type = syn::parse_str("Duration").unwrap();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
source: crates/vespera_macro/src/parser/parameters.rs
3+
expression: parameters
4+
---
5+
[]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
source: crates/vespera_macro/src/parser/parameters.rs
3+
expression: parameters
4+
---
5+
[]

0 commit comments

Comments
 (0)