unc_rpc_error_macro/
lib.rs1use std::cell::RefCell;
2use std::collections::BTreeMap;
3
4use proc_macro::TokenStream;
5use serde::{Deserialize, Serialize};
6#[cfg(feature = "dump_errors_schema")]
7use serde_json::Value;
8use syn::{parse_macro_input, DeriveInput};
9
10use unc_rpc_error_core::{parse_error_type, ErrorType};
11
12thread_local!(static SCHEMA: RefCell<Schema> = RefCell::new(Schema::default()));
13
14#[derive(Default, Debug, Deserialize, Serialize)]
15struct Schema {
16 pub schema: BTreeMap<String, ErrorType>,
17}
18
19#[cfg(feature = "dump_errors_schema")]
20fn merge(a: &mut Value, b: &Value) {
21 match (a, b) {
22 (&mut Value::Object(ref mut a), &Value::Object(ref b)) => {
23 for (k, v) in b {
24 merge(a.entry(k.clone()).or_insert(Value::Null), v);
25 }
26 }
27 (a, b) => {
28 *a = b.clone();
29 }
30 }
31}
32
33#[cfg(feature = "dump_errors_schema")]
34impl Drop for Schema {
35 fn drop(&mut self) {
46 use fs2::FileExt;
47 use std::fs::File;
48 use std::io::{Read, Seek, SeekFrom, Write};
49
50 struct Guard {
51 file: File,
52 }
53 impl Guard {
54 fn new(path: &str) -> Self {
55 let file = File::options()
56 .read(true)
57 .write(true)
58 .create_new(true)
59 .open(path)
60 .or_else(|_| File::options().read(true).write(true).open(path))
61 .unwrap_or_else(|err| panic!("can't open {path}: {err}"));
62 file.lock_exclusive().unwrap_or_else(|err| panic!("can't lock {path}: {err}"));
63 Guard { file }
64 }
65 }
66 impl Drop for Guard {
67 fn drop(&mut self) {
68 let _ = self.file.unlock();
69 }
70 }
71
72 let schema_json = serde_json::to_value(self).expect("Schema serialize failed");
73
74 let filename = "./target/rpc_errors_schema.json";
76 let mut guard = Guard::new(filename);
77
78 let existing_schema: Option<Value> = {
79 let mut buf = Vec::new();
80 guard
81 .file
82 .read_to_end(&mut buf)
83 .unwrap_or_else(|err| panic!("can't read {filename}: {err}"));
84 if buf.is_empty() {
85 None
86 } else {
87 let json = serde_json::from_slice(&buf)
88 .unwrap_or_else(|err| panic!("can't deserialize {filename}: {err}"));
89 Some(json)
90 }
91 };
92
93 let new_schema_json = match existing_schema {
94 None => schema_json,
95 Some(mut existing_schema) => {
96 merge(&mut existing_schema, &schema_json);
97 existing_schema
98 }
99 };
100
101 let new_schema_json_string = serde_json::to_string_pretty(&new_schema_json)
102 .expect("error schema serialization failed");
103
104 guard.file.set_len(0).unwrap_or_else(|err| panic!("can't truncate {filename}: {err}"));
105 guard
106 .file
107 .seek(SeekFrom::Start(0))
108 .unwrap_or_else(|err| panic!("can't seek {filename}: {err}"));
109 guard
110 .file
111 .write_all(new_schema_json_string.as_bytes())
112 .unwrap_or_else(|err| panic!("can't write {filename}: {err}"));
113 }
114}
115
116#[proc_macro_derive(RpcError)]
117pub fn rpc_error(input: TokenStream) -> TokenStream {
118 let input = parse_macro_input!(input as DeriveInput);
119
120 SCHEMA.with(|s| {
121 parse_error_type(&mut s.borrow_mut().schema, &input);
122 });
123 TokenStream::new()
124}