1use std::collections::BTreeMap;
2use std::fmt::Write;
3use std::sync::Arc;
4
5use indexmap::IndexMap;
6use tinc_pb_prost::http_endpoint_options;
7
8use crate::codegen::cel::{CelExpression, CelExpressions};
9use crate::codegen::utils::{field_ident_from_str, get_common_import_path, type_ident_from_str};
10use crate::{ExternPaths, Mode};
11
12#[derive(Debug, Clone, PartialEq)]
13pub(crate) enum ProtoType {
14 Value(ProtoValueType),
15 Modified(ProtoModifiedValueType),
16}
17
18impl ProtoType {
19 pub(crate) fn value_type(&self) -> Option<&ProtoValueType> {
20 match self {
21 Self::Value(value) => Some(value),
22 Self::Modified(modified) => modified.value_type(),
23 }
24 }
25
26 pub(crate) fn nested(&self) -> bool {
27 matches!(
28 self,
29 Self::Modified(ProtoModifiedValueType::Map(_, _) | ProtoModifiedValueType::Repeated(_))
30 )
31 }
32}
33
34#[derive(Debug, Clone, PartialEq)]
35pub(crate) enum ProtoValueType {
36 String,
37 Bytes,
38 Int32,
39 Int64,
40 UInt32,
41 UInt64,
42 Float,
43 Double,
44 Bool,
45 WellKnown(ProtoWellKnownType),
46 Message(ProtoPath),
47 Enum(ProtoPath),
48}
49
50#[derive(Debug, Clone, PartialEq)]
51pub(crate) enum ProtoWellKnownType {
52 Timestamp,
53 Duration,
54 Struct,
55 Value,
56 Empty,
57 ListValue,
58 Any,
59}
60
61impl ProtoValueType {
62 #[cfg(feature = "prost")]
63 pub(crate) fn from_pb(ty: &prost_reflect::Kind) -> Self {
64 match ty {
65 prost_reflect::Kind::Double => ProtoValueType::Double,
66 prost_reflect::Kind::Float => ProtoValueType::Float,
67 prost_reflect::Kind::Int32 => ProtoValueType::Int32,
68 prost_reflect::Kind::Int64 => ProtoValueType::Int64,
69 prost_reflect::Kind::Uint32 => ProtoValueType::UInt32,
70 prost_reflect::Kind::Uint64 => ProtoValueType::UInt64,
71 prost_reflect::Kind::Sint32 => ProtoValueType::Int32,
72 prost_reflect::Kind::Sint64 => ProtoValueType::Int64,
73 prost_reflect::Kind::Fixed32 => ProtoValueType::Float,
74 prost_reflect::Kind::Fixed64 => ProtoValueType::Double,
75 prost_reflect::Kind::Sfixed32 => ProtoValueType::Float,
76 prost_reflect::Kind::Sfixed64 => ProtoValueType::Double,
77 prost_reflect::Kind::Bool => ProtoValueType::Bool,
78 prost_reflect::Kind::String => ProtoValueType::String,
79 prost_reflect::Kind::Bytes => ProtoValueType::Bytes,
80 prost_reflect::Kind::Message(message) => ProtoValueType::from_proto_path(message.full_name()),
81 prost_reflect::Kind::Enum(enum_) => ProtoValueType::Enum(ProtoPath::new(enum_.full_name())),
82 }
83 }
84
85 pub(crate) fn from_proto_path(path: &str) -> Self {
86 match path {
87 "google.protobuf.Timestamp" => ProtoValueType::WellKnown(ProtoWellKnownType::Timestamp),
88 "google.protobuf.Duration" => ProtoValueType::WellKnown(ProtoWellKnownType::Duration),
89 "google.protobuf.Struct" => ProtoValueType::WellKnown(ProtoWellKnownType::Struct),
90 "google.protobuf.Value" => ProtoValueType::WellKnown(ProtoWellKnownType::Value),
91 "google.protobuf.Empty" => ProtoValueType::WellKnown(ProtoWellKnownType::Empty),
92 "google.protobuf.ListValue" => ProtoValueType::WellKnown(ProtoWellKnownType::ListValue),
93 "google.protobuf.Any" => ProtoValueType::WellKnown(ProtoWellKnownType::Any),
94 "google.protobuf.BoolValue" => ProtoValueType::Bool,
95 "google.protobuf.Int32Value" => ProtoValueType::Int32,
96 "google.protobuf.Int64Value" => ProtoValueType::Int64,
97 "google.protobuf.UInt32Value" => ProtoValueType::UInt32,
98 "google.protobuf.UInt64Value" => ProtoValueType::UInt64,
99 "google.protobuf.FloatValue" => ProtoValueType::Float,
100 "google.protobuf.DoubleValue" => ProtoValueType::Double,
101 "google.protobuf.StringValue" => ProtoValueType::String,
102 "google.protobuf.BytesValue" => ProtoValueType::Bytes,
103 _ => ProtoValueType::Message(ProtoPath::new(path)),
104 }
105 }
106
107 pub(crate) fn proto_path(&self) -> &str {
108 match self {
109 ProtoValueType::WellKnown(ProtoWellKnownType::Timestamp) => "google.protobuf.Timestamp",
110 ProtoValueType::WellKnown(ProtoWellKnownType::Duration) => "google.protobuf.Duration",
111 ProtoValueType::WellKnown(ProtoWellKnownType::Struct) => "google.protobuf.Struct",
112 ProtoValueType::WellKnown(ProtoWellKnownType::Value) => "google.protobuf.Value",
113 ProtoValueType::WellKnown(ProtoWellKnownType::Empty) => "google.protobuf.Empty",
114 ProtoValueType::WellKnown(ProtoWellKnownType::ListValue) => "google.protobuf.ListValue",
115 ProtoValueType::WellKnown(ProtoWellKnownType::Any) => "google.protobuf.Any",
116 ProtoValueType::Bool => "google.protobuf.BoolValue",
117 ProtoValueType::Int32 => "google.protobuf.Int32Value",
118 ProtoValueType::Int64 => "google.protobuf.Int64Value",
119 ProtoValueType::UInt32 => "google.protobuf.UInt32Value",
120 ProtoValueType::UInt64 => "google.protobuf.UInt64Value",
121 ProtoValueType::Float => "google.protobuf.FloatValue",
122 ProtoValueType::Double => "google.protobuf.DoubleValue",
123 ProtoValueType::String => "google.protobuf.StringValue",
124 ProtoValueType::Bytes => "google.protobuf.BytesValue",
125 ProtoValueType::Enum(path) | ProtoValueType::Message(path) => path.as_ref(),
126 }
127 }
128}
129
130#[derive(Debug, Clone, PartialEq)]
131pub(crate) struct ProtoEnumType {
132 pub package: ProtoPath,
133 pub full_name: ProtoPath,
134 pub comments: Comments,
135 pub options: ProtoEnumOptions,
136 pub variants: IndexMap<String, ProtoEnumVariant>,
137}
138
139impl ProtoEnumType {
140 fn rust_path(&self, package: &str) -> syn::Path {
141 get_common_import_path(package, &self.full_name)
142 }
143}
144
145#[derive(Debug, Clone, PartialEq)]
146pub(crate) struct ProtoEnumOptions {
147 pub repr_enum: bool,
148}
149
150#[derive(Debug, Clone, PartialEq)]
151pub(crate) struct ProtoEnumVariant {
152 pub full_name: ProtoPath,
153 pub comments: Comments,
154 pub options: ProtoEnumVariantOptions,
155 pub rust_ident: syn::Ident,
156 pub value: i32,
157}
158
159#[derive(Debug, Clone, PartialEq)]
160pub(crate) struct ProtoEnumVariantOptions {
161 pub serde_name: String,
162 pub visibility: ProtoVisibility,
163}
164
165#[derive(Debug, Clone, PartialEq)]
166pub(crate) enum ProtoModifiedValueType {
167 Repeated(ProtoValueType),
168 Map(ProtoValueType, ProtoValueType),
169 Optional(ProtoValueType),
170 OneOf(ProtoOneOfType),
171}
172
173impl ProtoModifiedValueType {
174 pub(crate) fn value_type(&self) -> Option<&ProtoValueType> {
175 match self {
176 Self::Repeated(v) => Some(v),
177 Self::Map(_, v) => Some(v),
178 Self::Optional(v) => Some(v),
179 _ => None,
180 }
181 }
182}
183
184#[derive(Debug, Clone, PartialEq)]
185pub(crate) struct ProtoMessageType {
186 pub package: ProtoPath,
187 pub full_name: ProtoPath,
188 pub comments: Comments,
189 pub options: ProtoMessageOptions,
190 pub fields: IndexMap<String, ProtoMessageField>,
191}
192
193impl ProtoMessageType {
194 fn rust_path(&self, package: &str) -> syn::Path {
195 get_common_import_path(package, &self.full_name)
196 }
197}
198
199#[derive(Debug, Clone, PartialEq, Default)]
200pub(crate) struct ProtoMessageOptions {
201 pub cel: Vec<CelExpression>,
202}
203
204#[derive(Debug, Clone, PartialEq)]
205pub(crate) struct ProtoMessageField {
206 pub full_name: ProtoPath,
207 pub message: ProtoPath,
208 pub ty: ProtoType,
209 pub comments: Comments,
210 pub options: ProtoFieldOptions,
211}
212
213impl ProtoMessageField {
214 pub(crate) fn rust_ident(&self) -> syn::Ident {
215 field_ident_from_str(self.full_name.split('.').next_back().unwrap())
216 }
217}
218
219#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
220pub(crate) enum ProtoFieldSerdeOmittable {
221 True,
222 False,
223 TrueButStillSerialize,
224}
225
226impl ProtoFieldSerdeOmittable {
227 pub(crate) fn is_true(&self) -> bool {
228 matches!(self, Self::True | Self::TrueButStillSerialize)
229 }
230}
231
232#[derive(Debug, Clone, Copy, PartialEq)]
233pub(crate) enum ProtoVisibility {
234 Default,
235 Skip,
236 InputOnly,
237 OutputOnly,
238}
239
240impl ProtoVisibility {
241 pub(crate) fn from_pb(visibility: tinc_pb_prost::Visibility) -> Self {
242 match visibility {
243 tinc_pb_prost::Visibility::Skip => ProtoVisibility::Skip,
244 tinc_pb_prost::Visibility::InputOnly => ProtoVisibility::InputOnly,
245 tinc_pb_prost::Visibility::OutputOnly => ProtoVisibility::OutputOnly,
246 tinc_pb_prost::Visibility::Unspecified => ProtoVisibility::Default,
247 }
248 }
249
250 pub(crate) fn has_output(&self) -> bool {
251 matches!(self, ProtoVisibility::OutputOnly | ProtoVisibility::Default)
252 }
253
254 pub(crate) fn has_input(&self) -> bool {
255 matches!(self, ProtoVisibility::InputOnly | ProtoVisibility::Default)
256 }
257}
258
259#[derive(Debug, Clone, PartialEq)]
260pub(crate) struct ProtoFieldOptions {
261 pub serde_name: String,
262 pub serde_omittable: ProtoFieldSerdeOmittable,
263 pub nullable: bool,
264 pub flatten: bool,
265 pub visibility: ProtoVisibility,
266 pub cel_exprs: CelExpressions,
267}
268
269#[derive(Debug, Clone, PartialEq)]
270pub(crate) struct ProtoOneOfType {
271 pub full_name: ProtoPath,
272 pub message: ProtoPath,
273 pub options: ProtoOneOfOptions,
274 pub fields: IndexMap<String, ProtoOneOfField>,
275}
276
277impl ProtoOneOfType {
278 pub(crate) fn rust_path(&self, package: &str) -> syn::Path {
279 get_common_import_path(package, &self.full_name)
280 }
281}
282
283#[derive(Debug, Clone, PartialEq)]
284pub(crate) struct ProtoOneOfOptions {
285 pub tagged: Option<Tagged>,
286}
287
288#[derive(Debug, Clone, PartialEq)]
289pub(crate) struct Tagged {
290 pub tag: String,
291 pub content: String,
292}
293
294#[derive(Debug, Clone, PartialEq)]
295pub(crate) struct ProtoOneOfField {
296 pub full_name: ProtoPath,
297 pub message: ProtoPath,
298 pub comments: Comments,
299 pub ty: ProtoValueType,
300 pub options: ProtoFieldOptions,
301}
302
303impl ProtoOneOfField {
304 pub(crate) fn rust_ident(&self) -> syn::Ident {
305 type_ident_from_str(self.full_name.split('.').next_back().unwrap())
306 }
307}
308
309#[derive(Debug, Clone, PartialEq, PartialOrd, Ord, Eq, Hash)]
310pub(crate) struct ProtoPath(pub Arc<str>);
311
312impl ProtoPath {
313 pub(crate) fn trim_last_segment(&self) -> &str {
314 let (item, _) = self.0.rsplit_once('.').unwrap_or_default();
316 item
317 }
318}
319
320impl std::ops::Deref for ProtoPath {
321 type Target = str;
322
323 fn deref(&self) -> &Self::Target {
324 &self.0
325 }
326}
327
328impl AsRef<str> for ProtoPath {
329 fn as_ref(&self) -> &str {
330 &self.0
331 }
332}
333
334impl ProtoPath {
335 pub(crate) fn new(absolute: impl std::fmt::Display) -> Self {
336 Self(absolute.to_string().into())
337 }
338}
339
340impl PartialEq<&str> for ProtoPath {
341 fn eq(&self, other: &&str) -> bool {
342 &*self.0 == *other
343 }
344}
345
346impl PartialEq<str> for ProtoPath {
347 fn eq(&self, other: &str) -> bool {
348 &*self.0 == other
349 }
350}
351
352impl std::borrow::Borrow<str> for ProtoPath {
353 fn borrow(&self) -> &str {
354 &self.0
355 }
356}
357
358impl std::fmt::Display for ProtoPath {
359 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
360 f.write_str(&self.0)
361 }
362}
363
364#[derive(Debug, Clone, PartialEq)]
365pub(crate) struct ProtoService {
366 pub full_name: ProtoPath,
367 pub package: ProtoPath,
368 pub comments: Comments,
369 pub options: ProtoServiceOptions,
370 pub methods: IndexMap<String, ProtoServiceMethod>,
371}
372
373#[derive(Debug, Clone, PartialEq)]
374pub(crate) struct ProtoServiceOptions {
375 pub prefix: Option<String>,
376}
377
378#[derive(Debug, Clone, PartialEq)]
379pub(crate) enum ProtoServiceMethodIo {
380 Single(ProtoValueType),
381 Stream(ProtoValueType),
382}
383
384impl ProtoServiceMethodIo {
385 pub(crate) fn is_stream(&self) -> bool {
386 matches!(self, ProtoServiceMethodIo::Stream(_))
387 }
388
389 pub(crate) fn value_type(&self) -> &ProtoValueType {
390 match self {
391 ProtoServiceMethodIo::Single(ty) => ty,
392 ProtoServiceMethodIo::Stream(ty) => ty,
393 }
394 }
395}
396
397#[derive(Debug, Clone, PartialEq)]
398pub(crate) struct ProtoServiceMethod {
399 pub full_name: ProtoPath,
400 pub service: ProtoPath,
401 pub comments: Comments,
402 pub input: ProtoServiceMethodIo,
403 pub output: ProtoServiceMethodIo,
404 pub endpoints: Vec<ProtoServiceMethodEndpoint>,
405 pub cel: Vec<CelExpression>,
406}
407
408#[derive(Debug, Clone, PartialEq, Default)]
409pub(crate) struct Comments {
410 pub leading: Option<Arc<str>>,
411 pub detached: Arc<[Arc<str>]>,
412 pub trailing: Option<Arc<str>>,
413}
414
415impl Comments {
416 pub(crate) fn is_empty(&self) -> bool {
417 self.leading.is_none() && self.detached.is_empty() && self.trailing.is_none()
418 }
419}
420
421impl std::fmt::Display for Comments {
422 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
423 let mut newline = false;
424 if let Some(leading) = self.leading.as_ref() {
425 leading.trim().fmt(f)?;
426 newline = true;
427 }
428
429 for detached in self.detached.iter() {
430 if newline {
431 f.write_char('\n')?;
432 }
433 newline = true;
434 detached.trim().fmt(f)?;
435 }
436
437 if let Some(detached) = self.trailing.as_ref() {
438 if newline {
439 f.write_char('\n')?;
440 }
441 detached.trim().fmt(f)?;
442 }
443
444 Ok(())
445 }
446}
447
448#[derive(Debug, Clone, PartialEq)]
449pub(crate) struct ProtoServiceMethodEndpoint {
450 pub method: http_endpoint_options::Method,
451 pub request: Option<http_endpoint_options::Request>,
452 pub response: Option<http_endpoint_options::Response>,
453}
454
455#[derive(Debug, Clone)]
456pub(crate) struct ProtoTypeRegistry {
457 messages: BTreeMap<ProtoPath, ProtoMessageType>,
458 enums: BTreeMap<ProtoPath, ProtoEnumType>,
459 services: BTreeMap<ProtoPath, ProtoService>,
460 extern_paths: ExternPaths,
461 _mode: Mode,
462}
463
464impl ProtoTypeRegistry {
465 pub(crate) fn new(mode: Mode, extern_paths: ExternPaths) -> Self {
466 Self {
467 messages: BTreeMap::new(),
468 enums: BTreeMap::new(),
469 services: BTreeMap::new(),
470 extern_paths,
471 _mode: mode,
472 }
473 }
474
475 pub(crate) fn register_message(&mut self, message: ProtoMessageType) {
476 self.messages.insert(message.full_name.clone(), message);
477 }
478
479 pub(crate) fn register_enum(&mut self, enum_: ProtoEnumType) {
480 self.enums.insert(enum_.full_name.clone(), enum_);
481 }
482
483 pub(crate) fn register_service(&mut self, service: ProtoService) {
484 self.services.insert(service.full_name.clone(), service);
485 }
486
487 pub(crate) fn get_message(&self, full_name: &str) -> Option<&ProtoMessageType> {
488 self.messages.get(full_name)
489 }
490
491 pub(crate) fn get_enum(&self, full_name: &str) -> Option<&ProtoEnumType> {
492 self.enums.get(full_name)
493 }
494
495 pub(crate) fn get_service(&self, full_name: &str) -> Option<&ProtoService> {
496 self.services.get(full_name)
497 }
498
499 pub(crate) fn messages(&self) -> impl Iterator<Item = &ProtoMessageType> {
500 self.messages.values()
501 }
502
503 pub(crate) fn enums(&self) -> impl Iterator<Item = &ProtoEnumType> {
504 self.enums.values()
505 }
506
507 pub(crate) fn services(&self) -> impl Iterator<Item = &ProtoService> {
508 self.services.values()
509 }
510
511 pub(crate) fn resolve_rust_path(&self, package: &str, path: &str) -> Option<syn::Path> {
512 self.extern_paths
513 .resolve(path)
514 .or_else(|| Some(self.enums.get(path)?.rust_path(package)))
515 .or_else(|| Some(self.messages.get(path)?.rust_path(package)))
516 }
517
518 pub(crate) fn has_extern(&self, path: &str) -> bool {
519 self.extern_paths.contains(path)
520 }
521}