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