architect_api_schema_builder/
manual.rs

1//! This module provides utilities for generating `tonic` service definitions for use by our client
2//! sdk code generators.
3//!
4//! [2024-12-30] dkasten: fork of tonic-build/src/manual.rs at
5//! https://github.com/hyperium/tonic/commit/1c5150aaf62d6e72ce6c07966a9f19ceedb52702
6//!
7//! # Example
8//!
9//! ```rust,no_run
10//! fn main() -> Result<(), Box<dyn std::error::Error>> {
11//!     let greeter_service = tonic_build::manual::Service::builder()
12//!         .name("Greeter")
13//!         .package("helloworld")
14//!         .method(
15//!             tonic_build::manual::Method::builder()
16//!                 .name("say_hello")
17//!                 .route_name("SayHello")
18//!                 // Provide the path to the Request type
19//!                 .input_type("crate::HelloRequest")
20//!                 // Provide the path to the Response type
21//!                 .output_type("super::HelloResponse")
22//!                 // Provide the path to the Codec to use
23//!                 .codec_path("crate::JsonCodec")
24//!                 .build(),
25//!         )
26//!         .build();
27//!
28//!     // note we run first with a borrowed reference since tonic takes ownership
29//!     sdk_build::manual::Builder::new().compile(&[&greeter_service]);
30//!     tonic_build::manual::Builder::new().compile(&[greeter_service]);
31//!     Ok(())
32//! }
33//! ```
34// This module forked from https://github.com/hyperium/tonic/commit/1c5150aaf62d6e72ce6c07966a9f19ceedb52702
35
36use crate::code_gen::CodeGenBuilder;
37use proc_macro2::TokenStream;
38use std::{
39    fs,
40    path::{Path, PathBuf},
41};
42use tonic_build::{manual, Service};
43
44struct ServiceGenerator {
45    // builder: Builder,
46    definitions: TokenStream,
47}
48
49impl ServiceGenerator {
50    fn generate(&mut self, service: &manual::Service, rewrite_crate: &str) {
51        let definition = CodeGenBuilder::new()
52            .emit_package(true)
53            .compile_well_known_types(false)
54            .generate_server_definition(service, rewrite_crate, "");
55
56        self.definitions.extend(definition);
57    }
58
59    fn finalize(&mut self, buf: &mut String) {
60        if !self.definitions.is_empty() {
61            let definitions = &self.definitions;
62
63            let server_definitions = quote::quote! {
64                #definitions
65            };
66
67            let ast: syn::File =
68                syn::parse2(server_definitions).expect("not a valid tokenstream");
69            let code = prettyplease::unparse(&ast);
70            buf.push_str(&code);
71
72            self.definitions = TokenStream::default();
73        }
74    }
75}
76
77/// Service generator builder.
78#[derive(Debug, Default)]
79pub struct Builder {
80    rewrite_crate_name: Option<String>,
81    out_dir: Option<PathBuf>,
82}
83
84impl Builder {
85    /// Create a new Builder
86    pub fn new() -> Self {
87        Self::default()
88    }
89
90    /// Rewrite `crate::` references to provided crate
91    ///
92    pub fn rewrite_crate(mut self, crate_name: &str) -> Self {
93        self.rewrite_crate_name = Some(crate_name.to_string());
94        self
95    }
96
97    /// Set the output directory to generate code to.
98    ///
99    /// Defaults to the `OUT_DIR` environment variable.
100    pub fn out_dir(mut self, out_dir: impl AsRef<Path>) -> Self {
101        self.out_dir = Some(out_dir.as_ref().to_path_buf());
102        self
103    }
104
105    /// Performs code generation for the provided services.
106    ///
107    /// Generated services will be output into the directory specified by `out_dir`
108    /// with files named `<package_name>.<service_name>.sdk.rs`.
109    pub fn compile(self, services: &[&manual::Service]) {
110        let out_dir = if let Some(out_dir) = self.out_dir.as_ref() {
111            fs::create_dir_all(out_dir)
112                .expect(&format!("failed to create out dir: {}", out_dir.display()));
113            out_dir.clone()
114        } else {
115            PathBuf::from(std::env::var("OUT_DIR").unwrap())
116        };
117
118        let rewrite_crate_name = if let Some(name) = self.rewrite_crate_name.as_ref() {
119            name
120        } else {
121            "crate"
122        };
123
124        let mut generator = ServiceGenerator {
125            // builder: self,
126            definitions: TokenStream::default(),
127        };
128
129        for service in services {
130            let mut output = String::new();
131            generator.generate(service, rewrite_crate_name);
132            generator.finalize(&mut output);
133
134            let out_file =
135                out_dir.join(format!("{}.{}.sdk.rs", service.package(), service.name()));
136            fs::write(&out_file, output)
137                .expect(&format!("failed to write: {}", out_file.display()));
138        }
139    }
140}