1use std::{
2 error::Error,
3 fmt::{self, Debug},
4 io::{self, Write},
5 str,
6};
7
8use listener::{Listener, ListenerRequest};
9use log::{error, warn};
10use manifest::Manifest;
11use resource::{Resource, ResourceRequest};
12use serde::{de::DeserializeOwned, Deserialize};
13use serde_json::Value;
14use view::{View, ViewRequest};
15
16pub mod api;
17pub mod components;
18pub mod listener;
19pub mod manifest;
20pub mod resource;
21pub mod view;
22
23pub type Result<T, E = Box<dyn Error>> = std::result::Result<T, E>;
24
25#[macro_export]
28macro_rules! from_value {
29 ($name:ident) => {
30 impl Into<$name> for serde_json::Value {
31 fn into(self) -> $name {
32 serde_json::from_value(self).unwrap()
33 }
34 }
35 };
36}
37
38#[macro_export]
39macro_rules! props {
40 ($name:ident) => {
41 impl From<$name> for crate::components::lenra::DefsProps {
42 fn from(value: $name) -> crate::components::lenra::DefsProps {
43 serde_json::to_value(value).unwrap().into()
44 }
45 }
46 impl From<$name> for crate::manifest::DefsProps {
47 fn from(value: $name) -> crate::manifest::DefsProps {
48 serde_json::to_value(value).unwrap().into()
49 }
50 }
51 };
52}
53
54#[derive(Default)]
57pub struct LenraApp {
58 pub manifest: Manifest,
59 pub views: Vec<View>,
60 pub listeners: Vec<Listener>,
61 pub resources: Vec<Resource>,
62}
63
64impl LenraApp {
65 pub fn run(self) -> Result<()> {
66 env_logger::init();
67
68 let request = serde_json::from_reader(std::io::stdin());
69 self.handle(
70 request.unwrap_or(Request::Other(Value::Null)),
71 &mut io::stdout(),
72 )
73 }
74
75 pub(crate) fn handle<W: Write>(self, request: Request, writer: &mut W) -> Result<()> {
76 match request {
77 Request::View(view) => self.handle_view(view, writer)?,
78 Request::Listener(listener) => self.handle_listener(listener, writer)?,
79 Request::Resource(resource) => self.handle_resource(resource, writer)?,
80 Request::Other(req) => {
81 if req != Value::Null {
82 warn!("Not managed request: {}", req);
83 }
84 write!(writer, "{}", serde_json::to_string(&self.manifest)?)?;
85 }
86 };
87 Ok(())
88 }
89
90 fn handle_view<W: Write>(self, request: ViewRequest, writer: &mut W) -> Result<()> {
91 let opt = self.views.iter().find(|&v| v.name() == request.name());
92 if let Some(view) = opt {
93 let result = view.handle(request)?;
94 write!(writer, "{}", result)?;
95 } else {
96 let message = format!("No view found for {}", request.name());
97 error!("{}", message);
98 return Err(Box::new(CustomError { message }));
99 };
100 Ok(())
101 }
102
103 fn handle_listener<W: Write>(self, request: ListenerRequest, _writer: &mut W) -> Result<()> {
104 let opt = self.listeners.iter().find(|&v| v.name() == request.name());
105 if let Some(listener) = opt {
106 listener.handle(request)
107 } else {
108 let message = format!("No listener found for {}", request.name());
109 error!("{}", message);
110 Err(Box::new(CustomError { message }))
111 }
112 }
113
114 fn handle_resource<W: Write>(self, request: ResourceRequest, writer: &mut W) -> Result<()> {
115 let opt = self.resources.iter().find(|&v| v.name() == request.name());
116 if let Some(resource) = opt {
117 let result = resource.handle(request)?;
118 writer.write_all(&result)?;
119 } else {
120 let message = format!("No resource found for {}", request.name());
121 error!("{}", message);
122 return Err(Box::new(CustomError { message }));
123 };
124 Ok(())
125 }
126}
127
128pub trait HandleParams<R> {
129 fn from_request(request: R) -> Self;
130}
131
132pub trait NamedRequest {
133 fn name(&self) -> String;
134}
135
136pub trait RequestHandler<Req, Res>: Sized
137where
138 Req: NamedRequest,
139{
140 fn name(&self) -> String;
141 fn handle(&self, request: Req) -> Result<Res>;
142 fn create(name: &str, build_fn: Box<dyn Fn(Req) -> Result<Res>>) -> Self;
143}
144
145pub trait Handler<Req, Res>: RequestHandler<Req, Res>
146where
147 Req: NamedRequest,
148{
149 fn new<P, F>(name: &str, build_fn: F) -> Self
150 where
151 P: HandleParams<Req>,
152 F: Fn(P) -> Result<Res> + 'static,
153 {
154 let boxed_fn: Box<dyn Fn(Req) -> Result<Res>> =
155 Box::new(move |request: Req| build_fn(P::from_request(request)));
156 Self::create(name, boxed_fn)
157 }
158}
159
160pub(crate) fn from_opt_value<T>(opt: Option<Value>) -> Result<Option<T>>
161where
162 T: DeserializeOwned + 'static,
163{
164 Ok(match opt {
165 Some(value) => match value.clone() {
166 Value::Null => None,
167 Value::Object(obj) => {
168 if obj.is_empty() {
169 None
170 } else {
171 Some(
172 serde_json::from_value(value)
173 .map_err(|err| Box::new(err) as Box<dyn std::error::Error>)?,
174 )
175 }
176 }
177 _ => Some(
178 serde_json::from_value(value)
179 .map_err(|err| Box::new(err) as Box<dyn std::error::Error>)?,
180 ),
181 },
182 None => None,
183 })
184}
185
186#[derive(Deserialize, Debug, PartialEq)]
188#[serde(untagged)]
189enum Request {
190 View(ViewRequest),
191 Listener(ListenerRequest),
192 Resource(ResourceRequest),
193 Other(Value),
194}
195
196#[derive(Debug)]
197struct CustomError {
198 message: String,
199}
200
201impl Error for CustomError {}
202
203impl fmt::Display for CustomError {
204 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
205 write!(f, "{}", self.message)
206 }
207}
208
209pub trait ComponentBuilder<T>: Sized + Clone + std::fmt::Debug
210where
211 T: serde::ser::Serialize + std::convert::TryFrom<Self>,
212 T::Error: std::fmt::Display + std::fmt::Debug,
213{
214 fn build(self) -> T {
215 T::try_from(self).unwrap()
216 }
217}
218
219#[cfg(test)]
220mod test {
221
222 use serde_json::json;
223
224 use crate::view::{ViewParams, ViewResponseGenerator};
225
226 use super::*;
227
228 #[test]
229 fn simple_view() {
230 let app = LenraApp {
231 views: vec![View::new("test", |_: ViewParams| {
235 Ok(json!({"type": "text", "value": "test"}).gen())
236 })],
237 ..Default::default()
238 };
239 let request = ViewRequest {
240 view: "test".into(),
241 data: None,
242 props: None,
243 context: None,
244 };
245 let mut buf = Vec::new();
246 app.handle(Request::View(request), &mut buf).unwrap();
247 let result = String::from_utf8(buf).unwrap();
248 assert_eq!(result, r#"{"type":"text","value":"test"}"#);
249 }
250
251 #[test]
252 #[should_panic]
253 fn unkown_view() {
254 let app = LenraApp {
255 views: vec![View::new("test", |_: ViewParams| {
259 Ok(json!({"type": "text", "value": "test"}).gen())
260 })],
261 ..Default::default()
262 };
263 let request = ViewRequest {
264 view: "test2".into(),
265 data: None,
266 props: None,
267 context: None,
268 };
269 let mut buf = Vec::new();
270 app.handle(Request::View(request), &mut buf).unwrap();
271 }
272}