allframe_core/router/
metadata.rs

1//! Route metadata for documentation generation
2//!
3//! This module provides the types and functionality for extracting and storing
4//! metadata about registered routes, which can then be used to generate
5//! OpenAPI specifications, GraphQL schemas, and gRPC reflection data.
6
7use serde::{Deserialize, Serialize};
8
9/// Metadata about a registered route
10///
11/// Contains all information needed to generate documentation
12/// for a route across different protocols (REST, GraphQL, gRPC).
13#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
14pub struct RouteMetadata {
15    /// The route path (e.g., "/users", "/users/{id}")
16    pub path: String,
17
18    /// HTTP method for REST routes (e.g., "GET", "POST")
19    /// Empty for non-REST protocols
20    pub method: String,
21
22    /// Protocol this route belongs to (e.g., "rest", "graphql", "grpc")
23    pub protocol: String,
24
25    /// Optional description from doc comments
26    pub description: Option<String>,
27
28    /// Request schema as JSON Schema (if available)
29    pub request_schema: Option<serde_json::Value>,
30
31    /// Response schema as JSON Schema (if available)
32    pub response_schema: Option<serde_json::Value>,
33}
34
35impl RouteMetadata {
36    /// Create a new RouteMetadata
37    pub fn new(
38        path: impl Into<String>,
39        method: impl Into<String>,
40        protocol: impl Into<String>,
41    ) -> Self {
42        Self {
43            path: path.into(),
44            method: method.into(),
45            protocol: protocol.into(),
46            description: None,
47            request_schema: None,
48            response_schema: None,
49        }
50    }
51
52    /// Set the description
53    pub fn with_description(mut self, description: impl Into<String>) -> Self {
54        self.description = Some(description.into());
55        self
56    }
57
58    /// Set the request schema
59    pub fn with_request_schema(mut self, schema: serde_json::Value) -> Self {
60        self.request_schema = Some(schema);
61        self
62    }
63
64    /// Set the response schema
65    pub fn with_response_schema(mut self, schema: serde_json::Value) -> Self {
66        self.response_schema = Some(schema);
67        self
68    }
69}
70
71#[cfg(test)]
72mod tests {
73    use super::*;
74
75    #[test]
76    fn test_route_metadata_creation() {
77        let metadata = RouteMetadata::new("/users", "GET", "rest");
78
79        assert_eq!(metadata.path, "/users");
80        assert_eq!(metadata.method, "GET");
81        assert_eq!(metadata.protocol, "rest");
82        assert_eq!(metadata.description, None);
83        assert_eq!(metadata.request_schema, None);
84        assert_eq!(metadata.response_schema, None);
85    }
86
87    #[test]
88    fn test_route_metadata_with_description() {
89        let metadata =
90            RouteMetadata::new("/users", "POST", "rest").with_description("Create a new user");
91
92        assert_eq!(metadata.description, Some("Create a new user".to_string()));
93    }
94
95    #[test]
96    fn test_route_metadata_with_schemas() {
97        let request_schema = serde_json::json!({
98            "type": "object",
99            "properties": {
100                "name": {"type": "string"}
101            }
102        });
103
104        let response_schema = serde_json::json!({
105            "type": "object",
106            "properties": {
107                "id": {"type": "string"},
108                "name": {"type": "string"}
109            }
110        });
111
112        let metadata = RouteMetadata::new("/users", "POST", "rest")
113            .with_request_schema(request_schema.clone())
114            .with_response_schema(response_schema.clone());
115
116        assert_eq!(metadata.request_schema, Some(request_schema));
117        assert_eq!(metadata.response_schema, Some(response_schema));
118    }
119
120    #[test]
121    fn test_route_metadata_builder_pattern() {
122        let metadata = RouteMetadata::new("/users/{id}", "GET", "rest")
123            .with_description("Get user by ID")
124            .with_response_schema(serde_json::json!({"type": "object"}));
125
126        assert_eq!(metadata.path, "/users/{id}");
127        assert_eq!(metadata.method, "GET");
128        assert_eq!(metadata.protocol, "rest");
129        assert!(metadata.description.is_some());
130        assert!(metadata.response_schema.is_some());
131        assert!(metadata.request_schema.is_none());
132    }
133
134    #[test]
135    fn test_route_metadata_graphql_protocol() {
136        let metadata =
137            RouteMetadata::new("users", "query", "graphql").with_description("Query users");
138
139        assert_eq!(metadata.protocol, "graphql");
140        assert_eq!(metadata.method, "query");
141    }
142
143    #[test]
144    fn test_route_metadata_grpc_protocol() {
145        let metadata = RouteMetadata::new("UserService.CreateUser", "unary", "grpc")
146            .with_description("Create a user via gRPC");
147
148        assert_eq!(metadata.protocol, "grpc");
149        assert_eq!(metadata.method, "unary");
150    }
151
152    #[test]
153    fn test_route_metadata_clone() {
154        let metadata1 = RouteMetadata::new("/test", "GET", "rest").with_description("Test route");
155
156        let metadata2 = metadata1.clone();
157
158        assert_eq!(metadata1, metadata2);
159    }
160
161    #[test]
162    fn test_route_metadata_serialization() {
163        let metadata = RouteMetadata::new("/users", "POST", "rest").with_description("Create user");
164
165        let json = serde_json::to_string(&metadata).unwrap();
166        let deserialized: RouteMetadata = serde_json::from_str(&json).unwrap();
167
168        assert_eq!(metadata, deserialized);
169    }
170}