1use alux_ext::{ApplyAlg, HandlerContextAlg};
8use alux_http::{
9 FileOutAlg, HandlerAlg, HandlerEndpointAlg, HttpInputAlg, HttpSelectorAlg, JsonOutAlg, OutputAlg, OutputKindAlg,
10 RouteAlg, SelectorAlg, append_path,
11};
12use core::any::type_name;
13use core::marker::PhantomData;
14use std::sync::Arc;
15
16#[derive(Debug, Clone, PartialEq, Eq)]
18pub struct TextEndpoint {
19 handler: &'static str,
20 inputs: &'static str,
21 args: &'static str,
22 result: &'static str,
23 transform: &'static str,
24 output: &'static str,
25}
26
27pub struct TextInputRole<Role, Input>(PhantomData<fn(Role) -> Input>);
29
30pub struct PathRole;
32pub struct QueryRole;
34pub struct BodyRole;
36pub struct HeaderRole;
38pub struct AuthRole;
40pub struct ContextRole;
42
43pub struct TextJsonOutput;
45
46impl<From> OutputAlg<From> for TextJsonOutput {
47 type Output = From;
48
49 fn output(from: From) -> From {
50 from
51 }
52}
53
54pub struct TextFileOutput;
56
57impl<From> OutputAlg<From> for TextFileOutput {
58 type Output = From;
59
60 fn output(from: From) -> From {
61 from
62 }
63}
64
65#[derive(Debug, Clone, PartialEq, Eq)]
66enum TextSelectorPart {
67 Method(&'static str),
68 Path(String),
69 Prefix(String),
70}
71
72#[derive(Debug, Clone, Default, PartialEq, Eq)]
74pub struct TextSelector {
75 parts: Vec<TextSelectorPart>,
76}
77
78#[derive(Debug, Clone, PartialEq, Eq)]
79struct TextRouteEntry {
80 selector: TextSelector,
81 endpoint: TextEndpoint,
82}
83
84#[derive(Debug, Clone, Default, PartialEq, Eq)]
86pub struct TextRoute {
87 entries: Vec<TextRouteEntry>,
88}
89
90impl TextRoute {
91 pub fn labels(&self) -> Vec<String> {
93 self.entries.iter().map(|entry| entry.selector.label()).collect()
94 }
95
96 pub fn paths(&self) -> Vec<String> {
98 self.entries.iter().map(|entry| entry.selector.path()).collect()
99 }
100
101 pub fn lines(&self) -> Vec<String> {
103 self.entries
104 .iter()
105 .map(|entry| {
106 format!(
107 concat!(
108 "### {}\n",
109 "- `handler`: `{}`\n",
110 "- `inputs`: `{}`\n",
111 "- `args`: `{}`\n",
112 "- `result`: `{}`\n",
113 "- `transform`: `{}`\n",
114 "- `output`: `{}`",
115 ),
116 entry.selector.label(),
117 entry.endpoint.handler,
118 entry.endpoint.inputs,
119 entry.endpoint.args,
120 entry.endpoint.result,
121 entry.endpoint.transform,
122 entry.endpoint.output,
123 )
124 })
125 .collect()
126 }
127}
128
129impl TextSelector {
130 pub fn path(&self) -> String {
132 let mut path = String::new();
133 for part in &self.parts {
134 if let TextSelectorPart::Path(value) | TextSelectorPart::Prefix(value) = part {
135 append_path(&mut path, value);
136 }
137 }
138
139 if path.is_empty() {
140 path.push('/');
141 }
142
143 path
144 }
145
146 pub fn label(&self) -> String {
148 let method = self
149 .parts
150 .iter()
151 .rev()
152 .find_map(|part| match part {
153 TextSelectorPart::Method(value) => Some(*value),
154 TextSelectorPart::Path(_) | TextSelectorPart::Prefix(_) => None,
155 })
156 .unwrap_or("*");
157
158 format!("{method} {}", self.path())
159 }
160}
161
162#[derive(Debug, Default)]
164pub struct TextHandlerImpl;
165
166impl HandlerAlg for TextHandlerImpl {
167 type Endpoint = TextEndpoint;
168}
169
170impl<Context, Inputs, Args, Transform, Output> HandlerEndpointAlg<Context, Inputs, Args, Transform, Output>
171 for TextHandlerImpl
172where
173 Transform: OutputKindAlg<TextHandlerImpl, Output>,
174{
175 fn finish_handler<Handler>(&self, _handler: Handler) -> TextEndpoint
176 where
177 Handler: ApplyAlg<Context, Args, Output = Output> + Send + Sync + 'static,
178 {
179 TextEndpoint {
180 handler: type_name::<Handler>(),
181 inputs: type_name::<Inputs>(),
182 args: type_name::<Args>(),
183 result: type_name::<Output>(),
184 transform: type_name::<<Transform as OutputKindAlg<Self, Output>>::Transform>(),
185 output: type_name::<<<Transform as OutputKindAlg<Self, Output>>::Transform as OutputAlg<Output>>::Output>(),
186 }
187 }
188}
189
190impl<Context> HandlerContextAlg<Context> for TextHandlerImpl
191where
192 Context: Send + Sync + 'static,
193{
194 type Handle = Arc<Context>;
195}
196
197impl SelectorAlg for TextHandlerImpl {
198 type Selector = TextSelector;
199
200 fn identity(&self) -> TextSelector {
201 TextSelector::default()
202 }
203
204 fn compose(&self, mut first: TextSelector, second: TextSelector) -> TextSelector {
205 first.parts.extend(second.parts);
206 first
207 }
208}
209
210impl RouteAlg for TextHandlerImpl {
211 type Route = TextRoute;
212 type Selector = TextSelector;
213 type Endpoint = TextEndpoint;
214
215 fn initial(&self) -> TextRoute {
216 TextRoute::default()
217 }
218
219 fn coproduct(&self, mut left: TextRoute, right: TextRoute) -> TextRoute {
220 left.entries.extend(right.entries);
221 left
222 }
223
224 fn precompose(&self, selector: TextSelector, mut route: TextRoute) -> TextRoute {
225 for entry in &mut route.entries {
226 entry.selector = self.compose(selector.clone(), core::mem::take(&mut entry.selector));
227 }
228 route
229 }
230
231 fn lift(&self, endpoint: TextEndpoint) -> TextRoute {
232 TextRoute { entries: vec![TextRouteEntry { selector: self.identity(), endpoint }] }
233 }
234}
235
236impl HttpInputAlg for TextHandlerImpl {
237 type Path<Input> = TextInputRole<PathRole, Input>;
238 type Query<Input> = TextInputRole<QueryRole, Input>;
239 type Body<Input> = TextInputRole<BodyRole, Input>;
240 type Header<Input> = TextInputRole<HeaderRole, Input>;
241 type Auth<Input> = TextInputRole<AuthRole, Input>;
242 type Context<Input> = TextInputRole<ContextRole, Input>;
243}
244
245impl JsonOutAlg for TextHandlerImpl {
246 type Json<From> = TextJsonOutput;
247}
248
249impl FileOutAlg for TextHandlerImpl {
250 type File<From> = TextFileOutput;
251}
252
253impl HttpSelectorAlg for TextHandlerImpl {
254 type Selector = TextSelector;
255
256 fn http_get(&self) -> TextSelector {
257 TextSelector { parts: vec![TextSelectorPart::Method("GET")] }
258 }
259
260 fn http_post(&self) -> TextSelector {
261 TextSelector { parts: vec![TextSelectorPart::Method("POST")] }
262 }
263
264 fn http_path(&self, path: &str) -> TextSelector {
265 TextSelector { parts: vec![TextSelectorPart::Path(path.into())] }
266 }
267
268 fn http_prefix(&self, prefix: &str) -> TextSelector {
269 TextSelector { parts: vec![TextSelectorPart::Prefix(prefix.into())] }
270 }
271}