1use crate::*;
2use hashbrown::HashSet;
3
4#[derive(Debug, PartialEq)]
6#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
7#[cfg_attr(feature = "std", derive(Template))]
8#[cfg_attr(feature = "std", template(path = "file.proto.j2"))]
9#[non_exhaustive]
10pub struct ProtoFile {
11 pub name: FixedStr,
12 pub package: FixedStr,
13 pub imports: FileImports,
14 pub messages: Vec<MessageSchema>,
15 pub enums: Vec<EnumSchema>,
16 pub options: Vec<ProtoOption>,
17 pub edition: Edition,
18 pub services: Vec<Service>,
19 pub extensions: Vec<Extension>,
20}
21
22impl ProtoFile {
23 pub(crate) fn sort_items(&mut self) {
24 self.extensions
25 .sort_unstable_by_key(|e| e.target.as_str());
26
27 self.messages
28 .sort_unstable_by_key(|m| m.name.clone());
29
30 for msg in self.messages.iter_mut() {
31 sort_nested(msg);
32 }
33
34 self.enums
35 .sort_unstable_by_key(|e| e.short_name.clone());
36 self.services
37 .sort_unstable_by_key(|s| s.name.clone());
38 }
39
40 #[doc(hidden)]
41 #[must_use]
42 pub fn new(name: &'static str, package: &'static str) -> Self {
43 Self {
44 name: name.into(),
45 package: package.into(),
46 imports: FileImports::new(name),
47 messages: Default::default(),
48 enums: Default::default(),
49 options: Default::default(),
50 edition: Default::default(),
51 services: Default::default(),
52 extensions: Default::default(),
53 }
54 }
55
56 #[doc(hidden)]
57 #[inline]
58 pub fn with_options(&mut self, options: Vec<ProtoOption>) -> &mut Self {
59 self.options = options;
60 self
61 }
62
63 #[doc(hidden)]
64 #[inline]
65 pub fn with_imports(
66 &mut self,
67 imports: impl IntoIterator<Item = impl Into<FixedStr>>,
68 ) -> &mut Self {
69 self.imports
70 .extend(imports.into_iter().map(|s| s.into()));
71 self
72 }
73
74 #[doc(hidden)]
75 #[inline]
76 pub const fn with_edition(&mut self, edition: Edition) -> &mut Self {
77 self.edition = edition;
78 self
79 }
80
81 #[doc(hidden)]
82 pub fn with_messages(&mut self, mut messages: Vec<MessageSchema>) -> &mut Self {
83 for message in &mut messages {
84 message.register_imports(&mut self.imports);
85 message.file = self.name.clone();
86 }
87
88 self.messages.append(&mut messages);
89
90 self
91 }
92
93 #[doc(hidden)]
94 #[inline]
95 pub fn with_enums(&mut self, mut enums: Vec<EnumSchema>) -> &mut Self {
96 for enum_ in &mut enums {
97 enum_.file = self.name.clone();
98 }
99
100 self.enums.append(&mut enums);
101
102 self
103 }
104
105 #[doc(hidden)]
106 pub fn with_services(&mut self, mut services: Vec<Service>) -> &mut Self {
107 for service in &services {
108 for (request, response) in service
109 .handlers
110 .iter()
111 .map(|h| (&h.request, &h.response))
112 {
113 self.imports.insert_from_path(&request.message);
114 self.imports.insert_from_path(&response.message);
115 }
116
117 if *service.file != *self.name {
118 self.imports.set.insert(service.file.clone());
119 }
120 }
121
122 self.services.append(&mut services);
123
124 self
125 }
126
127 #[doc(hidden)]
128 pub fn with_extensions(&mut self, mut extensions: Vec<Extension>) -> &mut Self {
129 if !extensions.is_empty() {
130 self.imports
131 .set
132 .insert("google/protobuf/descriptor.proto".into());
133 }
134
135 self.extensions.append(&mut extensions);
136
137 self
138 }
139}
140
141fn sort_nested(message: &mut MessageSchema) {
142 message
143 .messages
144 .sort_unstable_by_key(|m| m.name.clone());
145 message
146 .enums
147 .sort_unstable_by_key(|e| e.name.clone());
148}
149
150pub trait FileSchema {
154 const NAME: &str;
155 const PACKAGE: &str;
156 const EXTERN_PATH: &str;
157 fn file_schema() -> ProtoFile;
158}
159
160#[doc(hidden)]
161pub struct FileReference {
162 pub name: &'static str,
163 pub package: &'static str,
164 pub extern_path: &'static str,
165}
166
167#[derive(Default, Debug, PartialEq, Eq, Clone, Copy)]
169#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
170pub enum Edition {
171 #[default]
172 Proto3,
173 E2023,
174}
175
176impl Display for Edition {
177 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
178 match self {
179 Self::Proto3 => write!(f, "syntax = \"proto3\""),
180 Self::E2023 => write!(f, "edition = \"2023\""),
181 }
182 }
183}
184
185#[derive(PartialEq, Eq, Debug)]
187#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
188#[non_exhaustive]
189pub struct FileImports {
190 pub set: HashSet<FixedStr>,
191 pub file: FixedStr,
192 #[cfg_attr(feature = "serde", serde(skip))]
193 pub(crate) added_validate_proto: bool,
194}
195
196impl Extend<FixedStr> for FileImports {
197 fn extend<T: IntoIterator<Item = FixedStr>>(&mut self, iter: T) {
198 let iter = iter.into_iter();
199
200 let reserve = if self.set.is_empty() {
201 iter.size_hint().0
202 } else {
203 iter.size_hint().0.div_ceil(2)
204 };
205
206 self.set.reserve(reserve);
207
208 for import in iter {
209 self.insert_internal(import);
210 }
211 }
212}
213
214impl IntoIterator for FileImports {
215 type Item = FixedStr;
216 type IntoIter = hashbrown::hash_set::IntoIter<FixedStr>;
217
218 fn into_iter(self) -> Self::IntoIter {
219 self.set.into_iter()
220 }
221}
222
223impl FileImports {
224 #[must_use]
225 pub(crate) fn new(file: impl Into<FixedStr>) -> Self {
226 Self {
227 file: file.into(),
228 set: HashSet::default(),
229 added_validate_proto: false,
230 }
231 }
232
233 pub(crate) fn insert_validate_proto(&mut self) {
234 if !self.added_validate_proto {
235 self.set
236 .insert("buf/validate/validate.proto".into());
237 self.added_validate_proto = true;
238 }
239 }
240
241 pub(crate) fn insert_internal(&mut self, import: FixedStr) {
242 if *import != *self.file {
243 if import == "buf/validate/validate.proto" {
244 self.insert_validate_proto();
245 } else {
246 self.set.insert(import);
247 }
248 }
249 }
250
251 pub(crate) fn insert_from_path(&mut self, path: &ProtoPath) {
252 if path.file != self.file {
253 self.set.insert(path.file.clone());
254 }
255 }
256
257 #[must_use]
258 pub(crate) fn as_sorted_vec(&self) -> Vec<&FixedStr> {
259 let mut imports: Vec<&FixedStr> = self.set.iter().collect();
260
261 imports.sort_unstable();
262
263 imports
264 }
265}