1use std::collections::BTreeMap;
2use syn::{Data, DataEnum, DataStruct, DeriveInput, Fields, FieldsNamed, FieldsUnnamed};
3
4#[derive(Default, Debug, serde::Deserialize, serde::Serialize)]
5pub struct ErrorType {
6 pub name: String,
8 pub subtypes: Vec<String>,
10 pub props: BTreeMap<String, String>,
12}
13
14fn parse_rpc_error_variant(input: &DeriveInput) -> String {
15 let type_name = input.ident.to_string();
16 let type_kind: Vec<&str> = type_name.split("Kind").collect();
17 type_kind[0].to_string()
18}
19
20fn error_type_name(schema: &mut BTreeMap<String, ErrorType>, name: String) -> &mut ErrorType {
21 let error_type = ErrorType { name: name.clone(), ..Default::default() };
22 schema.entry(name).or_insert(error_type)
23}
24
25pub fn parse_error_type(schema: &mut BTreeMap<String, ErrorType>, input: &DeriveInput) {
26 let name = parse_rpc_error_variant(input);
27 match &input.data {
28 Data::Enum(DataEnum { ref variants, .. }) => {
29 let error_type = error_type_name(schema, name);
31 let mut direct_error_types = vec![];
32 for variant in variants {
33 error_type.subtypes.push(variant.ident.to_string());
34 match &variant.fields {
35 Fields::Unnamed(FieldsUnnamed { ref unnamed, .. }) => {
36 if unnamed.iter().count() > 1 {
38 panic!(
39 "Error types doesn't support tuple variants with multiple fields"
40 );
41 }
42 }
43 Fields::Named(FieldsNamed { ref named, .. }) => {
44 let mut error_type =
46 ErrorType { name: variant.ident.to_string(), ..Default::default() };
47 for field in named {
48 error_type.props.insert(
49 field
50 .ident
51 .as_ref()
52 .expect("named fields must have ident")
53 .to_string(),
54 "".to_owned(),
55 );
56 }
57 direct_error_types.push(error_type);
58 }
59 Fields::Unit => {
60 direct_error_types.push(ErrorType {
61 name: variant.ident.to_string(),
62 ..Default::default()
63 });
64 }
65 }
66 }
67 for e in direct_error_types {
68 let error_type = error_type_name(schema, e.name.clone());
69 error_type.name = e.name;
70 error_type.props = e.props;
71 }
72 }
73 Data::Struct(DataStruct { ref fields, .. }) => {
74 let error_type = error_type_name(schema, name);
75 match fields {
76 Fields::Named(FieldsNamed { ref named, .. }) => {
77 for field in named {
78 let field_name =
79 field.ident.as_ref().expect("named fields must have ident").to_string();
80 if field_name == "kind" {
81 continue;
82 }
83 error_type.props.insert(field_name, "".to_owned()); }
85 }
86 _ => {
87 panic!("RpcError supports structs with the named fields only");
88 }
89 }
90 }
91 Data::Union(_) => {
92 panic!("Unions are not supported");
93 }
94 }
95}
96
97#[cfg(test)]
98mod tests {
99 use super::*;
100 use quote::quote;
101 #[test]
102 fn should_merge_kind() {
103 let mut schema = BTreeMap::default();
104 let error_type = syn::parse2(quote! {
105 pub struct ActionError {
106 pub index: Option<u64>,
107 pub kind: ActionErrorKind,
108 }
109 })
110 .unwrap();
111 parse_error_type(&mut schema, &error_type);
112 let expected: BTreeMap<String, ErrorType> = serde_json::from_str(
113 r#"
114 {
115 "ActionError": {
116 "name": "ActionError",
117 "subtypes": [],
118 "props": {
119 "index": ""
120 }
121 }
122 }
123 "#,
124 )
125 .unwrap();
126 assert_eq!(
127 serde_json::to_string(&expected).unwrap(),
128 serde_json::to_string(&schema).unwrap()
129 );
130 let error_type_kind: DeriveInput = syn::parse2(quote! {
131 pub enum ActionErrorKind {
132 AccountAlreadyExists { account_id: String },
133 }
134 })
135 .unwrap();
136 let expected: BTreeMap<String, ErrorType> = serde_json::from_str(
137 r#"
138 {
139 "ActionError": {
140 "name": "ActionError",
141 "subtypes": ["AccountAlreadyExists"],
142 "props": {
143 "index": ""
144 }
145 },
146 "AccountAlreadyExists": {
147 "name": "AccountAlreadyExists",
148 "subtypes": [],
149 "props": {
150 "account_id": ""
151 }
152 }
153 }
154 "#,
155 )
156 .unwrap();
157 parse_error_type(&mut schema, &error_type_kind);
158 assert_eq!(
159 serde_json::to_string(&expected).unwrap(),
160 serde_json::to_string(&schema).unwrap()
161 );
162 }
163
164 #[test]
165 fn complex() {
166 let mut schema = BTreeMap::default();
167 parse_error_type(
168 &mut schema,
169 &syn::parse2(quote! {
170 pub enum TxExecutionError {
171 ActionError(ActionError),
172 InvalidTxError(InvalidTxError),
173 }
174 })
175 .unwrap(),
176 );
177 parse_error_type(
178 &mut schema,
179 &syn::parse2(quote! {
180 pub enum InvalidTxError {
181 InvalidAccessKeyError(InvalidAccessKeyError),
182 InvalidSignerId { signer_id: AccountId },
183 }
184 })
185 .unwrap(),
186 );
187 parse_error_type(
188 &mut schema,
189 &syn::parse2(quote! {
190 pub enum InvalidAccessKeyError {
191 AccessKeyNotFound { account_id: AccountId, public_key: PublicKey },
193 }
194 })
195 .unwrap(),
196 );
197 parse_error_type(
198 &mut schema,
199 &syn::parse2(quote! {
200 pub struct ActionError {
201 pub index: Option<u64>,
202 pub kind: ActionErrorKind,
203 }
204 })
205 .unwrap(),
206 );
207 parse_error_type(
208 &mut schema,
209 &syn::parse2(quote! {
210 pub enum ActionErrorKind {
211 AccountAlreadyExists { account_id: String },
212 }
213 })
214 .unwrap(),
215 );
216 let expected: BTreeMap<String, ErrorType> = serde_json::from_str(
217 r#"
218 {
219 "AccessKeyNotFound": {
220 "name": "AccessKeyNotFound",
221 "subtypes": [],
222 "props": {
223 "account_id": "",
224 "public_key": ""
225 }
226 },
227 "AccountAlreadyExists": {
228 "name": "AccountAlreadyExists",
229 "subtypes": [],
230 "props": {
231 "account_id": ""
232 }
233 },
234 "ActionError": {
235 "name": "ActionError",
236 "subtypes": [
237 "AccountAlreadyExists"
238 ],
239 "props": {
240 "index": ""
241 }
242 },
243 "InvalidAccessKeyError": {
244 "name": "InvalidAccessKeyError",
245 "subtypes": [
246 "AccessKeyNotFound"
247 ],
248 "props": {}
249 },
250 "InvalidSignerId": {
251 "name": "InvalidSignerId",
252 "subtypes": [],
253 "props": {
254 "signer_id": ""
255 }
256 },
257 "InvalidTxError": {
258 "name": "InvalidTxError",
259 "subtypes": [
260 "InvalidAccessKeyError",
261 "InvalidSignerId"
262 ],
263 "props": {}
264 },
265 "TxExecutionError": {
266 "name": "TxExecutionError",
267 "subtypes": [
268 "ActionError",
269 "InvalidTxError"
270 ],
271 "props": {}
272 }
273 }"#,
274 )
275 .unwrap();
276 assert_eq!(
277 serde_json::to_string(&expected).unwrap(),
278 serde_json::to_string(&schema).unwrap()
279 );
280 }
281 #[test]
282 #[should_panic]
283 fn should_not_accept_tuples() {
284 let mut schema = BTreeMap::default();
285 parse_error_type(
286 &mut schema,
287 &syn::parse2(quote! {
288 pub enum ErrorWithATupleVariant {
289 Var(One, Two)
290 }
291 })
292 .unwrap(),
293 );
294 }
295}