rustify
A Rust crate which provides an abstraction layer over HTTP REST API endpoints
Rustify is a small crate which provides a way to easily scaffold code which communicates with HTTP REST API endpoints. It covers simple cases such as basic GET requests as well as more advanced cases such as sending serialized data and deserializing the result. A derive macro is provided to keep code DRY.
Rustify provides both a trait for implementing API endpoints as well as clients
for executing requests against the defined endpoints. This crate targets async
first, however, blocking support can be enabled with the blocking
feature flag.
Additionally, the Endpoint
trait offers both async
and blocking variants
of each execution method (blocking methods are available when flag is enabled).
Presently, rustify only supports JSON serialization and generally assumes the
remote endpoint accepts and responds with JSON. Raw byte data can be sent
by tagging a field with #[endpoint(data)]
and can be received by using the
Endpoint::exec_raw()
method.
Installation
cargo add rustify
Architecture
This crate consists of two primary traits:
- The
Endpoint
trait which represents a remote HTTP REST API endpoint - The
Client
trait which is responsible for executing theEndpoint
This provides a loosely coupled interface that allows for multiple
implementations of the Client
trait which may use different HTTP backends. The
Client
trait in particular was kept intentionally easy to implement and is
only required to send http::Request
s and return http::Response
s. A blocking
variant of the client (rustify::blocking::client::Client
) is provided for
implementations that block.
The Endpoint
trait is what will be most implemented by end-users of this
crate. Since the implementation can be verbose and most functionality can be
defined with very little syntax, a macro is provided via rustify_derive
which
should be used for generating implementations of this trait.
Usage
The below example creates a Test
endpoint that, when executed, will send a GET
request to http://api.com/test/path
and expect an empty response:
use Client;
use Endpoint;
use Endpoint;
use Serialize;
let endpoint = Test ;
let client = default;
let result = endpoint.exec;
assert!;
Advanced Usage
This examples demonstrates the complexity available using the full suite of
options offered by the macro (this requires the middleware
feature):
use Bytes;
use Builder;
use Client;
use ;
use ClientError;
use Endpoint;
use ;
use Value;
use skip_serializing_none;
let client = default;
let endpoint = builder
.name
.kind
.build
.unwrap;
let result = endpoint.exec_mut;
Breaking this down:
- The
path
argument supports basic substitution using curly braces. In this case the final url would behttp://api.com/test/path/test
. Since thename
field is only used to build the endpoint URL, we add the#[serde(skip)]
attribute to informserde
to not serialize this field when building the request. - The
method
argument specifies the type of the HTTP request. - The
result
argument specifies the type of response that theexec()
method will return. This type must deriveDeserialize
. - The
builder
argument tells the macro to add some useful functions for when the endpoint is using theBuilder
derive macro from derive_builder. In particular, it adds abuilder()
static method to the base struct which returns a default instance of the builder for the endpoint.
Endpoints contain various methods for executing requests; in this example the
exec_mut()
variant is being used which allows passing an instance of an
object that implements MiddleWare
which can be used to mutate the request and
response object respectively. Here an arbitrary request header containing a
fictitious API token is being injected and the response has a wrapper removed
before final parsing.
This example also demonstrates a common pattern of using
skip_serializing_none macro to force serde
to not serialize fields of
type Option::None
. When combined with the default
parameter offered by
derive_builder the result is an endpoint which can have required and/or
optional fields as needed and which don't get serialized if absent when
building. For example:
// Errors, `kind` field is required
let endpoint = builder
.name
.build
.unwrap;
// Produces POST http://api.com/test/path/test {"kind": "test"}
let endpoint = builder
.name
.kind
.build
.unwrap;
// Produces POST http://api.com/test/path/test {"kind": "test", "optional": "yes"}
let endpoint = builder
.name
.kind
.optional
.build
.unwrap
Features
The following features are available for this crate:
blocking
: Enables the blocking variants ofClient
s as well as the blockingexec()
functions inEndpoint
s.middleware
: Enables usingMiddleWare
for modifying requests/responses duringEndpoint
execution.wrapper
: Enables using aWrapper
to wrap responses fromEndpoint
execution.
Error Handling
All errors generated by this crate are wrapped in the ClientError
enum
provided by the crate.
Testing
See the the tests directory for tests. Run tests with
cargo test
.
Contributing
- Fork it (https://github.com/jmgilman/rustify/fork)
- Create your feature branch (git checkout -b feature/fooBar)
- Commit your changes (git commit -am 'Add some fooBar')
- Push to the branch (git push origin feature/fooBar)
- Create a new Pull Request