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