awesome_operates/router/
mod.rs1use axum::{
2 body::Body,
3 http::{self, Method, Request},
4 response::Response,
5 routing::MethodRouter,
6 Router,
7};
8use serde_json::Value;
9use snafu::OptionExt;
10use tower::{Service, ServiceExt};
11
12use crate::error::{OptionNoneSnafu, Result};
13use crate::helper::iter_object;
14use crate::method_exchange;
15
16mod handler;
17#[cfg(test)]
18mod tests;
19
20#[derive(Default)]
49pub struct RequestMatcher {
50 pub router: Router,
51}
52
53impl RequestMatcher {
54 pub fn from_openapi(openapi: &Value, path_prefix: &str) -> Result<Self> {
55 let route_handles = Self::openapi_route_handles(openapi, path_prefix)?;
56 Ok(RequestMatcher::from_route_methods(route_handles))
57 }
58
59 pub fn from_route_methods(route_methods: Vec<(String, MethodRouter)>) -> Self {
60 let mut router = Router::new();
61 for (path, resp) in route_methods {
62 router = router.route(path.as_ref(), resp);
63 }
64 RequestMatcher { router }
65 }
66
67 pub fn openapi_route_handles(
70 openapi: &Value,
71 path_prefix: &str,
72 ) -> Result<Vec<(String, MethodRouter)>> {
73 let mut route_handlers = vec![];
74 for (path, operate) in iter_object(openapi, "paths")? {
75 let path = path.replace('{', ":").replace('}', "");
76 for (method, detail) in operate.as_object().context(OptionNoneSnafu)?.iter() {
77 if !detail.is_object() {
78 continue;
79 }
80 let summary = detail.get("summary");
81 let component = Self::api_component(
82 openapi,
83 detail.pointer("/requestBody/content/application~1json/schema/$ref"),
84 );
85 let path_with_prefix = format!("{}{path}", path_prefix.trim_end_matches('/'));
86 let resp = serde_json::json!({
87 "openapi_path": path,
88 "method": method,
89 "summary": summary,
90 "component": component,
91 "path_with_prefix": path_with_prefix,
92 });
93 tracing::debug!(
94 r#"read route handle
95 path_prefix[{path_prefix}]
96 path[{path}]
97 method[{method}]
98 summary[{summary:?}]
99 component[{component:?}]
100 resp[{resp}]"#
101 );
102 route_handlers.push((path_with_prefix, method_exchange!(method, &path, resp)));
103 }
104 }
105 Ok(route_handlers)
106 }
107
108 pub fn api_component<'a>(
109 openapi: &'a Value,
110 component_path: Option<&Value>,
111 ) -> Option<&'a Value> {
112 if let Some(path) = component_path {
113 if let Some(p) = path.as_str() {
114 if p.starts_with('#') {
115 return openapi.pointer(&p.replace('#', ""));
116 }
117 }
118 }
119 None
120 }
121
122 pub async fn match_request_to_response(
123 &mut self,
124 method: Method,
125 path: &str,
126 body: Option<Body>,
127 ) -> anyhow::Result<Response> {
128 let method = method.as_str().to_uppercase().parse().unwrap();
130 tracing::debug!(
131 "match request [method]{} [path]:{} body:[{body:?}] ",
132 method,
133 path
134 );
135 let request = Self::build_request(method, path, body);
136 tracing::debug!("match request before {request:?}");
137 let response = ServiceExt::<Request<Body>>::ready(&mut self.router)
138 .await?
139 .call(request)
140 .await?;
141 tracing::debug!("match api with result status: {}", response.status());
142 Ok(response)
143 }
144
145 pub async fn match_request_to_json_response(
146 &mut self,
147 method: Method,
148 path: &str,
149 body: Option<Body>,
150 ) -> anyhow::Result<Value> {
151 let response = self.match_request_to_response(method, path, body).await?;
152 let bytes = http_body_util::BodyExt::collect(response.into_body())
153 .await?
154 .to_bytes();
155 Ok(serde_json::from_slice(&bytes)?)
156 }
157
158 pub fn build_request(method: Method, path: &str, body: Option<Body>) -> Request<Body> {
159 let body = body.unwrap_or_default();
160 Request::builder()
161 .method(method)
162 .header(http::header::CONTENT_TYPE, mime::APPLICATION_JSON.as_ref())
163 .uri(path)
164 .body(body)
165 .unwrap()
166 }
167}