Skip to content

Commit 9c4e754

Browse files
committed
refactor(cli,core,web): unify derive_more error hierarchy, enforce strong IR typing, and harden CST/security abstractions
- Consolidate workspace error handling around cdd_core::error::AppError with derive_more::{Display, Error, From}, adding structured domain variants (MissingArgument, ToolNotFound, InvalidSpecVersion, DuplicateIdentifier, UnresolvedReference, AstMutationError, ConfigError, NotImplemented, NotFound, SyncError, Unauthorized, ValidationError) and wrapping AppError in a zero-duplication Actix ResponseError newtype in cdd-web. - Eliminate all production .expect() calls across OpenAPI emission, RFC 3986 URI/reference resolution, schema resolution, and emitted Actix/Axum/Reqwest/MCP templates; emit typed WorkflowError and AppError results in generated Arazzo and MCP targets. - Replace regex-based #[serde(...)] / #[oai(...)] attribute parsing and path template extraction with deterministic ra_ap_syntax token-tree and slice scanners, and upgrade route/test patching to inspect CST ast::Path and ast::Module nodes. - Wire strong domain types (RustType, HttpMethod, StatusCodeSpec, MediaType, OperationId, RustIdent, ExtensionKey) across ParsedRoute, RouteParam, RequestBodyDefinition, ResponseHeader, ParsedField, and ParsedVariant, replacing fragile substring heuristics with RustType pattern matching. - Remove clippy #[allow(...)] suppressions by introducing RouteBuildContext, CallbackResolutionContext, OperationParseContext, and RefResolutionScope parameter context structs and boxing large CLI subcommand payloads. - Replace length-only token signatures and stub handlers in cdd-web with 256-bit keyed MAC token signing, UTC expiration validation, salted password hashing, InMemoryUserDao consolidation, and typed Pet/Store/User request extractors while maintaining 100.00% doc and test coverage.
1 parent 3de9151 commit 9c4e754

47 files changed

Lines changed: 2658 additions & 1312 deletions

Some content is hidden

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

‎cli/src/from_arazzo.rs‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,7 @@ edition = "2021"
204204
205205
[dependencies]
206206
clap = { version = "4", features = ["derive"] }
207+
derive_more = { version = "2.1", default-features = false, features = ["std", "from", "display", "error"] }
207208
regex = "1"
208209
reqwest = { version = "0.12", features = ["json"] }
209210
serde = { version = "1.0", features = ["derive"] }

‎cli/src/main.rs‎

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,6 @@ pub struct Cli {
6363

6464
/// All available commands in the CLI.
6565
#[derive(Subcommand, Debug)]
66-
#[allow(clippy::large_enum_variant)]
6766
pub enum Commands {
6867
/// Synchronize an OpenAPI specification with source code.
6968
Sync(cdd_cli::sync::SyncArgs),
@@ -72,13 +71,13 @@ pub enum Commands {
7271
ToDocsJson(cdd_cli::to_docs_json::ToDocsJsonArgs),
7372
/// Generate code from an OpenAPI specification.
7473
#[clap(name = "from_openapi")]
75-
FromOpenApi(cdd_cli::from_openapi::FromOpenApiArgs),
74+
FromOpenApi(Box<cdd_cli::from_openapi::FromOpenApiArgs>),
7675
/// Generate an OpenAPI specification from source code.
7776
#[clap(name = "to_openapi")]
7877
ToOpenApi(cdd_cli::to_openapi::ToOpenApiArgs),
7978
/// Generate code (SDK, CLI, or Server) from an Arazzo 1.1.0 workflow specification.
8079
#[clap(name = "from_arazzo")]
81-
FromArazzo(cdd_cli::from_arazzo::FromArazzoArgs),
80+
FromArazzo(Box<cdd_cli::from_arazzo::FromArazzoArgs>),
8281
/// Extract an Arazzo 1.1.0 workflow specification from Rust source code.
8382
#[clap(name = "to_arazzo")]
8483
ToArazzo(cdd_cli::to_arazzo::ToArazzoArgs),
@@ -100,7 +99,7 @@ pub enum Commands {
10099
TestGen(cdd_cli::test_gen::TestGenArgs),
101100
/// Generate a JSON Schema from a Rust struct or enum.
102101
#[clap(name = "schema-gen")]
103-
SchemaGen(cdd_cli::schema_gen::SchemaGenArgs),
102+
SchemaGen(Box<cdd_cli::schema_gen::SchemaGenArgs>),
104103
}
105104

106105
/// Dispatches a parsed [`Cli`] command to its underlying implementation.

‎cli/src/mcp.rs‎

Lines changed: 18 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -313,7 +313,7 @@ pub fn dispatch_tool_command(
313313
let input_str = args
314314
.get("input")
315315
.and_then(Value::as_str)
316-
.ok_or_else(|| AppError::General("Missing required argument: 'input'".into()))?;
316+
.ok_or(AppError::MissingArgument { argument: "input" })?;
317317
let output_str = args
318318
.get("output")
319319
.and_then(Value::as_str)
@@ -336,7 +336,7 @@ pub fn dispatch_tool_command(
336336
let input_str = args
337337
.get("input")
338338
.and_then(Value::as_str)
339-
.ok_or_else(|| AppError::General("Missing required argument: 'input'".into()))?;
339+
.ok_or(AppError::MissingArgument { argument: "input" })?;
340340
let no_imports = args
341341
.get("no_imports")
342342
.and_then(Value::as_bool)
@@ -377,7 +377,7 @@ pub fn dispatch_tool_command(
377377
.get("dir")
378378
.or_else(|| args.get("input"))
379379
.and_then(Value::as_str)
380-
.ok_or_else(|| AppError::General("Missing required argument: 'dir'".into()))?;
380+
.ok_or(AppError::MissingArgument { argument: "dir" })?;
381381
let truth_str = args.get("truth").and_then(Value::as_str).unwrap_or("code");
382382
let output_str = args
383383
.get("output")
@@ -446,7 +446,7 @@ pub fn dispatch_tool_command(
446446
let input_str = args
447447
.get("input")
448448
.and_then(Value::as_str)
449-
.ok_or_else(|| AppError::General("Missing required argument: 'input'".into()))?;
449+
.ok_or(AppError::MissingArgument { argument: "input" })?;
450450
let output_str = args
451451
.get("output")
452452
.and_then(Value::as_str)
@@ -473,7 +473,9 @@ pub fn dispatch_tool_command(
473473
output_str
474474
))
475475
}
476-
_ => Err(AppError::General(format!("Tool not found: {}", tool_name))),
476+
_ => Err(AppError::ToolNotFound {
477+
tool: tool_name.to_string(),
478+
}),
477479
}
478480
}
479481

@@ -583,23 +585,18 @@ pub fn handle_request(input: &str) -> Option<String> {
583585
};
584586
ok_response(req.id, &result)
585587
}
588+
Err(e @ AppError::ToolNotFound { .. }) => {
589+
err_response(req.id, -32601, e.to_string())
590+
}
586591
Err(e) => {
587-
if call_params.name != "from_openapi"
588-
&& call_params.name != "to_openapi"
589-
&& call_params.name != "to_docs_json"
590-
&& call_params.name != "sync"
591-
{
592-
err_response(req.id, -32601, e.to_string())
593-
} else {
594-
let result = CallToolResult {
595-
content: vec![CallToolResultContent::Text(TextContent {
596-
text: format!("Error executing {}: {}", call_params.name, e),
597-
})],
598-
is_error: Some(true),
599-
meta: None,
600-
};
601-
ok_response(req.id, &result)
602-
}
592+
let result = CallToolResult {
593+
content: vec![CallToolResultContent::Text(TextContent {
594+
text: format!("Error executing {}: {}", call_params.name, e),
595+
})],
596+
is_error: Some(true),
597+
meta: None,
598+
};
599+
ok_response(req.id, &result)
603600
}
604601
}
605602
}

‎cli/src/serve_json_rpc.rs‎

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use crate::mcp::dispatch_tool_command;
1111
use actix_web::{web, HttpResponse, Responder};
1212
#[cfg(not(coverage))]
1313
use actix_web::{App, HttpServer};
14-
use cdd_core::error::AppResult;
14+
use cdd_core::error::{AppError, AppResult};
1515
use clap::Args;
1616
use serde::{Deserialize, Serialize};
1717
use serde_json::{json, Value};
@@ -102,7 +102,7 @@ async fn handle_rpc(body: String) -> impl Responder {
102102
id,
103103
},
104104
Err(e) => {
105-
let code = if e.to_string().contains("Missing required argument") {
105+
let code = if matches!(e, AppError::MissingArgument { .. }) {
106106
-32602
107107
} else {
108108
-32000
@@ -133,7 +133,7 @@ async fn handle_rpc(body: String) -> impl Responder {
133133
}
134134
}
135135
Err(e) => {
136-
let code = if e.to_string().contains("Missing required argument") {
136+
let code = if matches!(e, AppError::MissingArgument { .. }) {
137137
-32602
138138
} else {
139139
-32000
@@ -249,8 +249,11 @@ pub struct ServeJsonRpcConfig {
249249
pub listen: String,
250250
}
251251

252-
#[allow(dead_code)]
253-
fn configure_rpc_app(cfg: &mut web::ServiceConfig) {
252+
/// Configures the Actix Web service routes for the JSON-RPC 2.0 endpoint.
253+
///
254+
/// # Arguments
255+
/// * `cfg` - Mutable Actix [`web::ServiceConfig`] to register routes on.
256+
pub fn configure_rpc_app(cfg: &mut web::ServiceConfig) {
254257
cfg.route("/", web::post().to(handle_rpc));
255258
}
256259

‎cli/src/sync.rs‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -64,11 +64,11 @@ pub struct SyncArgs {
6464
pub force_type: Vec<(String, String)>,
6565
}
6666

67-
/// Helper to parse "key=value" arguments.
68-
fn parse_key_val(s: &str) -> Result<(String, String), String> {
69-
let pos = s
70-
.find('=')
71-
.ok_or_else(|| format!("invalid KEY=value: no `=` found in `{}`", s))?;
67+
/// Helper to parse `"key=value"` arguments.
68+
fn parse_key_val(s: &str) -> AppResult<(String, String)> {
69+
let pos = s.find('=').ok_or_else(|| {
70+
AppError::ValidationError(format!("invalid KEY=value: no `=` found in `{}`", s))
71+
})?;
7272
Ok((s[..pos].to_string(), s[pos + 1..].to_string()))
7373
}
7474

‎core/src/classes/emit.rs‎

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,13 @@ pub fn make_record_field(
4141
}
4242

4343
let file = parse.tree();
44-
Ok(file
45-
.syntax()
44+
file.syntax()
4645
.descendants()
4746
.find_map(ast::RecordField::cast)
48-
.expect("valid wrapper struct contains record field"))
47+
.ok_or_else(|| AppError::AstMutationError {
48+
target: name.to_string(),
49+
reason: format!("Failed to construct AST RecordField from '{raw_field}'"),
50+
})
4951
}
5052

5153
/// Generates a complete Rust source string for multiple Models (Structs or Enums).
@@ -687,5 +689,7 @@ mod tests {
687689
assert!(multi_code.contains("#[serde(rename = \"CustomEnum\")]"));
688690
assert!(multi_code.contains("/// Unit variant doc"));
689691
assert!(multi_code.contains("UnitVar,"));
692+
693+
assert!(make_record_field("//", "", false, 0).is_err());
690694
}
691695
}

0 commit comments

Comments
 (0)