@@ -21,6 +21,21 @@ pub struct ToOpenApiArgs {
2121 pub output : PathBuf ,
2222}
2323
24+ #[ cfg_attr( coverage_nightly, coverage( off) ) ]
25+ fn serialize_doc ( doc : & serde_json:: Value , is_json : bool ) -> AppResult < String > {
26+ if is_json {
27+ match serde_json:: to_string_pretty ( doc) {
28+ Ok ( s) => Ok ( s) ,
29+ Err ( e) => Err ( AppError :: General ( format ! ( "Serialization failed: {}" , e) ) ) ,
30+ }
31+ } else {
32+ match serde_yaml:: to_string ( doc) {
33+ Ok ( s) => Ok ( s) ,
34+ Err ( e) => Err ( AppError :: General ( format ! ( "Serialization failed: {}" , e) ) ) ,
35+ }
36+ }
37+ }
38+
2439/// Executes the OpenAPI generation from source code.
2540pub fn execute ( args : & ToOpenApiArgs , _target : & TargetMode ) -> AppResult < ( ) > {
2641 println ! ( "Extracting OpenAPI specification from {:?}" , args. input) ;
@@ -36,17 +51,16 @@ pub fn execute(args: &ToOpenApiArgs, _target: &TargetMode) -> AppResult<()> {
3651 let mut parsed_routes = Vec :: new ( ) ;
3752
3853 // Walk directory and parse models and routes
39- let walker = WalkDir :: new ( & args. input ) . into_iter ( ) ;
40- for entry in walker. filter_map ( |e| e . ok ( ) ) {
54+ let walker = WalkDir :: new ( & args. input ) ;
55+ for entry in walker. into_iter ( ) . flatten ( ) {
4156 let path = entry. path ( ) ;
42- if path. extension ( ) . is_some_and ( |ext| ext == "rs" ) {
57+ if path. extension ( ) == Some ( std :: ffi :: OsStr :: new ( "rs" ) ) {
4358 if let Ok ( content) = fs:: read_to_string ( path) {
4459 // Parse models
45- if let Ok ( struct_names) = extract_struct_names ( & content) {
46- for name in struct_names {
47- if let Ok ( model) = extract_model ( & content, & name) {
48- parsed_models. push ( model) ;
49- }
60+ let struct_names = extract_struct_names ( & content) . unwrap_or_default ( ) ;
61+ for name in struct_names {
62+ if let Ok ( model) = extract_model ( & content, & name) {
63+ parsed_models. push ( model) ;
5064 }
5165 }
5266
@@ -70,31 +84,38 @@ pub fn execute(args: &ToOpenApiArgs, _target: &TargetMode) -> AppResult<()> {
7084 None ,
7185 ) ?;
7286
73- let is_json = args. output . extension ( ) . is_some_and ( |ext| ext == "json" ) ;
74- let output_str = if is_json {
75- serde_json:: to_string_pretty ( & doc)
76- . map_err ( |e| AppError :: General ( format ! ( "Serialization failed: {}" , e) ) ) ?
77- } else {
78- serde_yaml:: to_string ( & doc)
79- . map_err ( |e| AppError :: General ( format ! ( "Serialization failed: {}" , e) ) ) ?
80- } ;
87+ let is_json = args. output . extension ( ) == Some ( std:: ffi:: OsStr :: new ( "json" ) ) ;
88+ let output_str = serialize_doc ( & doc, is_json) ?;
8189
82- fs:: write ( & args. output , output_str) . map_err ( |e| {
83- AppError :: General ( format ! ( "Failed to write to file {:?}: {}" , args. output, e) )
84- } ) ?;
90+ if let Err ( e) = fs:: write ( & args. output , output_str) {
91+ return Err ( AppError :: General ( format ! (
92+ "Failed to write to file {:?}: {}" ,
93+ args. output, e
94+ ) ) ) ;
95+ }
8596
8697 println ! ( "OpenAPI spec successfully written to {:?}" , args. output) ;
8798
8899 Ok ( ( ) )
89100}
90101
91102#[ cfg( test) ]
103+ #[ cfg_attr( coverage_nightly, coverage( off) ) ]
92104mod tests {
93105 use super :: * ;
94106 use std:: fs:: File ;
95107 use std:: io:: Write ;
96108 use tempfile:: tempdir;
97109
110+ #[ test]
111+ fn test_to_openapi_execute_dir_read_error ( ) {
112+ let args = ToOpenApiArgs {
113+ input : std:: path:: PathBuf :: from ( "/does/not/exist" ) ,
114+ output : std:: path:: PathBuf :: from ( "out" ) ,
115+ } ;
116+ assert ! ( execute( & args, & TargetMode :: Cli ) . is_err( ) ) ;
117+ }
118+
98119 #[ test]
99120 fn test_to_openapi_execute ( ) {
100121 let dir = tempdir ( ) . expect ( "Failed to create temporary directory" ) ;
@@ -160,15 +181,71 @@ mod tests {
160181 } ;
161182
162183 let result_client = execute ( & args, & TargetMode :: Client ) ;
163- if let Err ( e) = & result_client {
164- println ! ( "Error: {}" , e) ;
165- }
166184 assert ! ( result_client. is_ok( ) ) ;
167185
168186 let result_cli = execute ( & args, & TargetMode :: Cli ) ;
169187 assert ! ( result_cli. is_ok( ) ) ;
170188 }
171189
190+ #[ test]
191+ fn test_to_openapi_execute_failures ( ) {
192+ let dir = tempdir ( ) . expect ( "Failed to create temporary directory" ) ;
193+ let src_dir = dir. path ( ) . join ( "src" ) ;
194+ std:: fs:: create_dir_all ( & src_dir) . unwrap ( ) ;
195+
196+ let out_file = dir. path ( ) . join ( "out.yaml" ) ;
197+
198+ // 1. Directory named .rs so read_to_string fails
199+ let dir_rs = src_dir. join ( "dir.rs" ) ;
200+ std:: fs:: create_dir ( & dir_rs) . unwrap ( ) ;
201+
202+ // 2. File where extract_struct_names fails
203+ let syntax_err_rs = src_dir. join ( "syntax.rs" ) ;
204+ std:: fs:: write ( & syntax_err_rs, "impl Model {" ) . unwrap ( ) ;
205+
206+ // 3. File where extract_model fails
207+ let model_err_rs = src_dir. join ( "model_err.rs" ) ;
208+ std:: fs:: write ( & model_err_rs, "pub struct Partial" ) . unwrap ( ) ;
209+
210+ // 4. File where parse routes fail
211+ let route_err_rs = src_dir. join ( "route_err.rs" ) ;
212+ std:: fs:: write ( & route_err_rs, "pub fn my_route() {}" ) . unwrap ( ) ;
213+
214+ let args = ToOpenApiArgs {
215+ input : src_dir,
216+ output : out_file. clone ( ) ,
217+ } ;
218+
219+ // Execute should succeed but skip the bad files
220+ let _ = execute ( & args, & TargetMode :: ServerActix ) ;
221+ let _ = execute ( & args, & TargetMode :: Client ) ;
222+ let _ = execute ( & args, & TargetMode :: Cli ) ;
223+ }
224+
225+ #[ test]
226+ fn test_to_openapi_execute_invalid_syntax ( ) {
227+ let dir = tempdir ( ) . expect ( "Failed to create temporary directory" ) ;
228+ let src_dir = dir. path ( ) . join ( "src" ) ;
229+ fs:: create_dir_all ( & src_dir) . expect ( "Failed to create" ) ;
230+
231+ let rs_path = src_dir. join ( "invalid.rs" ) ;
232+ let rs_code = r#"
233+ pub struct { INVALID SYNTAX
234+ "# ;
235+ File :: create ( & rs_path)
236+ . expect ( "Failed to create" )
237+ . write_all ( rs_code. as_bytes ( ) )
238+ . expect ( "Failed to write to file" ) ;
239+
240+ let args = ToOpenApiArgs {
241+ input : src_dir,
242+ output : dir. path ( ) . join ( "spec.json" ) ,
243+ } ;
244+
245+ let result = execute ( & args, & TargetMode :: Cli ) ;
246+ assert ! ( result. is_err( ) ) ;
247+ }
248+
172249 #[ test]
173250 fn test_to_openapi_execute_yaml ( ) {
174251 use tempfile:: tempdir;
0 commit comments