architect_api_schema_builder/
manual.rs1use crate::code_gen::CodeGenBuilder;
37use proc_macro2::TokenStream;
38use quote::quote;
39use std::{
40 fs,
41 path::{Path, PathBuf},
42};
43use tonic_build::{manual, Service};
44
45struct ServiceGenerator {
46 definitions: TokenStream,
48}
49
50impl ServiceGenerator {
51 fn generate(&mut self, service: &manual::Service, rewrite_crate: &str) {
52 let definition = CodeGenBuilder::new()
53 .emit_package(true)
54 .compile_well_known_types(false)
55 .generate_server_definition(service, rewrite_crate, "");
56
57 self.definitions.extend(definition);
58 }
59
60 fn finalize(&mut self, buf: &mut String) {
61 if !self.definitions.is_empty() {
62 let definitions = &self.definitions;
63
64 let server_definitions = quote::quote! {
65 #definitions
66 };
67
68 let ast: syn::File =
69 syn::parse2(server_definitions).expect("not a valid tokenstream");
70 let code = prettyplease::unparse(&ast);
71 buf.push_str(&code);
72
73 self.definitions = TokenStream::default();
74 }
75 }
76}
77
78#[derive(Debug, Default)]
80pub struct Builder {
81 rewrite_crate_name: Option<String>,
82 out_dir: Option<PathBuf>,
83 emit_composite_package: bool,
84}
85
86impl Builder {
87 pub fn new() -> Self {
89 Self::default()
90 }
91
92 pub fn rewrite_crate(mut self, crate_name: &str) -> Self {
95 self.rewrite_crate_name = Some(crate_name.to_string());
96 self
97 }
98
99 pub fn out_dir(mut self, out_dir: impl AsRef<Path>) -> Self {
103 self.out_dir = Some(out_dir.as_ref().to_path_buf());
104 self
105 }
106
107 pub fn emit_composite_package(mut self, emit_composite_package: bool) -> Self {
111 self.emit_composite_package = emit_composite_package;
112 self
113 }
114
115 pub fn compile(self, services: &[&manual::Service]) {
120 let out_dir = if let Some(out_dir) = self.out_dir.as_ref() {
121 fs::create_dir_all(out_dir).unwrap_or_else(|_| {
122 panic!("failed to create out dir: {}", out_dir.display())
123 });
124 out_dir.clone()
125 } else {
126 PathBuf::from(std::env::var("OUT_DIR").unwrap())
127 };
128
129 let rewrite_crate_name = if let Some(name) = self.rewrite_crate_name.as_ref() {
131 name
132 } else {
133 "crate"
134 };
135
136 let mut generator = ServiceGenerator {
137 definitions: TokenStream::default(),
139 };
140
141 for service in services {
142 let mut output = String::new();
143 generator.generate(service, rewrite_crate_name);
144 generator.finalize(&mut output);
145
146 let out_file = out_dir.join(out_file(service));
147 fs::write(&out_file, output)
148 .unwrap_or_else(|_| panic!("failed to write: {}", out_file.display()));
149 }
150
151 if self.emit_composite_package {
152 let out_file = out_dir.join("packages.sdk.rs");
153 let output = generate_composite_package(services);
154 let ast = syn::parse2(output).unwrap();
155 let code = prettyplease::unparse(&ast);
156
157 fs::write(&out_file, code)
158 .unwrap_or_else(|_| panic!("failed to write: {}", out_file.display()));
159 }
160 }
161}
162
163fn out_file(service: &manual::Service) -> String {
164 format!("{}.{}.sdk.rs", service.package(), service.name())
165}
166
167fn generate_composite_package(services: &[&manual::Service]) -> TokenStream {
168 let mut includes = TokenStream::new();
169 let mut rpc_calls = TokenStream::new();
170
171 for service in services {
172 let service_name = syn::Lit::Str(syn::LitStr::new(
173 &out_file(service),
174 proc_macro2::Span::call_site(),
175 ));
176 includes.extend(quote! {
177 include!(#service_name);
178 });
179
180 let call = crate::server::server_fn_ident(service.name());
181 rpc_calls.extend(quote! { #call(), });
182 }
183
184 let rpcs = quote! {
185 vec![#rpc_calls]
186 };
187
188 quote! {
189 #includes
190
191 pub fn definitions() -> Vec<schema_builder::code_gen_types::SdkGeneratorStruct> {
192 #rpcs
193 }
194 }
195}