tinc_build/
types.rs

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        // remove the last .<segment> from the path
315        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}