Skip to main content

kinetics_parser/params/
endpoint.rs

1use crate::environment::{parse_environment, Environment};
2use http::Method;
3use serde::{Deserialize, Serialize};
4use syn::{
5    bracketed,
6    parse::{Parse, ParseStream},
7    punctuated::Punctuated,
8    token, Ident, LitBool, LitStr,
9};
10
11const ALLOWED_METHODS: [Method; 5] = [
12    Method::GET,
13    Method::POST,
14    Method::PUT,
15    Method::DELETE,
16    Method::PATCH,
17];
18
19#[derive(Debug, Clone, Serialize, Deserialize)]
20pub struct Endpoint {
21    pub name: Option<String>,
22    pub url_path: String,
23    pub environment: Environment,
24    pub is_disabled: Option<bool>,
25    pub methods: Vec<String>,
26}
27
28impl Parse for Endpoint {
29    fn parse(input: ParseStream) -> syn::Result<Self> {
30        let mut name = None;
31        let mut url_path = None;
32        let mut environment = None;
33        let mut is_disabled = None;
34        let mut methods = vec![];
35
36        while !input.is_empty() {
37            let ident_span = input.span();
38            let ident: Ident = input.parse()?;
39            input.parse::<token::Eq>()?;
40
41            match ident.to_string().as_str() {
42                "name" => {
43                    if name.is_some() {
44                        return Err(syn::Error::new(ident_span, "Duplicate attribute `name`"));
45                    }
46                    name = Some(input.parse::<LitStr>()?.value());
47                }
48                "url_path" => {
49                    if url_path.is_some() {
50                        return Err(syn::Error::new(
51                            ident_span,
52                            "Duplicate attribute `url_path`",
53                        ));
54                    }
55                    url_path = Some(input.parse::<LitStr>()?.value());
56                }
57                "environment" => {
58                    if environment.is_some() {
59                        return Err(syn::Error::new(
60                            ident_span,
61                            "Duplicate attribute `environment`",
62                        ));
63                    }
64                    environment = Some(parse_environment(input)?);
65                }
66                "is_disabled" => {
67                    if is_disabled.is_some() {
68                        return Err(syn::Error::new(
69                            ident_span,
70                            "Duplicate attribute `is_disabled`",
71                        ));
72                    }
73                    is_disabled = Some(input.parse::<LitBool>()?.value());
74                }
75                "methods" => {
76                    if !methods.is_empty() {
77                        return Err(syn::Error::new(ident_span, "Duplicate attribute `methods`"));
78                    }
79
80                    // Parse a list of methods with a comma delimiter, like: ["POST", "GET", ...]
81                    let content;
82                    bracketed!(content in input);
83                    let parsed = Punctuated::<LitStr, token::Comma>::parse_terminated(&content)?;
84
85                    methods = parsed
86                        .iter()
87                        .map(|item| LitStr::value(item).to_uppercase())
88                        .collect();
89
90                    for method in methods.iter() {
91                        match Method::from_bytes(method.as_bytes()) {
92                            Ok(method) => {
93                                if !ALLOWED_METHODS.contains(&method) {
94                                    let allowed = ALLOWED_METHODS
95                                        .iter()
96                                        .map(|m| m.as_str())
97                                        .collect::<Vec<&str>>()
98                                        .join(", ");
99
100                                    return Err(syn::Error::new(
101                                        ident_span,
102                                        format!(
103                                            "Unsupported method: {method}. Available: [{allowed}]",
104                                        ),
105                                    ));
106                                }
107                            }
108                            Err(err) => {
109                                return Err(syn::Error::new(
110                                    ident_span,
111                                    format!("Invalid method: {}", err),
112                                ))
113                            }
114                        }
115                    }
116                }
117
118                // Ignore unknown attributes
119                _ => {}
120            }
121
122            if !input.is_empty() {
123                input.parse::<token::Comma>()?;
124            }
125        }
126
127        Ok(Endpoint {
128            name,
129            url_path: url_path
130                .ok_or_else(|| input.error("Missing required attribute `url_path`"))?,
131            environment: environment.unwrap_or_default(),
132            methods,
133            is_disabled,
134        })
135    }
136}