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