Skip to main content

protify/
file.rs

1use crate::*;
2use hashbrown::HashSet;
3
4/// Struct that represents a protobuf file and its contents.
5#[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
150/// Trait that can generate a [`ProtoFile`].
151///
152/// Implemented by the unit structs generated by the [`define_proto_file`] macro.
153pub 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/// The protobuf edition for a file.
168#[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/// HashSet wrapper for a file's imports. Skips insertion if the file is equal to the origin file.
186#[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}