conjure_macros/lib.rs
1// Copyright 2022 Palantir Technologies, Inc.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14//! Macros exposed by Conjure crates.
15//!
16//! Do not consume directly.
17#![warn(missing_docs)]
18
19use proc_macro::TokenStream;
20use syn::{Error, ItemTrait, TraitItem};
21
22mod client;
23mod derive_with;
24mod endpoints;
25mod log_safety;
26mod path;
27
28/// Creates a Conjure client type implementing the annotated trait.
29///
30/// For a trait named `MyService`, the macro will create a type named `MyServiceClient` which
31/// implements the Conjure `Service`/`AsyncService` and `MyService` traits.
32///
33/// The attribute has several parameters:
34///
35/// * `name` - The value of the `service` field in the `Endpoint` extension. Defaults to the trait's
36/// name.
37/// * `version` - The value of the `version` field in the `Endpoint` extension. Defaults to
38/// `Some(env!("CARGO_PKG_VERSION"))`.
39/// * `local` - For async clients, causes the generated struct to use the `LocalAsyncClient` APIs
40/// that don't have a `Send` bound.
41///
42/// # Parameters
43///
44/// The trait can optionally be declared generic over the request body and response writer types by
45/// using the `#[request_writer]` and `#[response_body]` annotations on the type parameters.
46///
47/// # Endpoints
48///
49/// Each method corresponds to a separate HTTP endpoint, and is expected to take `&self` and return
50/// `Result<T, Error>`. Each must be annotated with `#[endpoint]`, which has several
51/// parameters:
52///
53/// * `method` - The HTTP method (e.g. `GET`). Required.
54/// * `path` - The HTTP path template. Path parameters should be identified by `{name}` and must
55/// make up an entire path component. Required.
56/// * `name` - The value of the `name` field in the `Endpoint` extension. Defaults to the method's
57/// name.
58/// * `accept` - A type implementing `DeserializeResponse` which will be used to create the return
59/// value. Defaults to returning `()`.
60///
61/// Each method argument must have an annotation describing the type of parameter. One of:
62///
63/// * `#[path]` - A path parameter.
64///
65/// Parameters:
66/// * `name` - The name of the path template parameter. Defaults to the argument name.
67/// * `encoder` - A type implementing `EncodeParam` which will be used to encode the value into
68/// a string. Defaults to `DisplayParamEncoder`.
69/// * `#[query]` - A query parameter.
70///
71/// Parameters:
72/// * `name` - The string used as the key in the encoded URI. Required.
73/// * `encoder` - A type implementing `EncodeParam` which will be used to encode the value into
74/// a string. Defaults to `DisplayParamEncoder`.
75/// * `#[auth]` - A `BearerToken` used to authenticate the request. A method may only have at most
76/// one auth parameter.
77///
78/// Parameters:
79/// * `cookie_name` - The name of the cookie used if the token is to be passed via a `Cookie`
80/// header. If unset, it will be passed via an `Authorization` header instead.
81/// * `#[header]` - A header.
82///
83/// Parameters:
84/// * `name` - The header name. Required.
85/// * `encoder` - A type implementing `EncodeHeader` which will be used to encode the value
86/// into a header. Defaults to `DisplayHeaderEncoder`.
87/// * `#[body]` - The request body. A method may only have at most one body parameter.
88///
89/// Parameters:
90/// * `serializer` - A type implementing `SerializeRequest` which will be used to serialize the
91/// value into a body. Defaults to `StdRequestSerializer`.
92/// # Async
93///
94/// Both blocking and async clients are supported. For technical reasons, async method definitions
95/// will be rewritten by the macro to require the returned future be `Send` unless the `local` flag
96/// is set in the attribute.
97///
98/// # Examples
99///
100/// ```rust,ignore
101/// use conjure_error::Error;
102/// use conjure_http::{conjure_client, endpoint};
103/// use conjure_http::client::{
104/// AsyncClient, AsyncService, Client, ConjureRuntime, StdResponseDeserializer,
105/// DeserializeResponse, DisplaySeqEncoder, RequestBody, SerializeRequest, Service, WriteBody,
106/// };
107/// use conjure_object::BearerToken;
108/// use http::Response;
109/// use http::header::HeaderValue;
110/// use std::io::Write;
111/// use std::sync::Arc;
112///
113/// #[conjure_client]
114/// trait MyService {
115/// #[endpoint(method = GET, path = "/yaks/{yak_id}", accept = StdResponseDeserializer)]
116/// fn get_yak(&self, #[auth] auth: &BearerToken, #[path] yak_id: i32) -> Result<String, Error>;
117///
118/// #[endpoint(method = POST, path = "/yaks")]
119/// fn create_yak(
120/// &self,
121/// #[auth] auth_token: &BearerToken,
122/// #[query(name = "parentName", encoder = DisplaySeqEncoder)] parent_id: Option<&str>,
123/// #[body] yak: &str,
124/// ) -> Result<(), Error>;
125/// }
126///
127/// fn do_work(client: impl Client, runtime: &Arc<ConjureRuntime>, auth: &BearerToken) -> Result<(), Error> {
128/// let client = MyServiceClient::new(client, runtime);
129/// client.create_yak(auth, None, "my cool yak")?;
130///
131/// Ok(())
132/// }
133///
134/// #[conjure_client]
135/// trait MyServiceAsync {
136/// #[endpoint(method = GET, path = "/yaks/{yak_id}", accept = StdResponseDeserializer)]
137/// async fn get_yak(
138/// &self,
139/// #[auth] auth: &BearerToken,
140/// #[path] yak_id: i32,
141/// ) -> Result<String, Error>;
142///
143/// #[endpoint(method = POST, path = "/yaks")]
144/// async fn create_yak(
145/// &self,
146/// #[auth] auth_token: &BearerToken,
147/// #[query(name = "parentName", encoder = DisplaySeqEncoder)] parent_id: Option<&str>,
148/// #[body] yak: &str,
149/// ) -> Result<(), Error>;
150/// }
151///
152/// async fn do_work_async<C>(client: C, runtime: &Arc<ConjureRuntime>, auth: &BearerToken) -> Result<(), Error>
153/// where
154/// C: AsyncClient + Sync + Send,
155/// C::ResponseBody: 'static + Send,
156/// {
157/// let client = MyServiceAsyncClient::new(client, runtime);
158/// client.create_yak(auth, None, "my cool yak").await?;
159///
160/// Ok(())
161/// }
162///
163/// #[conjure_client]
164/// trait MyStreamingService<#[response_body] I, #[request_writer] O>
165/// where
166/// O: Write,
167/// {
168/// #[endpoint(method = POST, path = "/streamData")]
169/// fn upload_stream(
170/// &self,
171/// #[body(serializer = StreamingRequestSerializer)] body: StreamingRequest,
172/// ) -> Result<(), Error>;
173///
174/// #[endpoint(method = GET, path = "/streamData", accept = StreamingResponseDeserializer)]
175/// fn download_stream(&self) -> Result<I, Error>;
176/// }
177///
178/// struct StreamingRequest;
179///
180/// impl<W> WriteBody<W> for StreamingRequest
181/// where
182/// W: Write,
183/// {
184/// fn write_body(&mut self, w: &mut W) -> Result<(), Error> {
185/// // ...
186/// Ok(())
187/// }
188///
189/// fn reset(&mut self) -> bool {
190/// true
191/// }
192/// }
193///
194/// enum StreamingRequestSerializer {}
195///
196/// impl<W> SerializeRequest<'static, StreamingRequest, W> for StreamingRequestSerializer
197/// where
198/// W: Write,
199/// {
200/// fn content_type(_: &ConjureRuntime, _: &StreamingRequest) -> HeaderValue {
201/// HeaderValue::from_static("text/plain")
202/// }
203///
204/// fn serialize(_: &ConjureRuntime, value: StreamingRequest) -> Result<RequestBody<'static, W>, Error> {
205/// Ok(RequestBody::Streaming(Box::new(value)))
206/// }
207/// }
208///
209/// enum StreamingResponseDeserializer {}
210///
211/// impl<R> DeserializeResponse<R, R> for StreamingResponseDeserializer {
212/// fn accept(_: &ConjureRuntime) -> Option<HeaderValue> {
213/// None
214/// }
215///
216/// fn deserialize(_: &ConjureRuntime, response: Response<R>) -> Result<R, Error> {
217/// Ok(response.into_body())
218/// }
219/// }
220/// ```
221#[proc_macro_attribute]
222pub fn conjure_client(attr: TokenStream, item: TokenStream) -> TokenStream {
223 client::generate(attr, item)
224}
225
226/// Creates a Conjure service type wrapping types implementing the annotated trait.
227///
228/// For a trait named `MyService`, the macro will create a type named `MyServiceEndpoints` which
229/// implements the conjure `Service` trait.
230///
231/// The attribute has a parameter:
232///
233/// * `name` - The value returned from the `EndpointMetadata::service_name` method. Defaults to the
234/// trait name.
235/// * `use_legacy_error_serialization` - If set, parameters of service errors will be serialized in
236/// old stringified format.
237///
238/// # Parameters
239///
240/// The trait can optionally be declared generic over the request body and response writer types by
241/// using the `#[request_body]` and `#[response_writer]` annotations on the type parameters.
242///
243/// # Endpoints
244///
245/// Each method corresponds to a separate HTTP endpoint, and is expected to take `&self` and return
246/// `Result<T, Error>`. Each must be annotated with `#[endpoint]`, which has several parameters:
247///
248/// * `method` - The HTTP method (e.g. `GET`). Required.
249/// * `path` - The HTTP path template. Path parameters should be identified by `{name}` and must
250/// make up an entire path component. Required.
251/// * `name` - The value returned from the `EndpointMetadata::name` method. Defaults to the method
252/// name.
253/// * `produces` - A type implementing `SerializeResponse` which will be used to convert the value
254/// returned by the method into a response. Defaults to `EmptyResponseSerializer`.
255///
256/// Each method argument must have an annotation describing the type of parameter. One of:
257///
258/// * `#[path]` - A path parameter.
259///
260/// Parameters:
261/// * `name` - The name of the path template parameter. Defaults to the argument name.
262/// * `decoder` - A type implementing `DecodeParam` which will be used to decode the value.
263/// Defaults to `FromStrDecoder`.
264/// * `safe` - If set, the parameter will be added to the `SafeParams` response extension.
265/// * `log_as` - The name of the parameter used in request logging and error reporting. Defaults
266/// to the argument name.
267/// * `#[query]` - A query parameter.
268///
269/// Parameters:
270/// * `name` - The string used as the key in the encoded URI. Required.
271/// * `decoder` - A type implementing `DecodeParam` which will be used to decode the value.
272/// Defaults to `FromStrDecoder`.
273/// * `safe` - If set, the parameter will be added to the `SafeParams` response extension.
274/// * `log_as` - The name of the parameter used in request logging and error reporting. Defaults
275/// to the argument name.
276/// * `#[auth]` - A `BearerToken` used to authenticate the request.
277///
278/// Parameters:
279/// * `cookie_name` - The name of the cookie if the token is to be parsed from a `Cookie`
280/// header. If unset, it will be parsed from an `Authorization` header instead.
281/// * `#[header]` - A header parameter.
282///
283/// Parameters:
284/// * `name` - The header name. Required.
285/// * `decoder` - A type implementing `DecodeHeader` which will be used to decode the value.
286/// Defaults to `FromStrDecoder`.
287/// * `safe` - If set, the parameter will be added to the `SafeParams` response extension.
288/// * `log_as` - The name of the parameter used in request logging and error reporting. Defaults
289/// to the argument name.
290/// * `#[body]` - The request body.
291///
292/// Parameters:
293/// * `deserializer` - A type implementing `DeserializeRequest` which will be used to
294/// deserialize the request body into a value. Defaults to `StdRequestDeserializer`.
295/// * `safe` - If set, the parameter will be added to the `SafeParams` response extension.
296/// * `log_as` - The name of the parameter used in request logging and error reporting. Defaults
297/// to the argument name.
298/// * `#[context]` - A `RequestContext` which provides lower level access to the request.
299///
300/// # Async
301///
302/// Both blocking and async services are supported. For technical reasons, async method definitions
303/// will be rewritten by the macro to require the returned future be `Send`.
304///
305/// # Examples
306///
307/// ```rust,ignore
308/// use conjure_error::Error;
309/// use conjure_http::{conjure_endpoints, endpoint};
310/// use conjure_http::server::{
311/// ConjureRuntime, DeserializeRequest, FromStrOptionDecoder, ResponseBody, SerializeResponse,
312/// StdResponseSerializer, WriteBody,
313/// };
314/// use conjure_object::BearerToken;
315/// use http::Response;
316/// use http::header::{CONTENT_TYPE, HeaderMap, HeaderValue};
317/// use std::io::Write;
318///
319/// #[conjure_endpoints]
320/// trait MyService {
321/// #[endpoint(method = GET, path = "/yaks/{yak_id}", produces = StdResponseSerializer)]
322/// fn get_yak(
323/// &self,
324/// #[auth] auth: BearerToken,
325/// #[path(safe)] yak_id: i32,
326/// ) -> Result<String, Error>;
327///
328/// #[endpoint(method = POST, path = "/yaks")]
329/// fn create_yak(
330/// &self,
331/// #[auth] auth: BearerToken,
332/// #[query(name = "parentName", decoder = FromStrOptionDecoder)] parent_id: Option<String>,
333/// #[body] yak: String,
334/// ) -> Result<(), Error>;
335/// }
336///
337/// #[conjure_endpoints]
338/// trait AsyncMyService {
339/// #[endpoint(method = GET, path = "/yaks/{yak_id}", produces = StdResponseSerializer)]
340/// async fn get_yak(
341/// &self,
342/// #[auth] auth: BearerToken,
343/// #[path(safe)] yak_id: i32,
344/// ) -> Result<String, Error>;
345///
346/// #[endpoint(method = POST, path = "/yaks")]
347/// async fn create_yak(
348/// &self,
349/// #[auth] auth: BearerToken,
350/// #[query(name = "parentName", decoder = FromStrOptionDecoder)] parent_id: Option<String>,
351/// #[body] yak: String,
352/// ) -> Result<(), Error>;
353/// }
354///
355/// #[conjure_endpoints]
356/// trait MyStreamingService<#[request_body] I, #[response_writer] O>
357/// where
358/// O: Write,
359/// {
360/// #[endpoint(method = POST, path = "/streamData")]
361/// fn receive_stream(
362/// &self,
363/// #[body(deserializer = StreamingRequestDeserializer)] body: I,
364/// ) -> Result<(), Error>;
365///
366/// #[endpoint(method = GET, path = "/streamData", produces = StreamingResponseSerializer)]
367/// fn stream_response(&self) -> Result<StreamingResponse, Error>;
368/// }
369///
370/// struct StreamingRequestDeserializer;
371///
372/// impl<I> DeserializeRequest<I, I> for StreamingRequestDeserializer {
373/// fn deserialize(
374/// _runtime: &ConjureRuntime,
375/// _headers: &HeaderMap,
376/// body: I,
377/// ) -> Result<I, Error> {
378/// Ok(body)
379/// }
380/// }
381///
382/// struct StreamingResponse;
383///
384/// impl<O> WriteBody<O> for StreamingResponse
385/// where
386/// O: Write,
387/// {
388/// fn write_body(self: Box<Self>, w: &mut O) -> Result<(), Error> {
389/// // ...
390/// Ok(())
391/// }
392/// }
393///
394/// struct StreamingResponseSerializer;
395///
396/// impl<O> SerializeResponse<StreamingResponse, O> for StreamingResponseSerializer
397/// where
398/// O: Write,
399/// {
400/// fn serialize(
401/// _runtime: &ConjureRuntime,
402/// _request_headers: &HeaderMap,
403/// body: StreamingResponse,
404/// ) -> Result<Response<ResponseBody<O>>, Error> {
405/// let mut response = Response::new(ResponseBody::Streaming(Box::new(body)));
406/// response.headers_mut().insert(CONTENT_TYPE, HeaderValue::from_static("text/plain"));
407/// Ok(response)
408/// }
409/// }
410/// ```
411#[proc_macro_attribute]
412pub fn conjure_endpoints(attr: TokenStream, item: TokenStream) -> TokenStream {
413 endpoints::generate(attr, item)
414}
415
416/// A no-op attribute macro required due to technical limitations of Rust's macro system.
417#[proc_macro_attribute]
418pub fn endpoint(_attr: TokenStream, item: TokenStream) -> TokenStream {
419 item
420}
421
422#[doc(hidden)]
423#[proc_macro_derive(DeriveWith, attributes(derive_with))]
424pub fn derive_with(input: proc_macro::TokenStream) -> TokenStream {
425 derive_with::generate(input)
426}
427
428/// Marks the annotated type as safe to log.
429///
430/// The `conjure_object` crate must be in scope.
431#[proc_macro_derive(LogSafe, attributes(assert_is_safe))]
432pub fn derive_safe(input: proc_macro::TokenStream) -> TokenStream {
433 log_safety::generate(input)
434}
435
436struct Errors(Vec<Error>);
437
438impl Errors {
439 fn new() -> Self {
440 Errors(vec![])
441 }
442
443 fn push(&mut self, error: Error) {
444 self.0.push(error);
445 }
446
447 fn build(mut self) -> Result<(), Error> {
448 let Some(mut error) = self.0.pop() else {
449 return Ok(());
450 };
451 for other in self.0 {
452 error.combine(other);
453 }
454 Err(error)
455 }
456}
457
458#[derive(Copy, Clone)]
459enum Asyncness {
460 Sync,
461 Async,
462 LocalAsync,
463}
464
465impl Asyncness {
466 fn resolve(trait_: &ItemTrait, local: bool) -> Result<Self, Error> {
467 let mut it = trait_.items.iter().filter_map(|t| match t {
468 TraitItem::Fn(f) => Some(f),
469 _ => None,
470 });
471
472 let Some(first) = it.next() else {
473 return Ok(Asyncness::Sync);
474 };
475
476 let is_async = first.sig.asyncness.is_some();
477
478 let mut errors = Errors::new();
479
480 for f in it {
481 if f.sig.asyncness.is_some() != is_async {
482 errors.push(Error::new_spanned(
483 f,
484 "all methods must either be sync or async",
485 ));
486 }
487 }
488
489 errors.build()?;
490 let asyncness = if is_async {
491 if local {
492 Asyncness::LocalAsync
493 } else {
494 Asyncness::Async
495 }
496 } else {
497 Asyncness::Sync
498 };
499 Ok(asyncness)
500 }
501}