Skip to content

Commit c791bab

Browse files
committed
Refactor
1 parent 2d80e30 commit c791bab

52 files changed

Lines changed: 11704 additions & 7840 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ lcov.info
33
coverage
44
build_rs_cov.profraw
55
.sisyphus/
6+
/docs

Cargo.lock

Lines changed: 0 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/vespera_macro/Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ proc-macro2 = "1"
1717
vespera_core = { workspace = true }
1818
serde = { version = "1.0", features = ["derive"] }
1919
serde_json = "1.0"
20-
anyhow = "1.0"
2120

2221
[dev-dependencies]
2322
rstest = "0.26"

crates/vespera_macro/rustfmt.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
imports_granularity = "Crate"
2+
group_imports = "StdExternalCrate"
3+
reorder_imports = true

crates/vespera_macro/src/args.rs

Lines changed: 24 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use crate::http::is_http_method;
2+
13
pub struct RouteArgs {
24
pub method: Option<syn::Ident>,
35
pub path: Option<syn::LitStr>,
@@ -22,33 +24,26 @@ impl syn::parse::Parse for RouteArgs {
2224
// Try to parse as method identifier (get, post, etc.)
2325
let ident: syn::Ident = input.parse()?;
2426
let ident_str = ident.to_string().to_lowercase();
25-
match ident_str.as_str() {
26-
"get" | "post" | "put" | "patch" | "delete" | "head" | "options" => {
27-
method = Some(ident);
28-
}
29-
"path" => {
30-
input.parse::<syn::Token![=]>()?;
31-
let lit: syn::LitStr = input.parse()?;
32-
path = Some(lit);
33-
}
34-
"error_status" => {
35-
input.parse::<syn::Token![=]>()?;
36-
let array: syn::ExprArray = input.parse()?;
37-
error_status = Some(array);
38-
}
39-
"tags" => {
40-
input.parse::<syn::Token![=]>()?;
41-
let array: syn::ExprArray = input.parse()?;
42-
tags = Some(array);
43-
}
44-
"description" => {
45-
input.parse::<syn::Token![=]>()?;
46-
let lit: syn::LitStr = input.parse()?;
47-
description = Some(lit);
48-
}
49-
_ => {
50-
return Err(lookahead.error());
51-
}
27+
if is_http_method(&ident_str) {
28+
method = Some(ident);
29+
} else if ident_str == "path" {
30+
input.parse::<syn::Token![=]>()?;
31+
let lit: syn::LitStr = input.parse()?;
32+
path = Some(lit);
33+
} else if ident_str == "error_status" {
34+
input.parse::<syn::Token![=]>()?;
35+
let array: syn::ExprArray = input.parse()?;
36+
error_status = Some(array);
37+
} else if ident_str == "tags" {
38+
input.parse::<syn::Token![=]>()?;
39+
let array: syn::ExprArray = input.parse()?;
40+
tags = Some(array);
41+
} else if ident_str == "description" {
42+
input.parse::<syn::Token![=]>()?;
43+
let lit: syn::LitStr = input.parse()?;
44+
description = Some(lit);
45+
} else {
46+
return Err(lookahead.error());
5247
}
5348

5449
// Check if there's a comma
@@ -74,9 +69,10 @@ impl syn::parse::Parse for RouteArgs {
7469

7570
#[cfg(test)]
7671
mod tests {
77-
use super::*;
7872
use rstest::rstest;
7973

74+
use super::*;
75+
8076
#[rstest]
8177
// Method only
8278
#[case("get", true, Some("get"), None, None)]

crates/vespera_macro/src/collector.rs

Lines changed: 42 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,61 @@
11
//! Collector for routes and structs
22
3-
use crate::file_utils::{collect_files, file_to_segments};
4-
use crate::metadata::{CollectedMetadata, RouteMetadata};
5-
use crate::route::{extract_doc_comment, extract_route_info};
6-
use anyhow::{Context, Result};
73
use std::path::Path;
4+
85
use syn::Item;
96

7+
use crate::{
8+
error::{MacroResult, err_call_site},
9+
file_utils::{collect_files, file_to_segments},
10+
metadata::{CollectedMetadata, RouteMetadata},
11+
route::{extract_doc_comment, extract_route_info},
12+
};
13+
1014
/// Collect routes and structs from a folder
11-
pub fn collect_metadata(folder_path: &Path, folder_name: &str) -> Result<CollectedMetadata> {
15+
pub fn collect_metadata(folder_path: &Path, folder_name: &str) -> MacroResult<CollectedMetadata> {
1216
let mut metadata = CollectedMetadata::new();
1317

14-
let files = collect_files(folder_path).with_context(|| {
15-
format!(
16-
"Failed to collect files from wtf: {}",
17-
folder_path.display()
18-
)
18+
let files = collect_files(folder_path).map_err(|e| {
19+
err_call_site(format!(
20+
"vespera! macro: failed to scan route folder '{}': {}. Verify the folder exists and is readable.",
21+
folder_path.display(),
22+
e
23+
))
1924
})?;
2025

2126
for file in files {
2227
if !file.extension().map(|e| e == "rs").unwrap_or(false) {
2328
continue;
2429
}
2530

26-
let content = std::fs::read_to_string(&file)
27-
.with_context(|| format!("Failed to read file: {}", file.display()))?;
31+
let content = std::fs::read_to_string(&file).map_err(|e| {
32+
err_call_site(format!(
33+
"vespera! macro: failed to read route file '{}': {}. Check file permissions.",
34+
file.display(),
35+
e
36+
))
37+
})?;
2838

29-
let file_ast = syn::parse_file(&content)
30-
.with_context(|| format!("Failed to parse file: {}", file.display()))?;
39+
let file_ast = syn::parse_file(&content).map_err(|e| {
40+
err_call_site(format!(
41+
"vespera! macro: syntax error in '{}': {}. Fix the Rust syntax errors in this file.",
42+
file.display(),
43+
e
44+
))
45+
})?;
3146

3247
// Get module path
3348
let segments = file
3449
.strip_prefix(folder_path)
3550
.map(|file_stem| file_to_segments(file_stem, folder_path))
36-
.context(format!(
37-
"Failed to strip prefix from file: {} (base: {})",
38-
file.display(),
39-
folder_path.display()
40-
))?;
51+
.map_err(|e| {
52+
err_call_site(format!(
53+
"Failed to strip prefix from file: {} (base: {}): {}",
54+
file.display(),
55+
folder_path.display(),
56+
e
57+
))
58+
})?;
4159

4260
let module_path = if folder_name.is_empty() {
4361
segments.join("::")
@@ -87,11 +105,13 @@ pub fn collect_metadata(folder_path: &Path, folder_name: &str) -> Result<Collect
87105

88106
#[cfg(test)]
89107
mod tests {
90-
use super::*;
91-
use rstest::rstest;
92108
use std::fs;
109+
110+
use rstest::rstest;
93111
use tempfile::TempDir;
94112

113+
use super::*;
114+
95115
fn create_temp_file(dir: &TempDir, filename: &str, content: &str) -> std::path::PathBuf {
96116
let file_path = dir.path().join(filename);
97117
if let Some(parent) = file_path.parent() {
@@ -600,7 +620,7 @@ pub fn options_handler() -> String { "options".to_string() }
600620
// Should return error when collect_files fails
601621
assert!(result.is_err());
602622
let error_msg = result.unwrap_err().to_string();
603-
assert!(error_msg.contains("Failed to collect files"));
623+
assert!(error_msg.contains("failed to scan route folder"));
604624
}
605625

606626
#[test]

crates/vespera_macro/src/error.rs

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
//! Unified error handling for vespera_macro.
2+
//!
3+
//! This module centralizes error handling for all proc-macro operations,
4+
//! ensuring consistent span-based error reporting at compile time.
5+
//!
6+
//! # Overview
7+
//!
8+
//! All proc-macro operations should return [`MacroResult<T>`] instead of panicking,
9+
//! allowing the Rust compiler to display user-friendly error messages with proper source locations.
10+
//!
11+
//! # Key Functions
12+
//!
13+
//! - [`err_call_site`] - Create an error at the macro call site
14+
//! - [`err_spanned`] - Create an error at a specific AST node location
15+
//! - [`IntoSynError`] - Convert other error types to syn::Error
16+
//!
17+
//! # Example
18+
//!
19+
//! ```ignore
20+
//! fn process_something(input: TokenStream) -> MacroResult<TokenStream> {
21+
//! let data = syn::parse2(input)?;
22+
//! // ... validation ...
23+
//! if invalid {
24+
//! return Err(err_call_site("invalid input format"));
25+
//! }
26+
//! Ok(quote! { /* ... */ })
27+
//! }
28+
//! ```
29+
30+
use proc_macro2::Span;
31+
use syn::Error;
32+
33+
/// Result type for all macro operations.
34+
pub type MacroResult<T> = Result<T, Error>;
35+
36+
/// Create an error at the call site.
37+
#[inline]
38+
pub fn err_call_site<M: std::fmt::Display>(message: M) -> Error {
39+
Error::new(Span::call_site(), message)
40+
}
41+
42+
// The following helpers are provided for future use when we need
43+
// span-based errors or error conversion from other types.
44+
45+
/// Create an error at the given span.
46+
#[allow(dead_code)]
47+
#[inline]
48+
pub fn err_spanned<T: quote::ToTokens, M: std::fmt::Display>(tokens: T, message: M) -> Error {
49+
Error::new_spanned(tokens, message)
50+
}
51+
52+
/// Trait for converting other error types to syn::Error.
53+
#[allow(dead_code)]
54+
pub trait IntoSynError: Sized {
55+
fn into_syn_error(self, span: Span) -> Error;
56+
fn into_syn_error_call_site(self) -> Error {
57+
self.into_syn_error(Span::call_site())
58+
}
59+
}
60+
61+
impl IntoSynError for std::io::Error {
62+
fn into_syn_error(self, span: Span) -> Error {
63+
Error::new(span, self.to_string())
64+
}
65+
}
66+
67+
impl IntoSynError for String {
68+
fn into_syn_error(self, span: Span) -> Error {
69+
Error::new(span, self)
70+
}
71+
}
72+
73+
impl IntoSynError for &str {
74+
fn into_syn_error(self, span: Span) -> Error {
75+
Error::new(span, self)
76+
}
77+
}
78+
79+
impl IntoSynError for serde_json::Error {
80+
fn into_syn_error(self, span: Span) -> Error {
81+
Error::new(span, self.to_string())
82+
}
83+
}
84+
85+
/// Extension trait for Result to convert errors with spans.
86+
#[allow(dead_code)]
87+
pub trait ResultExt<T, E> {
88+
fn map_syn_err(self, span: Span) -> MacroResult<T>;
89+
fn map_syn_err_call_site(self) -> MacroResult<T>;
90+
}
91+
92+
impl<T, E: IntoSynError> ResultExt<T, E> for Result<T, E> {
93+
fn map_syn_err(self, span: Span) -> MacroResult<T> {
94+
self.map_err(|e| e.into_syn_error(span))
95+
}
96+
fn map_syn_err_call_site(self) -> MacroResult<T> {
97+
self.map_err(|e| e.into_syn_error_call_site())
98+
}
99+
}
100+
101+
/// Extension trait for Option to convert to syn::Error.
102+
#[allow(dead_code)]
103+
pub trait OptionExt<T> {
104+
fn ok_or_syn_err<M: std::fmt::Display>(self, span: Span, message: M) -> MacroResult<T>;
105+
fn ok_or_syn_err_call_site<M: std::fmt::Display>(self, message: M) -> MacroResult<T>;
106+
}
107+
108+
impl<T> OptionExt<T> for Option<T> {
109+
fn ok_or_syn_err<M: std::fmt::Display>(self, span: Span, message: M) -> MacroResult<T> {
110+
self.ok_or_else(|| Error::new(span, message))
111+
}
112+
fn ok_or_syn_err_call_site<M: std::fmt::Display>(self, message: M) -> MacroResult<T> {
113+
self.ok_or_else(|| err_call_site(message))
114+
}
115+
}

crates/vespera_macro/src/file_utils.rs

Lines changed: 45 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,46 @@
1-
use anyhow::{Context, Result};
2-
use std::path::{Path, PathBuf};
1+
use std::{
2+
io,
3+
path::{Path, PathBuf},
4+
};
35

4-
pub fn collect_files(folder_path: &Path) -> Result<Vec<PathBuf>> {
6+
/// Read and parse a Rust source file, returning None on error (silent).
7+
pub fn try_read_and_parse_file(path: &Path) -> Option<syn::File> {
8+
let content = std::fs::read_to_string(path).ok()?;
9+
syn::parse_file(&content).ok()
10+
}
11+
12+
/// Read and parse a Rust source file, printing warnings on error.
13+
pub fn read_and_parse_file_warn(path: &Path, context: &str) -> Option<syn::File> {
14+
let content = match std::fs::read_to_string(path) {
15+
Ok(c) => c,
16+
Err(e) => {
17+
eprintln!(
18+
"Warning: {}: Cannot read '{}': {}",
19+
context,
20+
path.display(),
21+
e
22+
);
23+
return None;
24+
}
25+
};
26+
match syn::parse_file(&content) {
27+
Ok(ast) => Some(ast),
28+
Err(e) => {
29+
eprintln!(
30+
"Warning: {}: Parse error in '{}': {}",
31+
context,
32+
path.display(),
33+
e
34+
);
35+
None
36+
}
37+
}
38+
}
39+
40+
pub fn collect_files(folder_path: &Path) -> io::Result<Vec<PathBuf>> {
541
let mut files = Vec::new();
6-
for entry in std::fs::read_dir(folder_path)
7-
.with_context(|| format!("Failed to read directory: {}", folder_path.display()))?
8-
{
9-
let entry = entry.with_context(|| "Failed to read directory entry")?;
42+
for entry in std::fs::read_dir(folder_path)? {
43+
let entry = entry?;
1044
let path = entry.path();
1145
if path.is_file() {
1246
files.push(folder_path.join(path));
@@ -39,12 +73,13 @@ pub fn file_to_segments(file: &Path, base_path: &Path) -> Vec<String> {
3973

4074
#[cfg(test)]
4175
mod tests {
42-
use super::*;
76+
use std::{fs, path::PathBuf};
77+
4378
use rstest::rstest;
44-
use std::fs;
45-
use std::path::PathBuf;
4679
use tempfile::TempDir;
4780

81+
use super::*;
82+
4883
#[rstest]
4984
// Simple file paths
5085
#[case("routes/users.rs", "routes", vec!["users"])]

0 commit comments

Comments
 (0)