Skip to main content

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}