@@ -168,42 +168,54 @@ fn run_generation(
168168 }
169169
170170 let out_dir = args. get_output_dir ( ) ;
171- fs:: create_dir_all ( & out_dir) . unwrap_or_default ( ) ;
171+ fs:: create_dir_all ( & out_dir) . map_err ( |e| AppError :: General ( e . to_string ( ) ) ) ? ;
172172
173173 if !args. no_installable_package {
174174 let cargo_toml_path = out_dir. join ( "Cargo.toml" ) ;
175175 if !cargo_toml_path. exists ( ) {
176+ let mut dependencies = String :: from (
177+ r#"tokio = { version = "1", features = ["full"] }
178+ serde = { version = "1.0", features = ["derive"] }
179+ serde_json = "1.0"
180+ serde_qs = "1.0"
181+ reqwest = { version = "0.11", features = ["json"] }
182+ actix-web = "4.0"
183+ clap = { version = "4.0", features = ["derive"] }
184+ chrono = { version = "0.4", features = ["serde"] }
185+ utoipa = "4.2"
186+ uuid = { version = "1.8", features = ["serde", "v4"] }
187+ "# ,
188+ ) ;
189+
190+ if strategy. is_server ( ) {
191+ dependencies. push_str ( "actix-multipart = \" 0.7.2\" \n " ) ;
192+ dependencies. push_str ( "diesel = { version = \" 2.1\" , features = [\" postgres\" , \" sqlite\" , \" r2d2\" , \" chrono\" , \" uuid\" ] }\n " ) ;
193+ dependencies. push_str ( "fake = \" 2.9\" \n " ) ;
194+ dependencies. push_str ( "actix-cors = \" 0.7.1\" \n " ) ;
195+ }
196+
176197 let cargo_toml = format ! (
177198 r#"[package]
178199name = "{}"
179200version = "0.0.1"
180201edition = "2021"
181202
182203[dependencies]
183- tokio = {{ version = "1", features = ["full"] }}
184- serde = {{ version = "1.0", features = ["derive"] }}
185- serde_json = "1.0"
186- serde_qs = "1.0"
187- reqwest = {{ version = "0.11", features = ["json"] }}
188- actix-web = "4.0"
189- diesel = {{ version = "2.0", features = ["postgres"] }}
190- clap = {{ version = "4.0", features = ["derive"] }}
191- chrono = {{ version = "0.4", features = ["serde"] }}
192- utoipa = "4.2"
193- uuid = {{ version = "1.8", features = ["serde"] }}
204+ {}
194205"# ,
195206 out_dir
196207 . file_name( )
197208 . unwrap_or_else( || std:: ffi:: OsStr :: new( "generated_package" ) )
198- . to_string_lossy( )
209+ . to_string_lossy( ) ,
210+ dependencies
199211 ) ;
200- fs:: write ( cargo_toml_path, cargo_toml) . unwrap_or_default ( ) ;
212+ fs:: write ( cargo_toml_path, cargo_toml) . map_err ( |e| AppError :: General ( e . to_string ( ) ) ) ? ;
201213 }
202214 }
203215
204216 if !args. no_github_actions {
205217 let gh_workflows_dir = out_dir. join ( ".github" ) . join ( "workflows" ) ;
206- fs:: create_dir_all ( & gh_workflows_dir) . unwrap_or_default ( ) ;
218+ fs:: create_dir_all ( & gh_workflows_dir) . map_err ( |e| AppError :: General ( e . to_string ( ) ) ) ? ;
207219 let ci_yml_path = gh_workflows_dir. join ( "ci.yml" ) ;
208220 if !ci_yml_path. exists ( ) {
209221 let ci_yml = r#"name: CI
@@ -223,7 +235,7 @@ jobs:
223235 - name: Run tests
224236 run: cargo test --verbose
225237"# ;
226- fs:: write ( ci_yml_path, ci_yml) . unwrap_or_default ( ) ;
238+ fs:: write ( ci_yml_path, ci_yml) . map_err ( |e| AppError :: General ( e . to_string ( ) ) ) ? ;
227239 }
228240 }
229241
@@ -233,29 +245,72 @@ jobs:
233245
234246 // 1. Generate Models (DTOs)
235247 let src_dir = out_dir. join ( "src" ) ;
236- fs:: create_dir_all ( & src_dir) . unwrap_or_default ( ) ;
248+ fs:: create_dir_all ( & src_dir) . map_err ( |e| AppError :: General ( e . to_string ( ) ) ) ? ;
237249 let mut lib_rs_content = String :: new ( ) ;
238250
239251 let models_dir = src_dir. join ( "models" ) ;
240- fs:: create_dir_all ( & models_dir) . unwrap_or_default ( ) ;
252+ fs:: create_dir_all ( & models_dir) . map_err ( |e| AppError :: General ( e . to_string ( ) ) ) ? ;
241253
242254 let models = cdd_core:: openapi:: parse:: parse_openapi_spec ( & yaml_content) ?;
243255 if !models. is_empty ( ) {
244256 let rust_code = cdd_core:: classes:: emit:: generate_dtos ( & models) ;
245257 let file_path = models_dir. join ( "generated.rs" ) ;
246- fs:: write ( & file_path, rust_code) . unwrap_or_default ( ) ;
258+ fs:: write ( & file_path, rust_code) . map_err ( |e| AppError :: General ( e . to_string ( ) ) ) ? ;
247259 let mod_rs = "pub mod generated;\n pub use generated::*;\n " ;
248- fs:: write ( models_dir. join ( "mod.rs" ) , mod_rs) . unwrap_or_default ( ) ;
260+ fs:: write ( models_dir. join ( "mod.rs" ) , mod_rs)
261+ . map_err ( |e| AppError :: General ( e. to_string ( ) ) ) ?;
249262 lib_rs_content. push_str ( "pub mod models;\n " ) ;
250263 }
251264
252265 // 2. Scaffold Handlers
253266 let handlers_dir = src_dir. join ( "handlers" ) ;
254- fs:: create_dir_all ( & handlers_dir) . unwrap_or_default ( ) ;
267+ fs:: create_dir_all ( & handlers_dir) . map_err ( |e| AppError :: General ( e. to_string ( ) ) ) ?;
268+ lib_rs_content. push_str ( "pub mod handlers;\n " ) ;
269+
270+ if strategy. is_server ( ) {
271+ lib_rs_content. push_str ( "pub mod security;\n \n " ) ;
272+ lib_rs_content. push_str ( "use actix_web::web;\n \n " ) ;
273+ lib_rs_content. push_str ( "pub fn config(cfg: &mut web::ServiceConfig) {\n }\n " ) ;
274+
275+ let security_mod_content = r#"// Security module stub
276+ use std::marker::PhantomData;
277+
278+ #[derive(Clone, Default)]
279+ pub struct ApiKey;
280+
281+ #[derive(Clone, Default)]
282+ pub struct OAuth2<T>(pub PhantomData<T>);
283+
284+ #[derive(Clone, Default)]
285+ pub struct Oidc;
286+
287+ #[derive(Clone, Default)]
288+ pub struct PetstoreAuth<T>(pub PhantomData<T>);
289+
290+ pub mod scopes {
291+ #[derive(Clone)]
292+ pub struct WritePets;
293+ #[derive(Clone)]
294+ pub struct ReadPets;
295+ }
296+ "# ;
297+ fs:: write ( src_dir. join ( "security.rs" ) , security_mod_content)
298+ . map_err ( |e| AppError :: General ( e. to_string ( ) ) ) ?;
299+ }
300+
301+ fs:: write ( src_dir. join ( "lib.rs" ) , & lib_rs_content)
302+ . map_err ( |e| AppError :: General ( e. to_string ( ) ) ) ?;
303+
304+ let route_config_path = if strategy. is_server ( ) {
305+ Some ( src_dir. join ( "lib.rs" ) )
306+ } else {
307+ None
308+ } ;
309+
255310 let scaffold_args = ScaffoldArgs {
256311 openapi_path : input. clone ( ) ,
257312 output_dir : handlers_dir. clone ( ) ,
258- route_config_path : None ,
313+ route_config_path,
259314 force : false ,
260315 } ;
261316 crate :: scaffold:: execute ( & scaffold_args, strategy) ?;
@@ -267,29 +322,26 @@ jobs:
267322 for entry in entries. flatten ( ) {
268323 let path = entry. path ( ) ;
269324 if path. is_file ( )
270- && path. extension ( ) . unwrap_or_default ( ) == "rs"
271- && path. file_name ( ) . unwrap_or_default ( ) != "mod.rs"
325+ && path. extension ( ) . is_some_and ( |ext| ext == "rs" )
326+ && path. file_name ( ) . is_some_and ( |name| name != "mod.rs" )
272327 {
273- let mod_name = path. file_stem ( ) . unwrap_or_default ( ) . to_string_lossy ( ) ;
328+ let mod_name = path
329+ . file_stem ( )
330+ . ok_or_else ( || AppError :: General ( "Missing file stem" . to_string ( ) ) ) ?
331+ . to_string_lossy ( ) ;
274332 mods. push ( format ! ( "pub mod {};\n " , mod_name) ) ;
275333 }
276334 }
277335 }
278- if !mods. is_empty ( ) {
279- mods. sort ( ) ;
280- fs:: write ( handlers_dir. join ( "mod.rs" ) , mods. join ( "" ) ) . unwrap_or_default ( ) ;
281- lib_rs_content. push_str ( "pub mod handlers;\n " ) ;
282- }
283- }
284-
285- if !lib_rs_content. is_empty ( ) {
286- fs:: write ( src_dir. join ( "lib.rs" ) , lib_rs_content) . unwrap_or_default ( ) ;
336+ mods. sort ( ) ;
337+ fs:: write ( handlers_dir. join ( "mod.rs" ) , mods. join ( "" ) )
338+ . map_err ( |e| AppError :: General ( e. to_string ( ) ) ) ?;
287339 }
288340
289341 // 3. Generate Tests
290342 if args. tests {
291343 let tests_dir = out_dir. join ( "tests" ) ;
292- fs:: create_dir_all ( & tests_dir) . unwrap_or_default ( ) ;
344+ fs:: create_dir_all ( & tests_dir) . map_err ( |e| AppError :: General ( e . to_string ( ) ) ) ? ;
293345 let crate_name = out_dir
294346 . file_name ( )
295347 . unwrap_or_default ( )
@@ -427,6 +479,20 @@ components:
427479"# ;
428480 fs:: write ( & input_file, openapi_content) . expect ( "Failed to write to file" ) ;
429481
482+ // Test SdkCli with mcp
483+ let args_mcp = FromOpenApiArgs {
484+ command : FromOpenApiCommands :: SdkCli ( GenerateArgs {
485+ input : Some ( input_file. clone ( ) ) ,
486+ input_dir : None ,
487+ output_dir : Some ( dir. path ( ) . join ( "out1_mcp" ) ) ,
488+ no_github_actions : false ,
489+ no_installable_package : false ,
490+ tests : false ,
491+ mcp : true ,
492+ } ) ,
493+ } ;
494+ assert ! ( execute( & args_mcp) . is_ok( ) ) ;
495+
430496 // Test SdkCli
431497 let args = FromOpenApiArgs {
432498 command : FromOpenApiCommands :: SdkCli ( GenerateArgs {
@@ -554,6 +620,23 @@ components:
554620 let _ = run_generation ( & args, & cdd_core:: strategies:: ActixStrategy ) ;
555621 Ok ( ( ) )
556622 }
623+ #[ test]
624+ fn test_generate_from_openapi_to_sdk_cli_mcp ( ) {
625+ let dir = tempdir ( ) . expect ( "Failed to create temporary directory" ) ;
626+ let input_file = dir. path ( ) . join ( "spec.yaml" ) ;
627+ let openapi_content = "openapi: 3.0.0\n info:\n title: Test API\n version: 1.0.0\n paths:\n /test:\n get:\n operationId: test_op\n responses:\n \" 200\" :\n description: OK" ;
628+ std:: fs:: write ( & input_file, openapi_content) . expect ( "Failed to write to file" ) ;
629+ let config = FromOpenApiConfig {
630+ subcommand : "to_sdk_cli" . to_string ( ) ,
631+ input : Some ( input_file) ,
632+ output_dir : Some ( dir. path ( ) . join ( "out_cli_mcp" ) ) ,
633+ mcp : true ,
634+ ..Default :: default ( )
635+ } ;
636+ let result = generate_from_openapi ( & config) ;
637+ assert ! ( result. is_ok( ) ) ;
638+ }
639+
557640 #[ test]
558641 fn test_generate_from_openapi_unknown_subcommand ( ) {
559642 let config = FromOpenApiConfig {
0 commit comments