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 std::env::var("DOCS_RS").is_ok() {
121 PathBuf::from(std::env::var("OUT_DIR").unwrap())
123 } else if let Some(out_dir) = self.out_dir.as_ref() {
124 fs::create_dir_all(out_dir).unwrap_or_else(|_| {
126 panic!("failed to create out dir: {}", out_dir.display())
127 });
128 out_dir.clone()
129 } else {
130 PathBuf::from(std::env::var("OUT_DIR").unwrap())
131 };
132 let rewrite_crate_name = if let Some(name) = self.rewrite_crate_name.as_ref() {
134 name
135 } else {
136 "crate"
137 };
138
139 let mut generator = ServiceGenerator {
140 definitions: TokenStream::default(),
142 };
143
144 for service in services {
145 let mut output = String::new();
146 generator.generate(service, rewrite_crate_name);
147 generator.finalize(&mut output);
148
149 let out_file = out_dir.join(out_file(service));
150 fs::write(&out_file, output)
151 .unwrap_or_else(|_| panic!("failed to write: {}", out_file.display()));
152 }
153
154 if self.emit_composite_package {
155 let out_file = out_dir.join("packages.sdk.rs");
156 let output = generate_composite_package(services);
157 let ast = syn::parse2(output).unwrap();
158 let code = prettyplease::unparse(&ast);
159
160 fs::write(&out_file, code)
161 .unwrap_or_else(|_| panic!("failed to write: {}", out_file.display()));
162 }
163 }
164}
165
166fn out_file(service: &manual::Service) -> String {
167 format!("{}.{}.sdk.rs", service.package(), service.name())
168}
169
170fn generate_composite_package(services: &[&manual::Service]) -> TokenStream {
171 let mut includes = TokenStream::new();
172 let mut rpc_calls = TokenStream::new();
173
174 for service in services {
175 let service_name = syn::Lit::Str(syn::LitStr::new(
176 &out_file(service),
177 proc_macro2::Span::call_site(),
178 ));
179 includes.extend(quote! {
180 include!(#service_name);
181 });
182
183 let call = crate::server::server_fn_ident(service.name());
184 rpc_calls.extend(quote! { #call(), });
185 }
186
187 let rpcs = quote! {
188 vec![#rpc_calls]
189 };
190
191 quote! {
192 #includes
193
194 pub fn definitions() -> Vec<schema_builder::code_gen_types::SdkGeneratorStruct> {
195 #rpcs
196 }
197 }
198}