1mod api;
6mod api_run;
7mod config;
8mod input;
9mod types;
10
11pub use api::{table_impl, Api, ApiField, ApiVariant, FieldFormat};
12pub use api_run::{ApiInformation, ApiRun};
13pub use config::{arg_config, ApiInputConfig};
14use darling::FromMeta;
15use derive_builder::Builder;
16pub use input::{ApiInputFieldSerde, ApiInputSerde, ApiInputVariantSerde, DataSerde};
17use serde::{Deserialize, Serialize};
18use std::{collections::HashMap, fs::File, io::BufReader, path::PathBuf};
19pub use types::VecStringWrapper;
20
21#[macro_use]
22extern crate lazy_static;
23
24#[derive(Debug, Clone, Builder, FromMeta, Serialize, Deserialize)]
26#[builder(setter(into))]
27#[builder(default)]
28#[darling(default)]
29pub struct Endpoint {
30 #[serde(skip_serializing_if = "String::is_empty")]
38 pub route: String,
39 #[serde(skip_serializing_if = "String::is_empty")]
40 pub method: String,
41 #[serde(skip_serializing_if = "Option::is_none")]
42 pub payload_struct: Option<String>,
43 #[serde(skip_serializing_if = "Option::is_none")]
44 pub query_struct: Option<String>,
45 #[serde(skip_serializing_if = "String::is_empty")]
49 pub result_ok_status: String,
50 #[darling(multiple)]
51 pub result_ko_status: Vec<EndpointStatus>,
52 #[serde(skip_serializing_if = "String::is_empty")]
53 pub result_struct: String,
54 #[darling(rename = "multiple_results")]
56 pub result_multiple: bool,
57 #[darling(rename = "stream")]
61 pub result_is_stream: bool,
62 #[darling(default)]
64 #[darling(multiple)]
65 pub extra_header: Vec<Header>,
66 #[serde(skip_serializing_if = "Option::is_none")]
70 pub extra_action: Option<String>,
71 pub no_auth: bool,
73 pub transform_from: Option<String>,
112
113 #[serde(skip_serializing_if = "String::is_empty")]
120 pub cli_route: String,
121 #[serde(skip_serializing_if = "Option::is_none")]
123 pub cli_help: Option<String>,
124 #[serde(skip_serializing_if = "Option::is_none")]
126 pub cli_long_help: Option<String>,
127 #[serde(skip_serializing_if = "Option::is_none")]
128 pub cli_visible_aliases: Option<VecStringWrapper>,
129 #[serde(skip_serializing_if = "Option::is_none")]
130 pub cli_long_flag_aliases: Option<VecStringWrapper>,
131 #[serde(skip_serializing_if = "Option::is_none")]
132 pub cli_aliases: Option<VecStringWrapper>,
133 #[serde(skip_serializing_if = "Option::is_none")]
134 pub cli_short_flag_aliases: Option<VecStringWrapper>,
135 pub cli_no_output: bool,
149 #[serde(skip_serializing_if = "Option::is_none")]
150 pub cli_output_formats: Option<VecStringWrapper>,
151 pub cli_force_output_format: bool,
162
163 #[darling(default)]
164 #[darling(multiple)]
165 pub config: Vec<ApiInputConfig>,
166}
167
168#[derive(Debug, Clone, Default, Builder, FromMeta, Serialize, Deserialize)]
169#[builder(setter(into))]
170#[builder(default)]
171#[darling(default)]
172pub struct EndpointStatus {
173 pub status: String,
174 pub message: String,
175}
176
177#[derive(Debug, Clone, FromMeta, Serialize, Deserialize)]
178pub struct Header {
179 pub key: String,
180 pub value: String,
181}
182
183impl Default for Endpoint {
184 fn default() -> Self {
185 Self {
186 method: "GET".into(),
187 route: Default::default(),
188 payload_struct: Default::default(),
189 query_struct: Default::default(),
190 result_ok_status: "OK".into(),
192 result_ko_status: Default::default(),
193 result_struct: Default::default(),
194 result_multiple: Default::default(),
195 result_is_stream: false,
196 extra_header: Default::default(),
197 extra_action: Default::default(),
198 no_auth: false,
199 transform_from: Default::default(),
200 cli_route: Default::default(),
201 cli_help: Default::default(),
202 cli_long_help: Default::default(),
203 cli_visible_aliases: Default::default(),
204 cli_long_flag_aliases: Default::default(),
205 cli_aliases: Default::default(),
206 cli_short_flag_aliases: Default::default(),
207 cli_output_formats: Default::default(),
208 cli_force_output_format: Default::default(),
209 cli_no_output: Default::default(),
210 config: Default::default(),
211 }
212 }
213}
214
215pub type Emap = HashMap<String, EpNode>;
216
217#[derive(Debug, Clone, Serialize, Deserialize)]
218pub struct EpNode {
219 pub endpoint: Vec<Endpoint>,
220 pub route: Emap,
221}
222
223fn endpoint_filename() -> PathBuf {
224 let mut dir = scratch::path("crud_api");
225 if let Err(why) = std::fs::create_dir_all(&dir) {
226 panic!(
227 "! Error while creating the endpoints temp dir: {kind:?}",
228 kind = why.kind()
229 );
230 }
231
232 dir.push(format!("endpoints-{}.json", std::process::id()));
233 dir
234}
235
236#[derive(Default, Serialize, Deserialize)]
237struct TmpStore {
238 ep: Emap,
239 inputs: HashMap<String, ApiInputSerde>,
240}
241
242fn load_store() -> TmpStore {
243 match File::open(endpoint_filename()) {
244 Ok(file) => {
245 let reader = BufReader::new(file);
246 let u: TmpStore = serde_json::from_reader(reader).expect("Error reading endpoints.json.");
247 u
248 }
249 Err(_) => TmpStore::default(),
250 }
251}
252
253pub fn input_map() -> HashMap<String, ApiInputSerde> {
254 load_store().inputs
255}
256
257pub fn store_input(input: String, field: impl Into<ApiInputSerde>) {
258 let mut store = load_store();
259 store.inputs.insert(input, field.into());
262 let file = File::create(endpoint_filename()).expect("Can't open file in write mode");
263 serde_json::to_writer_pretty(file, &store).unwrap();
264}
265
266pub fn endpoints() -> Emap {
267 load_store().ep
268}
269
270pub fn store_endpoint(epoint: Endpoint) {
271 let mut store = load_store();
274 let mut segments: Vec<&str> = epoint.cli_route.split('/').collect();
275 segments.reverse();
276
277 let map = insert_endpoint(store.ep, &epoint, segments);
278
279 let file = File::create(endpoint_filename()).expect("Can't open file in write mode");
280 store.ep = map;
281 serde_json::to_writer_pretty(file, &store).unwrap();
282}
283
284fn insert_endpoint(map: Emap, ep: &Endpoint, mut segments: Vec<&str>) -> Emap {
285 if let Some(segment) = segments.pop() {
286 if segment.is_empty() {
287 return insert_endpoint(map, ep, segments);
288 }
289 let mut map = map;
290 if segments.is_empty() {
291 if let Some(node) = map.get(segment) {
293 let mut node = node.to_owned();
294 node.endpoint.push(ep.to_owned());
295 map.insert(segment.to_string(), node);
296 } else {
297 let node = EpNode {
298 endpoint: vec![ep.to_owned()],
299 route: HashMap::new(),
300 };
301 map.insert(segment.to_string(), node);
302 }
303 map
304 } else if let Some(node) = map.get(segment) {
305 let mut node = node.to_owned();
306 node.route = insert_endpoint(node.route.to_owned(), ep, segments);
307 map.insert(segment.to_string(), node);
308 map
309 } else {
310 let node = EpNode {
311 endpoint: vec![],
312 route: insert_endpoint(HashMap::new(), ep, segments),
313 };
314 map.insert(segment.to_string(), node);
315 map
316 }
317 } else {
318 map
319 }
320}
321
322#[cfg(test)]
323mod tests {
324 use super::{insert_endpoint, EndpointBuilder};
325 use std::collections::HashMap;
326
327 #[test]
328 fn test_insert_simple_endpoint() {
329 let ep = EndpointBuilder::default()
330 .cli_route("/")
331 .route("/")
332 .build()
333 .unwrap();
334 let mut segments: Vec<&str> = ep.cli_route.split('/').collect::<Vec<&str>>();
335 segments.reverse();
336 let map = HashMap::new();
337 let result = insert_endpoint(map, &ep, segments);
338 assert_eq!(serde_json::to_string(&result).unwrap(), "{}");
339 }
340
341 #[test]
342 fn test_insert_one_endpoint_at_one_level_endpoint() {
343 let ep = EndpointBuilder::default()
344 .cli_route("/post")
345 .route("/post")
346 .build()
347 .unwrap();
348 let mut segments: Vec<&str> = ep.cli_route.split('/').collect();
349 segments.reverse();
350 let map = HashMap::new();
351 let result = insert_endpoint(map, &ep, segments);
352 assert_eq!(serde_json::to_string(&result).unwrap(),"{\"post\":{\"endpoint\":[{\"route\":\"/post\",\"method\":\"GET\",\"result_ok_status\":\"OK\",\"result_ko_status\":[],\"result_multiple\":false,\"result_is_stream\":false,\"extra_header\":[],\"no_auth\":false,\"transform_from\":null,\"cli_route\":\"/post\",\"cli_no_output\":false,\"cli_force_output_format\":false,\"config\":[]}],\"route\":{}}}");
353 }
354
355 #[test]
356 fn test_insert_two_endpoints_at_one_level() {
357 let ep = EndpointBuilder::default()
358 .cli_route("/post")
359 .route("/post")
360 .build()
361 .unwrap();
362 let mut segments: Vec<&str> = ep.cli_route.split('/').collect();
363 segments.reverse();
364 let map = HashMap::new();
365 let map = insert_endpoint(map, &ep, segments);
366
367 let ep = EndpointBuilder::default()
368 .cli_route("/post")
369 .route("/post")
370 .method("POST")
371 .build()
372 .unwrap();
373 let mut segments: Vec<&str> = ep.cli_route.split('/').collect();
374 segments.reverse();
375 let result = insert_endpoint(map, &ep, segments);
376
377 assert_eq!(serde_json::to_string(&result).unwrap(),"{\"post\":{\"endpoint\":[{\"route\":\"/post\",\"method\":\"GET\",\"result_ok_status\":\"OK\",\"result_ko_status\":[],\"result_multiple\":false,\"result_is_stream\":false,\"extra_header\":[],\"no_auth\":false,\"transform_from\":null,\"cli_route\":\"/post\",\"cli_no_output\":false,\"cli_force_output_format\":false,\"config\":[]},{\"route\":\"/post\",\"method\":\"POST\",\"result_ok_status\":\"OK\",\"result_ko_status\":[],\"result_multiple\":false,\"result_is_stream\":false,\"extra_header\":[],\"no_auth\":false,\"transform_from\":null,\"cli_route\":\"/post\",\"cli_no_output\":false,\"cli_force_output_format\":false,\"config\":[]}],\"route\":{}}}");
378 }
379
380 #[test]
381 fn test_insert_three_endpoints_at_two_levels() {
382 let map = HashMap::new();
383 let ep = EndpointBuilder::default()
384 .cli_route("/post")
385 .route("/post")
386 .build()
387 .unwrap();
388 let mut segments: Vec<&str> = ep.cli_route.split('/').collect();
389 segments.reverse();
390 let map = insert_endpoint(map, &ep, segments);
391
392 let ep = EndpointBuilder::default()
393 .cli_route("/post")
394 .route("/post")
395 .method("POST")
396 .build()
397 .unwrap();
398 let mut segments: Vec<&str> = ep.cli_route.split('/').collect();
399 segments.reverse();
400 let map = insert_endpoint(map, &ep, segments);
401
402 let ep = EndpointBuilder::default()
403 .cli_route("/post/user")
404 .route("/post/user")
405 .build()
406 .unwrap();
407 let mut segments: Vec<&str> = ep.cli_route.split('/').collect();
408 segments.reverse();
409 let map = insert_endpoint(map, &ep, segments);
410 assert_eq!(serde_json::to_string(&map).unwrap(),"{\"post\":{\"endpoint\":[{\"route\":\"/post\",\"method\":\"GET\",\"result_ok_status\":\"OK\",\"result_ko_status\":[],\"result_multiple\":false,\"result_is_stream\":false,\"extra_header\":[],\"no_auth\":false,\"transform_from\":null,\"cli_route\":\"/post\",\"cli_no_output\":false,\"cli_force_output_format\":false,\"config\":[]},{\"route\":\"/post\",\"method\":\"POST\",\"result_ok_status\":\"OK\",\"result_ko_status\":[],\"result_multiple\":false,\"result_is_stream\":false,\"extra_header\":[],\"no_auth\":false,\"transform_from\":null,\"cli_route\":\"/post\",\"cli_no_output\":false,\"cli_force_output_format\":false,\"config\":[]}],\"route\":{\"user\":{\"endpoint\":[{\"route\":\"/post/user\",\"method\":\"GET\",\"result_ok_status\":\"OK\",\"result_ko_status\":[],\"result_multiple\":false,\"result_is_stream\":false,\"extra_header\":[],\"no_auth\":false,\"transform_from\":null,\"cli_route\":\"/post/user\",\"cli_no_output\":false,\"cli_force_output_format\":false,\"config\":[]}],\"route\":{}}}}}");
411 }
412
413 #[test]
414 fn test_insert_one_endpoints_at_three_levels() {
415 let map = HashMap::new();
416 let ep = EndpointBuilder::default()
417 .cli_route("/post/comments/replies")
418 .route("/post")
419 .build()
420 .unwrap();
421 let mut segments: Vec<&str> = ep.cli_route.split('/').collect();
422 segments.reverse();
423 let map = insert_endpoint(map, &ep, segments);
424
425 assert_eq!(serde_json::to_string(&map).unwrap(),"{\"post\":{\"endpoint\":[],\"route\":{\"comments\":{\"endpoint\":[],\"route\":{\"replies\":{\"endpoint\":[{\"route\":\"/post\",\"method\":\"GET\",\"result_ok_status\":\"OK\",\"result_ko_status\":[],\"result_multiple\":false,\"result_is_stream\":false,\"extra_header\":[],\"no_auth\":false,\"transform_from\":null,\"cli_route\":\"/post/comments/replies\",\"cli_no_output\":false,\"cli_force_output_format\":false,\"config\":[]}],\"route\":{}}}}}}}");
426 }
427
428 #[test]
429 fn test_endpoint_default() {
430 let ep = EndpointBuilder::default().build().unwrap();
431 assert_eq!(ep.route, "".to_string());
432 assert_eq!(ep.method, "GET".to_string());
433 }
434 #[test]
435 fn test_endpoint_result_struct() {
436 let ep = EndpointBuilder::default()
437 .result_struct("Endpoint")
438 .build()
439 .unwrap();
440 assert_eq!(ep.route, "".to_string());
441 assert_eq!(ep.method, "GET".to_string());
442 assert_eq!(ep.result_struct, "Endpoint".to_string());
443 }
444}