1#![allow(dead_code)]
2
3use proc_macro2::{Delimiter, Group, Literal, Punct, Spacing, Span, TokenStream};
4use quote::TokenStreamExt;
5use syn::Ident;
6#[cfg(feature = "prost")]
7pub mod service_generator_chain;
8
9pub trait Service {
16 type Comment: AsRef<str>;
18
19 type Method: Method;
21
22 fn name(&self) -> &str;
24 fn package(&self) -> &str;
26 fn identifier(&self) -> &str;
28 fn methods(&self) -> &[Self::Method];
30 fn comment(&self) -> &[Self::Comment];
32}
33
34pub trait Method {
41 type Comment: AsRef<str>;
43
44 fn name(&self) -> &str;
46 fn identifier(&self) -> &str;
48 fn client_streaming(&self) -> bool;
50 fn server_streaming(&self) -> bool;
52 fn comment(&self) -> &[Self::Comment];
54 fn deprecated(&self) -> bool {
56 false
57 }
58 fn request_response_name(
60 &self,
61 proto_path: &str,
62 compile_well_known_types: bool,
63 ) -> (TokenStream, TokenStream);
64}
65
66#[derive(Debug, Default, Clone)]
68pub struct Attributes {
69 module: Vec<(String, String)>,
71 structure: Vec<(String, String)>,
73}
74
75impl Attributes {
76 pub fn for_mod(&self, name: &str) -> Vec<syn::Attribute> {
77 generate_attributes(name, &self.module)
78 }
79
80 pub fn for_struct(&self, name: &str) -> Vec<syn::Attribute> {
81 generate_attributes(name, &self.structure)
82 }
83
84 pub fn push_mod(&mut self, pattern: impl Into<String>, attr: impl Into<String>) {
94 self.module.push((pattern.into(), attr.into()));
95 }
96
97 pub fn push_struct(&mut self, pattern: impl Into<String>, attr: impl Into<String>) {
107 self.structure.push((pattern.into(), attr.into()));
108 }
109}
110
111pub(crate) fn format_service_name<T: Service>(service: &T, emit_package: bool) -> String {
112 let package = if emit_package { service.package() } else { "" };
113 format!(
114 "{}{}{}",
115 package,
116 if package.is_empty() { "" } else { "." },
117 service.identifier(),
118 )
119}
120
121pub(crate) fn format_method_path<T: Service>(
122 service: &T,
123 method: &T::Method,
124 emit_package: bool,
125) -> String {
126 format!(
127 "{}/{}",
128 format_service_name(service, emit_package),
129 method.identifier()
130 )
131}
132
133pub(crate) fn format_method_name<T: Service>(
134 service: &T,
135 method: &T::Method,
136 emit_package: bool,
137) -> String {
138 format!(
139 "{}.{}",
140 format_service_name(service, emit_package),
141 method.identifier()
142 )
143}
144
145pub(crate) fn generate_attributes<'a>(
147 name: &str,
148 attrs: impl IntoIterator<Item = &'a (String, String)>,
149) -> Vec<syn::Attribute> {
150 attrs
151 .into_iter()
152 .filter(|(matcher, _)| match_name(matcher, name))
153 .flat_map(|(_, attr)| {
154 syn::parse_str::<syn::DeriveInput>(&format!("{}\nstruct fake;", attr))
156 .unwrap()
157 .attrs
158 })
159 .collect::<Vec<_>>()
160}
161
162pub(crate) fn generate_deprecated() -> TokenStream {
163 let mut deprecated_stream = TokenStream::new();
164 deprecated_stream.append(Ident::new("deprecated", Span::call_site()));
165
166 let group = Group::new(Delimiter::Bracket, deprecated_stream);
167
168 let mut stream = TokenStream::new();
169 stream.append(Punct::new('#', Spacing::Alone));
170 stream.append(group);
171
172 stream
173}
174
175pub(crate) fn generate_doc_comment<S: AsRef<str>>(comment: S) -> TokenStream {
177 let comment = comment.as_ref();
178
179 let comment = if !comment.starts_with(' ') {
180 format!(" {}", comment)
181 } else {
182 comment.to_string()
183 };
184
185 let mut doc_stream = TokenStream::new();
186
187 doc_stream.append(Ident::new("doc", Span::call_site()));
188 doc_stream.append(Punct::new('=', Spacing::Alone));
189 doc_stream.append(Literal::string(comment.as_ref()));
190
191 let group = Group::new(Delimiter::Bracket, doc_stream);
192
193 let mut stream = TokenStream::new();
194 stream.append(Punct::new('#', Spacing::Alone));
195 stream.append(group);
196 stream
197}
198
199pub(crate) fn generate_doc_comments<T: AsRef<str>>(comments: &[T]) -> TokenStream {
201 let mut stream = TokenStream::new();
202
203 for comment in comments {
204 stream.extend(generate_doc_comment(comment));
205 }
206
207 stream
208}
209
210pub(crate) fn match_name(pattern: &str, path: &str) -> bool {
212 if pattern.is_empty() {
213 false
214 } else if pattern == "." || pattern == path {
215 true
216 } else {
217 let pattern_segments = pattern.split('.').collect::<Vec<_>>();
218 let path_segments = path.split('.').collect::<Vec<_>>();
219
220 if &pattern[..1] == "." {
221 if pattern_segments.len() > path_segments.len() {
223 false
224 } else {
225 pattern_segments[..] == path_segments[..pattern_segments.len()]
226 }
227 } else if pattern_segments.len() > path_segments.len() {
229 false
230 } else {
231 pattern_segments[..] == path_segments[path_segments.len() - pattern_segments.len()..]
232 }
233 }
234}
235
236pub(crate) fn naive_snake_case(name: &str) -> String {
237 let mut s = String::new();
238 let mut it = name.chars().peekable();
239
240 while let Some(x) = it.next() {
241 s.push(x.to_ascii_lowercase());
242 if let Some(y) = it.peek() {
243 if y.is_uppercase() {
244 s.push('_');
245 }
246 }
247 }
248
249 s
250}