rustify/lib.rs
1//! # rustify
2//!
3//! <p align="center">
4//! <a href="https://crates.io/crates/rustify">
5//! <img src="https://img.shields.io/crates/v/rustify">
6//! </a>
7//! <a href="https://docs.rs/rustify">
8//! <img src="https://img.shields.io/docsrs/rustify" />
9//! </a>
10//! <a href="https://github.com/jmgilman/rustify/actions/workflows/ci.yml">
11//! <img src="https://github.com/jmgilman/rustify/actions/workflows/ci.yml/badge.svg"/>
12//! </a>
13//! </p>
14//!
15//! > A Rust library for interacting with HTTP API endpoints
16//!
17//! Rustify is a small library written in Rust which eases the burden of
18//! scaffolding HTTP APIs. It provides an `Endpoint` trait along with a macro helper
19//! which allows templating various remote endpoints. Both asynchronous and
20//! synchronous clients are offered for executing requests against endpoints with
21//! the option of implementing custom clients using the `Client` trait.
22//!
23//! Rustify provides support for serializing requests and deserializing responses.
24//! Raw requests and responses in the form of bytes are also supported. The library
25//! also contains many helpers for dealing with requests like support for middleware
26//! and wrapping API responses.
27//!
28//! ## Installation
29//!
30//! Add rustify as a dependency to your cargo.toml:
31//!
32//! ```ignore
33//! [dependencies]
34//! rustify = "0.6.1"
35//! rustify_derive = "0.5.4"
36//! ```
37//!
38//! ## Usage
39//!
40//! ### Basic
41//!
42//! ```rust
43//! use rustify::{Client, Endpoint};
44//! use rustify_derive::Endpoint;
45//!
46//! // Defines an API endpoint at /test/path that takes no inputs and returns an
47//! // empty response.
48//! #[derive(Endpoint)]
49//! #[endpoint(path = "test/path")]
50//! struct Test {}
51//!
52//! # tokio_test::block_on(async {
53//! let endpoint = Test {};
54//! let client = Client::default("http://api.com"); // Configures base address of http://api.com
55//! let result = endpoint.exec(&client).await; // Sends GET request to http://api.com/test/path
56//!
57//! // assert!(result.is_ok());
58//! # });
59//! ```
60//!
61//!
62//! ### Request Body
63//!
64//! ```rust
65//! use derive_builder::Builder;
66//! use rustify::{Client, Endpoint};
67//! use rustify_derive::Endpoint;
68//!
69//! // Defines an API endpoint at /test/path/{name} that takes one input for
70//! // creating the url and two inputs for building the request body. The content
71//! // type of the request body defaults to JSON, however, it can be modified by
72//! // passing the `request_type` parameter to the endpoint configuration.
73//! //
74//! // Note: The `#[endpoint(body)]` attribute tags are technically optional in the
75//! // below example. If no `body` attribute is found anywhere then rustify defaults
76//! // to serializing all "untagged" fields as part of the body. Fields can be opted
77//! // out of this behavior by tagging them with #[endpoint(skip)].
78//! #[derive(Builder, Endpoint)]
79//! #[endpoint(path = "test/path/{self.name}", method = "POST", builder = "true")]
80//! #[builder(setter(into))] // Improves the building process
81//! struct Test {
82//! #[endpoint(skip)] // This field shouldn't be serialized anywhere
83//! pub name: String, // Used to create a dynamic URL
84//! #[endpoint(body)] // Instructs rustify to serialize this field as part of the body
85//! pub age: i32,
86//! #[endpoint(body)]
87//! pub role: String,
88//! }
89//!
90//! // Setting `builder` to true creates a `builder()` method on our struct that
91//! // returns the TestBuilder type created by `derive_builder`.
92//! # tokio_test::block_on(async {
93//! let endpoint = Test::builder()
94//! .name("jmgilman")
95//! .age(42)
96//! .role("CEO")
97//! .build()
98//! .unwrap();
99//! let client = Client::default("http://api.com");
100//! let result = endpoint.exec(&client).await; // Sends POST request to http://api.com/test/path/jmgilman
101//!
102//! // assert!(result.is_ok());
103//! # });
104//! ```
105//!
106//! ### Query Parameters
107//!
108//! ```rust
109//! use derive_builder::Builder;
110//! use rustify::{Client, Endpoint};
111//! use rustify_derive::Endpoint;
112//!
113//! // Defines a similar API endpoint as in the previous example but adds an
114//! // optional query parameter to the request. Additionally, this example opts to
115//! // not specify the `#[endpoint(body)]` attributes to make use of the default
116//! // behavior covered in the previous example.
117//! #[derive(Default, Builder, Endpoint)]
118//! #[endpoint(path = "test/path/{self.name}", method = "POST", builder = "true")]
119//! #[builder(setter(into, strip_option), default)] // Improves building process
120//! struct Test {
121//! #[endpoint(skip)]
122//! pub name: String,
123//! #[endpoint(query)]
124//! pub scope: Option<String>, // Note: serialization is skipped when this field is None
125//! pub age: i32, // Serialized into the request body
126//! pub role: String, // Serialized into the request body
127//! }
128//!
129//! # tokio_test::block_on(async {
130//! let endpoint = Test::builder()
131//! .name("jmgilman")
132//! .scope("global")
133//! .age(42)
134//! .role("CEO")
135//! .build()
136//! .unwrap();
137//! let client = Client::default("http://api.com");
138//! let result = endpoint.exec(&client).await; // Sends POST request to http://api.com/test/path/jmgilman?scope=global
139//!
140//! // assert!(result.is_ok());
141//! # });
142//! ```
143//!
144//! ### Responses
145//!
146//! ```should_panic
147//! use rustify::{Client, Endpoint};
148//! use rustify_derive::Endpoint;
149//! use serde::Deserialize;
150//!
151//! // Defines an API endpoint at /test/path that takes a single byte array which
152//! // will be used as the request body (no serialization occurs). The endpoint
153//! // returns a `TestResponse` which contains the result of the operation.
154//! #[derive(Endpoint)]
155//! #[endpoint(path = "test/path", response = "TestResponse")]
156//! struct Test {
157//! #[endpoint(raw)] // Indicates this field contains the raw request body
158//! pub file: Vec<u8>
159//! }
160//!
161//! #[derive(Deserialize)]
162//! struct TestResponse {
163//! pub success: bool,
164//! }
165//!
166//! # tokio_test::block_on(async {
167//! let endpoint = Test {
168//! file: b"contents".to_vec(),
169//! };
170//! let client = Client::default("http://api.com");
171//! let result = endpoint.exec(&client).await;
172//!
173//! // assert!(result.is_ok());
174//!
175//! let response = result.unwrap().parse().unwrap(); // Returns the parsed `TestResponse`
176//! // dbg!(response.success);
177//! # });
178//! ```
179//!
180//! ## Examples
181//!
182//! You can find example usage in the [examples](examples) directory. They can
183//! be run with cargo:
184//!
185//! ```ignore
186//! cargo run --package rustify --example reqres1
187//! cargo run --package rustify --example reqres2
188//! ```
189//!
190//! The [vaultrs](https://github.com/jmgilman/vaultrs) crate is built upon rustify
191//! and serves as as good reference.
192//!
193//! ## Features
194//! The following features are available for this crate:
195//!
196//! * `blocking`: Enables the blocking variants of `Client`s as well as the blocking
197//! `exec()` functions in `Endpoint`s.
198//!
199//! ## Error Handling
200//!
201//! All errors generated by this crate are wrapped in the `ClientError` enum
202//! provided by the crate.
203//!
204//! ## Testing
205//!
206//! See the the [tests](tests) directory for tests. Run tests with `cargo test`.
207//!
208//! ## Contributing
209//!
210//! Check out the [issues][1] for items needing attention or submit your own and
211//! then:
212//!
213//! 1. Fork it (https://github.com/jmgilman/rustify/fork)
214//! 2. Create your feature branch (git checkout -b feature/fooBar)
215//! 3. Commit your changes (git commit -am 'Add some fooBar')
216//! 4. Push to the branch (git push origin feature/fooBar)
217//! 5. Create a new Pull Request
218//!
219//! [1]: https://github.com/jmgilman/rustify/issues
220
221#[macro_use]
222extern crate tracing;
223
224#[cfg(feature = "blocking")]
225pub mod blocking;
226pub mod client;
227pub mod clients;
228pub mod endpoint;
229pub mod enums;
230pub mod errors;
231pub mod http;
232
233#[doc(hidden)]
234#[path = "private/mod.rs"]
235pub mod __private;
236
237pub use crate::{
238 clients::reqwest::Client,
239 endpoint::{Endpoint, MiddleWare, Wrapper},
240};