Skip to main content

mockforge_openapi/
lib.rs

1//! # MockForge OpenAPI
2//!
3//! OpenAPI 3.x / Swagger 2.0 specification loading, parsing, schema validation,
4//! and response selection primitives for MockForge.
5//!
6//! This crate owns the [`OpenApiSpec`] domain model used throughout MockForge to
7//! drive mock responses, routing, validation, and contract analysis. It was
8//! extracted from `mockforge-core` so that downstream crates (contract drift,
9//! intelligence, recorders, etc.) can depend on OpenAPI types without pulling in
10//! the entirety of core.
11//!
12//! ## Modules
13//!
14//! - [`spec`] — [`OpenApiSpec`] loader (file / string / JSON / YAML), schema
15//!   resolution, operation iteration. Handles transparent Swagger 2.0 → OpenAPI
16//!   3.0 conversion via [`swagger_convert`].
17//! - [`schema`] — [`OpenApiSchema`] wrapper with JSON-Schema-backed validation.
18//! - [`multi_spec`] — [`MultiSpec`], load and merge multiple OpenAPI docs with
19//!   conflict strategies.
20//! - [`response_selection`] — [`ResponseSelectionMode`] + [`ResponseSelector`] for
21//!   choosing between multiple example responses (first / scenario / sequential /
22//!   random / weighted).
23//! - [`spec_parser`] — unified [`OpenApiValidator`] / [`GraphQLValidator`] +
24//!   [`SpecFormat`] detector covering OpenAPI 2.0/3.0/3.1 and GraphQL.
25//! - [`validation`] — request/response validation helpers against an
26//!   [`OpenApiSpec`].
27//! - [`swagger_convert`] — Swagger 2.0 → OpenAPI 3.0 conversion helper used by
28//!   [`spec`] (re-exported for advanced callers).
29
30pub mod custom_fixture;
31pub mod multi_spec;
32pub mod openapi_routes;
33pub mod request_fingerprint;
34pub mod response;
35pub mod response_rewriter;
36pub mod response_trace;
37pub mod route;
38pub mod schema;
39pub mod schema_ref_resolver;
40pub mod spec;
41pub mod spec_parser;
42pub mod swagger_convert;
43pub mod validation;
44
45pub use custom_fixture::CustomFixtureLoader;
46pub use request_fingerprint::RequestFingerprint;
47pub use response_rewriter::ResponseRewriter;
48
49/// `ResponseSelectionMode` / `ResponseSelector` live in
50/// [`mockforge_foundation::response_selection`] — it's a generic selection
51/// primitive used by non-OpenAPI response trace code. Re-export here so
52/// `mockforge_openapi::response_selection::...` paths keep resolving.
53pub use mockforge_foundation::response_selection;
54
55// Mirror the blanket re-exports that the legacy `mockforge_core::openapi`
56// module exposed, so consumers can continue to glob-import from the facade.
57pub use response::*;
58pub use response_selection::*;
59pub use route::*;
60pub use schema::*;
61pub use spec::*;
62pub use validation::*;
63
64// Named re-exports for commonly used items.
65pub use spec_parser::{GraphQLValidator, OpenApiValidator, SpecFormat};
66
67/// Wrapper for OpenAPI operation with enhanced metadata.
68#[derive(Debug, Clone)]
69pub struct OpenApiOperation {
70    /// HTTP method (GET, POST, PUT, etc.)
71    pub method: String,
72    /// API path (e.g., "/api/users/{id}")
73    pub path: String,
74    /// OpenAPI operation specification
75    pub operation: openapiv3::Operation,
76    /// Security requirements for this operation
77    pub security: Option<Vec<openapiv3::SecurityRequirement>>,
78}
79
80impl OpenApiOperation {
81    /// Create a new OpenAPI operation wrapper
82    pub fn new(method: String, path: String, operation: openapiv3::Operation) -> Self {
83        Self {
84            method,
85            path,
86            operation: operation.clone(),
87            security: operation.security.clone(),
88        }
89    }
90
91    /// Create an operation from an OpenAPI operation reference
92    pub fn from_operation(
93        method: &str,
94        path: String,
95        operation: &openapiv3::Operation,
96        _spec: &OpenApiSpec,
97    ) -> Self {
98        Self::new(method.to_string(), path, operation.clone())
99    }
100}
101
102/// Type alias for OpenAPI security requirements.
103pub type OpenApiSecurityRequirement = openapiv3::SecurityRequirement;
104
105#[cfg(test)]
106mod tests {
107    use super::*;
108
109    #[test]
110    fn test_openapi_operation_new() {
111        let operation = openapiv3::Operation::default();
112        let op = OpenApiOperation::new("GET".to_string(), "/test".to_string(), operation);
113
114        assert_eq!(op.method, "GET");
115        assert_eq!(op.path, "/test");
116        assert!(op.security.is_none());
117    }
118
119    #[test]
120    fn test_openapi_operation_with_security() {
121        let operation = openapiv3::Operation {
122            security: Some(vec![]),
123            ..Default::default()
124        };
125
126        let op = OpenApiOperation::new("POST".to_string(), "/secure".to_string(), operation);
127
128        assert_eq!(op.method, "POST");
129        assert_eq!(op.path, "/secure");
130        assert!(op.security.is_some());
131    }
132
133    #[test]
134    fn test_openapi_operation_from_operation() {
135        let operation = openapiv3::Operation::default();
136        let spec = OpenApiSpec::from_json(serde_json::json!({
137            "openapi": "3.0.0",
138            "info": {"title": "Test", "version": "1.0.0"},
139            "paths": {}
140        }))
141        .unwrap();
142
143        let op =
144            OpenApiOperation::from_operation("PUT", "/resource".to_string(), &operation, &spec);
145
146        assert_eq!(op.method, "PUT");
147        assert_eq!(op.path, "/resource");
148    }
149
150    #[test]
151    fn test_openapi_operation_clone() {
152        let operation = openapiv3::Operation::default();
153        let op1 = OpenApiOperation::new("GET".to_string(), "/test".to_string(), operation);
154        let op2 = op1.clone();
155
156        assert_eq!(op1.method, op2.method);
157        assert_eq!(op1.path, op2.path);
158    }
159}