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