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 spec;
40pub mod spec_parser;
41pub mod swagger_convert;
42pub mod validation;
43
44pub use custom_fixture::CustomFixtureLoader;
45pub use request_fingerprint::RequestFingerprint;
46pub use response_rewriter::ResponseRewriter;
47
48/// `ResponseSelectionMode` / `ResponseSelector` live in
49/// [`mockforge_foundation::response_selection`] — it's a generic selection
50/// primitive used by non-OpenAPI response trace code. Re-export here so
51/// `mockforge_openapi::response_selection::...` paths keep resolving.
52pub use mockforge_foundation::response_selection;
53
54// Mirror the blanket re-exports that the legacy `mockforge_core::openapi`
55// module exposed, so consumers can continue to glob-import from the facade.
56pub use response::*;
57pub use response_selection::*;
58pub use route::*;
59pub use schema::*;
60pub use spec::*;
61pub use validation::*;
62
63// Named re-exports for commonly used items.
64pub use spec_parser::{GraphQLValidator, OpenApiValidator, SpecFormat};
65
66/// Wrapper for OpenAPI operation with enhanced metadata.
67#[derive(Debug, Clone)]
68pub struct OpenApiOperation {
69    /// HTTP method (GET, POST, PUT, etc.)
70    pub method: String,
71    /// API path (e.g., "/api/users/{id}")
72    pub path: String,
73    /// OpenAPI operation specification
74    pub operation: openapiv3::Operation,
75    /// Security requirements for this operation
76    pub security: Option<Vec<openapiv3::SecurityRequirement>>,
77}
78
79impl OpenApiOperation {
80    /// Create a new OpenAPI operation wrapper
81    pub fn new(method: String, path: String, operation: openapiv3::Operation) -> Self {
82        Self {
83            method,
84            path,
85            operation: operation.clone(),
86            security: operation.security.clone(),
87        }
88    }
89
90    /// Create an operation from an OpenAPI operation reference
91    pub fn from_operation(
92        method: &str,
93        path: String,
94        operation: &openapiv3::Operation,
95        _spec: &OpenApiSpec,
96    ) -> Self {
97        Self::new(method.to_string(), path, operation.clone())
98    }
99}
100
101/// Type alias for OpenAPI security requirements.
102pub type OpenApiSecurityRequirement = openapiv3::SecurityRequirement;
103
104#[cfg(test)]
105mod tests {
106    use super::*;
107
108    #[test]
109    fn test_openapi_operation_new() {
110        let operation = openapiv3::Operation::default();
111        let op = OpenApiOperation::new("GET".to_string(), "/test".to_string(), operation);
112
113        assert_eq!(op.method, "GET");
114        assert_eq!(op.path, "/test");
115        assert!(op.security.is_none());
116    }
117
118    #[test]
119    fn test_openapi_operation_with_security() {
120        let operation = openapiv3::Operation {
121            security: Some(vec![]),
122            ..Default::default()
123        };
124
125        let op = OpenApiOperation::new("POST".to_string(), "/secure".to_string(), operation);
126
127        assert_eq!(op.method, "POST");
128        assert_eq!(op.path, "/secure");
129        assert!(op.security.is_some());
130    }
131
132    #[test]
133    fn test_openapi_operation_from_operation() {
134        let operation = openapiv3::Operation::default();
135        let spec = OpenApiSpec::from_json(serde_json::json!({
136            "openapi": "3.0.0",
137            "info": {"title": "Test", "version": "1.0.0"},
138            "paths": {}
139        }))
140        .unwrap();
141
142        let op =
143            OpenApiOperation::from_operation("PUT", "/resource".to_string(), &operation, &spec);
144
145        assert_eq!(op.method, "PUT");
146        assert_eq!(op.path, "/resource");
147    }
148
149    #[test]
150    fn test_openapi_operation_clone() {
151        let operation = openapiv3::Operation::default();
152        let op1 = OpenApiOperation::new("GET".to_string(), "/test".to_string(), operation);
153        let op2 = op1.clone();
154
155        assert_eq!(op1.method, op2.method);
156        assert_eq!(op1.path, op2.path);
157    }
158}