dubbo_build/
client.rs

1/*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements.  See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License.  You may obtain a copy of the License at
8 *
9 *     http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18use super::{generate_doc_comments, naive_snake_case, Attributes};
19use crate::{Method, Service};
20use proc_macro2::TokenStream;
21use quote::{format_ident, quote};
22
23/// Generate service for client.
24///
25/// This takes some `Service` and will generate a `TokenStream` that contains
26/// a public module with the generated client.
27pub fn generate<T: Service>(
28    service: &T,
29    emit_package: bool,
30    proto_path: &str,
31    compile_well_known_types: bool,
32    attributes: &Attributes,
33) -> TokenStream {
34    let service_ident = quote::format_ident!("{}Client", service.name());
35    let client_mod = quote::format_ident!("{}_client", naive_snake_case(service.name()));
36    let methods = generate_methods(service, emit_package, proto_path, compile_well_known_types);
37
38    let service_doc = generate_doc_comments(service.comment());
39
40    let package = if emit_package { service.package() } else { "" };
41    let path = format!(
42        "{}{}{}",
43        package,
44        if package.is_empty() { "" } else { "." },
45        service.identifier()
46    );
47
48    let mod_attributes = attributes.for_mod(package);
49    let struct_attributes = attributes.for_struct(&path);
50
51    quote! {
52        /// Generated client implementations.
53        #(#mod_attributes)*
54        pub mod #client_mod {
55            #![allow(
56                unused_variables,
57                dead_code,
58                missing_docs,
59                // will trigger if compression is disabled
60                clippy::let_unit_value,
61            )]
62            use dubbo::codegen::*;
63
64            #service_doc
65            #(#struct_attributes)*
66            #[derive(Clone)]
67            pub struct #service_ident {
68                inner: TripleClient,
69            }
70
71            impl #service_ident {
72                pub fn connect(host: String) -> Self {
73                    let cli = TripleClient::connect(host);
74                    #service_ident {
75                        inner: cli,
76                    }
77                }
78
79                pub fn new(builder: ClientBuilder) -> Self {
80                    Self {
81                        inner: TripleClient::new(builder),
82                    }
83                }
84
85                #methods
86
87            }
88        }
89    }
90}
91
92fn generate_methods<T: Service>(
93    service: &T,
94    emit_package: bool,
95    proto_path: &str,
96    compile_well_known_types: bool,
97) -> TokenStream {
98    let mut stream = TokenStream::new();
99    let package = if emit_package { service.package() } else { "" };
100
101    for method in service.methods() {
102        let service_unique_name = format!(
103            "{}{}{}",
104            package,
105            if package.is_empty() { "" } else { "." },
106            service.identifier()
107        );
108        let path = format!(
109            "/{}{}{}/{}",
110            package,
111            if package.is_empty() { "" } else { "." },
112            service.identifier(),
113            method.identifier()
114        );
115
116        stream.extend(generate_doc_comments(method.comment()));
117
118        let method = match (method.client_streaming(), method.server_streaming()) {
119            (false, false) => generate_unary(
120                service_unique_name,
121                &method,
122                proto_path,
123                compile_well_known_types,
124                path,
125            ),
126            (false, true) => generate_server_streaming(
127                service_unique_name,
128                &method,
129                proto_path,
130                compile_well_known_types,
131                path,
132            ),
133            (true, false) => generate_client_streaming(
134                service_unique_name,
135                &method,
136                proto_path,
137                compile_well_known_types,
138                path,
139            ),
140            (true, true) => generate_streaming(
141                service_unique_name,
142                &method,
143                proto_path,
144                compile_well_known_types,
145                path,
146            ),
147        };
148
149        stream.extend(method);
150    }
151
152    stream
153}
154
155fn generate_unary<T: Method>(
156    service_unique_name: String,
157    method: &T,
158    proto_path: &str,
159    compile_well_known_types: bool,
160    path: String,
161) -> TokenStream {
162    let ident = format_ident!("{}", method.name());
163    let (request, response) = method.request_response_name(proto_path, compile_well_known_types);
164    let method_name = method.identifier();
165
166    quote! {
167        pub async fn #ident(
168            &mut self,
169            request: Request<#request>,
170        ) -> Result<Response<#response>, dubbo::status::Status> {
171           let invocation = RpcInvocation::default()
172            .with_service_unique_name(String::from(#service_unique_name))
173            .with_method_name(String::from(#method_name));
174           let path = http::uri::PathAndQuery::from_static(#path);
175           self.inner.unary(
176                request,
177                path,
178                invocation,
179            ).await
180        }
181    }
182}
183
184fn generate_server_streaming<T: Method>(
185    service_unique_name: String,
186    method: &T,
187    proto_path: &str,
188    compile_well_known_types: bool,
189    path: String,
190) -> TokenStream {
191    let ident = format_ident!("{}", method.name());
192    let (request, response) = method.request_response_name(proto_path, compile_well_known_types);
193    let method_name = method.identifier();
194
195    quote! {
196        pub async fn #ident(
197            &mut self,
198            request: Request<#request>,
199        ) -> Result<Response<Decoding<#response>>, dubbo::status::Status> {
200            let invocation = RpcInvocation::default()
201             .with_service_unique_name(String::from(#service_unique_name))
202             .with_method_name(String::from(#method_name));
203            let path = http::uri::PathAndQuery::from_static(#path);
204            self.inner.server_streaming(
205                request,
206                path,
207                invocation,
208            ).await
209        }
210    }
211}
212
213fn generate_client_streaming<T: Method>(
214    service_unique_name: String,
215    method: &T,
216    proto_path: &str,
217    compile_well_known_types: bool,
218    path: String,
219) -> TokenStream {
220    let ident = format_ident!("{}", method.name());
221    let (request, response) = method.request_response_name(proto_path, compile_well_known_types);
222    let method_name = method.identifier();
223
224    quote! {
225        pub async fn #ident(
226            &mut self,
227            request: impl IntoStreamingRequest<Message = #request>
228        ) -> Result<Response<#response>, dubbo::status::Status> {
229            let invocation = RpcInvocation::default()
230             .with_service_unique_name(String::from(#service_unique_name))
231             .with_method_name(String::from(#method_name));
232            let path = http::uri::PathAndQuery::from_static(#path);
233            self.inner.client_streaming(
234                request,
235                path,
236                invocation,
237            ).await
238        }
239    }
240}
241
242fn generate_streaming<T: Method>(
243    service_unique_name: String,
244    method: &T,
245    proto_path: &str,
246    compile_well_known_types: bool,
247    path: String,
248) -> TokenStream {
249    let ident = format_ident!("{}", method.name());
250    let (request, response) = method.request_response_name(proto_path, compile_well_known_types);
251    let method_name = method.identifier();
252
253    quote! {
254        pub async fn #ident(
255            &mut self,
256            request: impl IntoStreamingRequest<Message = #request>
257        ) -> Result<Response<Decoding<#response>>, dubbo::status::Status> {
258            let invocation = RpcInvocation::default()
259             .with_service_unique_name(String::from(#service_unique_name))
260             .with_method_name(String::from(#method_name));
261            let path = http::uri::PathAndQuery::from_static(#path);
262            self.inner.bidi_streaming(
263                request,
264                path,
265                invocation,
266            ).await
267        }
268    }
269}