gussy up docs for release (#193)

make TryInto use fully specified as std::convert::TryInto
This commit is contained in:
Adam Leventhal 2022-09-10 17:15:14 -07:00 committed by GitHub
parent 04c59d7857
commit 7e5dc6586c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 1059 additions and 781 deletions

143
README.md
View File

@ -1,12 +1,19 @@
# Progenitor
Progenitor is a Rust crate for generating opinionated clients from API
descriptions specified in the OpenAPI 3.0.x format. It makes use of Rust
futures for async API calls and `Streams` for paginated interfaces.
descriptions in the OpenAPI 3.0.x specification. It makes use of Rust
futures for `async` API calls and `Streams` for paginated interfaces.
It generates a type called `Client` with methods that correspond to the
operations specified in the OpenAPI document.
The primary target is OpenAPI documents emitted by
[Dropshot](https://github.com/oxidecomputer/dropshot)-generated APIs, but it
can be used for many OpenAPI documents. As OpenAPI covers a wide range of APIs,
Progenitor may fail for some OpenAPI documents. If you encounter a problem, you
can help the project by filing an issue that includes the OpenAPI document that
produced the problem.
## Using Progenitor
There are three different ways of using the `progenitor` crate. The one you
@ -64,7 +71,7 @@ generate_api!(
Note that the macro will be re-evaluated when the OpenAPI json document
changes (when its mtime is updated).
### Builder
### `build.rs`
Progenitor includes an interface appropriate for use in a
[`build.rs`](https://doc.rust-lang.org/cargo/reference/build-scripts.html)
@ -154,3 +161,133 @@ uuid = { version = ">=0.8.0, <2.0.0", features = ["serde", "v4"] }
Note that there is a dependency on `percent-encoding` which macro- and
build.rs-generated clients is included from `progenitor-client`.
## Generation Styles
Progenitor can generate two distinct interface styles: positional and builder
(described below). The choice is simply a matter of preference that many vary
by API and taste.
## Positional (current default)
The "positional" style generates `Client` methods that accept parameters in
order, for example:
```rust
impl Client {
pub async fn instance_create<'a>(
&'a self,
organization_name: &'a types::Name,
project_name: &'a types::Name,
body: &'a types::InstanceCreate,
) -> Result<ResponseValue<types::Instance>, Error<types::Error>> {
// ...
}
}
```
A caller invokes this interface by specifying parameters by position:
```rust
let result = client.instance_create(org, proj, body).await?;
```
Note that the type of each parameter must match precisely--no conversion is
done implicitly.
## Builder
The "builder" style generates `Client` methods that produce a builder struct.
API parameters are applied to that builder, and then the builder is executed
(via a `send` method). The code is more extensive and more complex to enable
simpler and more legible consumers:
```rust
impl Client
pub fn instance_create(&self) -> builder::InstanceCreate {
builder::InstanceCreate::new(self)
}
}
mod builder {
pub struct InstanceCreate<'a> {
client: &'a super::Client,
organization_name: Result<types::Name, String>,
project_name: Result<types::Name, String>,
body: Result<types::InstanceCreate, String>,
}
impl<'a> InstanceCreate<'a> {
pub fn new(client: &'a super::Client) -> Self {
// ...
}
pub fn organization_name<V>(mut self, value: V) -> Self
where
V: TryInto<types::Name>,
{
// ...
}
pub fn project_name<V>(mut self, value: V) -> Self
where
V: TryInto<types::Name>,
{
// ...
}
pub fn body<V>(mut self, value: V) -> Self
where
V: TryInto<types::InstanceCreate>,
{
// ...
}
pub async fn send(self) ->
Result<ResponseValue<types::Instance>, Error<types::Error>>
{
// ...
}
}
}
```
Note that, unlike positional generation, consumers can supply compatible
(rather than invariant) parameters:
```rust
let result = client
.instance_create()
.organization_name("org")
.project_name("proj")
.body(body)
.send()
.await?;
```
The string parameters are will implicitly have `TryFrom::try_from()` invoked on
them. Failed conversions or missing required parameters will result in an
`Error` result from the `send()` call.
Generated `struct` types also have builders so that the `body` parameter can be
constructed inline:
```rust
let result = client
.instance_create()
.organization_name("org")
.project_name("proj")
.body(types::InstanceCreate::builder()
.name("...")
.description("...")
.hostname("...")
.ncpus(types::InstanceCpuCount(4))
.memory(types::ByteCount(1024 * 1024 * 1024)),
)
.send()
.await?;
```
Consumers do not need to specify parameters and struct properties that are not
required or for which the API specifies defaults. Neat!

140
docs/builder-generation.md Normal file
View File

@ -0,0 +1,140 @@
# Builder Generation
When the "builder" style is specified, Progenitor generates methods and builder
struct according to operations within an OpenAPI document. They take the
following general form:
```rust
impl Client {
pub fn operation_name(&self) -> builder::InstanceCreate {
builder::OperationName::new(self)
}
}
mod builder {
#[derive(Debug, Clone)]
pub struct OperationName<'a> {
client: &'a super::Client,
path_parameter_1: Result<String, String>,
path_parameter_2: Result<u32, String>,
query_parameter_1: Result<String, String>,
query_parameter_2: Result<Option<u32>, String>,
body: Result<types::ThisOperationBody, String>,
}
impl<'a> OperationName<'a> {
pub fn new(client: &'a super::Client) -> Self {
Self {
client,
path_parameter_1: Err("path_parameter_1 was not initialized".to_string()),
path_parameter_2: Err("path_parameter_2 was not initialized".to_string()),
query_parameter_1: Err("query_parameter_1 was not initialized".to_string()),
query_parameter_2: Ok(None),
body: Err("body was not initialized".to_string()),
}
}
pub fn path_parameter_1<V>(mut self, value: V) -> Self
where
V: std::convert::TryInto<String>,
{
self.organization_name = value
.try_into()
.map_err(|_| "conversion to `String` for path_parameter_1 failed".to_string());
self
}
pub fn path_parameter_2<V>(mut self, value: V) -> Self
where
V: std::convert::TryInto<u32>,
{
self.organization_name = value
.try_into()
.map_err(|_| "conversion to `u32` for path_parameter_2 failed".to_string());
self
}
pub fn query_parameter_1<V>(mut self, value: V) -> Self
where
V: std::convert::TryInto<String>,
{
self.organization_name = value
.try_into()
.map_err(|_| "conversion to `String` for query_parameter_1 failed".to_string());
self
}
pub fn query_parameter_2<V>(mut self, value: V) -> Self
where
V: std::convert::TryInto<u32>,
{
self.organization_name = value
.try_into()
.map_err(|_| "conversion to `u32` for query_parameter_2 failed".to_string());
self
}
pub fn body<V>(mut self, value: V) -> Self
where
V: TryInto<types::ThisOperationBody>,
{
self.body = value
.try_into()
.map_err(|_| "conversion to `ThisOperationBody` for body failed".to_string());
self
}
pub async fn send(self) -> Result<
ResponseValue<types::SuccessResponseType>,
Error<types::ErrorResponseType>,
> {
// ...
}
}
```
For more info on the `ResponseValue<T>` and `Error<E>` types, see
[progenitor_client](./progenitor-client.md).
Note that `send` methods are `async` so must be `await`ed to get the response value.
### Dropshot Paginated Operations
Dropshot defines a mechanism for pagination. If that mechanism is used for a
particular operation, Progenitor will generate an additional `stream` method on
the builder struct that produces a `Stream`. Consumers can iterate over all
items in the paginated collection without manually fetching individual pages.
Here's the signature for a typical generated method:
```rust
impl<'a> OperationName<'a> {
pub fn stream(
self,
) -> impl futures::Stream<
Item = Result<types::SuccessResponseType, Error<types::ErrorResponseType>>
> + Unpin + 'a {
// ...
}
}
```
A typical consumer of this method might look like this:
```rust
let mut stream = client.operation_name().stream();
loop {
match stream.try_next().await {
Ok(Some(item)) => println!("item {:?}", item),
Ok(None) => {
println!("done.");
break;
}
Err(_) => {
println!("error!");
break;
}
}
}
```

View File

@ -1,14 +1,17 @@
# Implementing the client
Once you have generated your client, you'll notice there are two methods to create a new client; `Client::new()` and `Client::new_with_client()`.
Once you have generated your client, you'll notice there are two methods to
create a new `Client`; `Client::new()` and `Client::new_with_client()`.
The first method will create a basic client without any headers or customisations that aren't already in your OpenAPI specification file:
The first method will create a basic client without any headers or
customizations that aren't already in your OpenAPI specification file:
```rust
let client = Client::new("https://foo/bar");
```
Should you need to set custom headers or other reqwest::ClientBuilder methods, you can use `Client::new_with_client()` as shown below.
Should you need to set custom headers or other `reqwest::ClientBuilder`
methods, you can use `Client::new_with_client()` as shown below.
```rust
let mut val = reqwest::header::HeaderValue::from_static("super-secret");
@ -28,4 +31,6 @@ let client_builder = reqwest::ClientBuilder::new()
let client Client::new_with_client("https://foo/bar", client_builder);
```
For more information on available methods, see the [reqwest](https://docs.rs/reqwest/latest/reqwest/struct.ClientBuilder.html) documentation.
For more information on available methods, see the
[reqwest](https://docs.rs/reqwest/latest/reqwest/struct.ClientBuilder.html)
documentation.

View File

@ -1,7 +1,8 @@
# Generated Methods
# Positional Generation
Progenitor generates methods according to operations within an OpenAPI
document. Each method takes the following general form:
When the "positional" style is specified, Progenitor generates methods
according to operations within an OpenAPI document. Each takes the following
general form:
```rust
impl Client {
@ -14,24 +15,25 @@ impl Client {
query_parameter_1: String,
query_parameter_2: Option<u32>,
// A body parameter (if specified) comes last
body: &ThisOperationBody,
body: &types::ThisOperationBody,
) -> Result<
ResponseValue<types::SuccessResponseType>,
Error<types::ErrorResponseType>
Error<types::ErrorResponseType>,
> {
..
// ...
}
```
For more info on the `ResponseValue<T>` and `Error<E>` types, see
[progenitor_client](./progenitor-client.md).
Note that methods are `async` so must be `await`ed to get the response.
Note that methods are `async` so must be `await`ed to get the response value.
### Dropshot Paginated Operations
The Dropshot crate defines a mechanism for pagination. If that mechanism is
used for a particular operation, Progenitor will generate an additional method
that produces a `Stream`. Consumers can iterate over all items in the paginated
Dropshot defines a mechanism for pagination. If that mechanism is used for a
particular operation, Progenitor will generate an additional method that
produces a `Stream`. Consumers can iterate over all items in the paginated
collection without manually fetching individual pages.
Here's the signature for a typical generated method:
@ -44,7 +46,8 @@ Here's the signature for a typical generated method:
) -> impl futures::Stream<
Item = Result<types::SuccessResponseType, Error<types::ErrorResponseType>>
> + Unpin + '_ {
..
// ...
}
```
A typical consumer of this method might look like this:

View File

@ -25,7 +25,8 @@ impl Client {
ResponseValue<types::SuccessResponseType>,
Error<types::ErrorResponseType>>
{
..
// ...
}
```
## `ResponseValue<T>`
@ -64,7 +65,9 @@ It can be used as the type `T` in most instances and extracted as a `T` using
There are five sub-categories of error covered by the error type variants:
- A request that did not conform to API requirements. This can occur when required builder or body parameters were not specified, and the error message will denote the specific failure.
- A request that did not conform to API requirements. This can occur when
required builder or body parameters were not specified, and the error message
will denote the specific failure.
- A communication error

View File

@ -324,8 +324,6 @@ impl Generator {
RequestBuilderExt,
ResponseValue,
};
#[allow(unused_imports)]
use std::convert::TryInto;
#(#builder_struct)*
}

View File

@ -1129,14 +1129,14 @@ impl Generator {
/// ```ignore
/// impl<'a> OperationId<'a> {
/// pub fn param_1<V>(self, value: V)
/// where V: TryInto<SomeType>
/// where V: std::convert::TryInto<SomeType>
/// {
/// self.param_1 = value.try_into()
/// .map_err(|_| #err_msg.to_string());
/// self
/// }
/// pub fn param_2<V>(self, value: V)
/// where V: TryInto<SomeType>
/// where V: std::convert::TryInto<SomeType>
/// {
/// self.param_2 = value.try_into()
/// .map(Some)
@ -1264,7 +1264,7 @@ impl Generator {
Ok(quote! {
pub fn #param_name<V>(mut self, value: V)
-> Self
where V: TryInto<#typ>
where V: std::convert::TryInto<#typ>
{
self.#param_name = value.try_into()
.map(Some)
@ -1277,7 +1277,7 @@ impl Generator {
let typ = ty.ident();
Ok(quote! {
pub fn #param_name<V>(mut self, value: V) -> Self
where V: TryInto<#typ>,
where V: std::convert::TryInto<#typ>,
{
self.#param_name = value.try_into()
.map_err(|_| #err_msg.to_string());
@ -1294,7 +1294,7 @@ impl Generator {
Ok(quote! {
pub fn #param_name<B>(mut self, value: B) -> Self
where B: TryInto<reqwest::Body>
where B: std::convert::TryInto<reqwest::Body>
{
self.#param_name = value.try_into()
.map_err(|_| #err_msg.to_string());

View File

@ -1574,7 +1574,7 @@ pub mod builder {
pub fn task<V>(mut self, value: V) -> Self
where
V: TryInto<String>,
V: std::convert::TryInto<String>,
{
self.task = value
.try_into()
@ -1647,7 +1647,7 @@ pub mod builder {
pub fn body<V>(mut self, value: V) -> Self
where
V: TryInto<types::TaskSubmit>,
V: std::convert::TryInto<types::TaskSubmit>,
{
self.body = value
.try_into()
@ -1691,7 +1691,7 @@ pub mod builder {
pub fn task<V>(mut self, value: V) -> Self
where
V: TryInto<String>,
V: std::convert::TryInto<String>,
{
self.task = value
.try_into()
@ -1701,7 +1701,7 @@ pub mod builder {
pub fn minseq<V>(mut self, value: V) -> Self
where
V: TryInto<u32>,
V: std::convert::TryInto<u32>,
{
self.minseq = value
.try_into()
@ -1757,7 +1757,7 @@ pub mod builder {
pub fn task<V>(mut self, value: V) -> Self
where
V: TryInto<String>,
V: std::convert::TryInto<String>,
{
self.task = value
.try_into()
@ -1805,7 +1805,7 @@ pub mod builder {
pub fn task<V>(mut self, value: V) -> Self
where
V: TryInto<String>,
V: std::convert::TryInto<String>,
{
self.task = value
.try_into()
@ -1815,7 +1815,7 @@ pub mod builder {
pub fn output<V>(mut self, value: V) -> Self
where
V: TryInto<String>,
V: std::convert::TryInto<String>,
{
self.output = value
.try_into()
@ -1867,7 +1867,7 @@ pub mod builder {
pub fn body<V>(mut self, value: V) -> Self
where
V: TryInto<types::UserCreate>,
V: std::convert::TryInto<types::UserCreate>,
{
self.body = value
.try_into()
@ -1936,7 +1936,7 @@ pub mod builder {
pub fn body<V>(mut self, value: V) -> Self
where
V: TryInto<types::WorkerBootstrap>,
V: std::convert::TryInto<types::WorkerBootstrap>,
{
self.body = value
.try_into()
@ -2007,7 +2007,7 @@ pub mod builder {
pub fn task<V>(mut self, value: V) -> Self
where
V: TryInto<String>,
V: std::convert::TryInto<String>,
{
self.task = value
.try_into()
@ -2017,7 +2017,7 @@ pub mod builder {
pub fn body<V>(mut self, value: V) -> Self
where
V: TryInto<types::WorkerAppendTask>,
V: std::convert::TryInto<types::WorkerAppendTask>,
{
self.body = value
.try_into()
@ -2066,7 +2066,7 @@ pub mod builder {
pub fn task<V>(mut self, value: V) -> Self
where
V: TryInto<String>,
V: std::convert::TryInto<String>,
{
self.task = value
.try_into()
@ -2076,7 +2076,7 @@ pub mod builder {
pub fn body<B>(mut self, value: B) -> Self
where
B: TryInto<reqwest::Body>,
B: std::convert::TryInto<reqwest::Body>,
{
self.body = value
.try_into()
@ -2133,7 +2133,7 @@ pub mod builder {
pub fn task<V>(mut self, value: V) -> Self
where
V: TryInto<String>,
V: std::convert::TryInto<String>,
{
self.task = value
.try_into()
@ -2143,7 +2143,7 @@ pub mod builder {
pub fn body<V>(mut self, value: V) -> Self
where
V: TryInto<types::WorkerCompleteTask>,
V: std::convert::TryInto<types::WorkerCompleteTask>,
{
self.body = value
.try_into()
@ -2192,7 +2192,7 @@ pub mod builder {
pub fn task<V>(mut self, value: V) -> Self
where
V: TryInto<String>,
V: std::convert::TryInto<String>,
{
self.task = value
.try_into()
@ -2202,7 +2202,7 @@ pub mod builder {
pub fn body<V>(mut self, value: V) -> Self
where
V: TryInto<types::WorkerAddOutput>,
V: std::convert::TryInto<types::WorkerAddOutput>,
{
self.body = value
.try_into()

View File

@ -1501,8 +1501,6 @@ pub mod builder {
use super::types;
#[allow(unused_imports)]
use super::{encode_path, ByteStream, Error, RequestBuilderExt, ResponseValue};
#[allow(unused_imports)]
use std::convert::TryInto;
///Builder for [`Client::control_hold`]
///
///[`Client::control_hold`]: super::Client::control_hold
@ -1576,7 +1574,7 @@ pub mod builder {
pub fn task<V>(mut self, value: V) -> Self
where
V: TryInto<String>,
V: std::convert::TryInto<String>,
{
self.task = value
.try_into()
@ -1649,7 +1647,7 @@ pub mod builder {
pub fn body<V>(mut self, value: V) -> Self
where
V: TryInto<types::TaskSubmit>,
V: std::convert::TryInto<types::TaskSubmit>,
{
self.body = value
.try_into()
@ -1693,7 +1691,7 @@ pub mod builder {
pub fn task<V>(mut self, value: V) -> Self
where
V: TryInto<String>,
V: std::convert::TryInto<String>,
{
self.task = value
.try_into()
@ -1703,7 +1701,7 @@ pub mod builder {
pub fn minseq<V>(mut self, value: V) -> Self
where
V: TryInto<u32>,
V: std::convert::TryInto<u32>,
{
self.minseq = value
.try_into()
@ -1759,7 +1757,7 @@ pub mod builder {
pub fn task<V>(mut self, value: V) -> Self
where
V: TryInto<String>,
V: std::convert::TryInto<String>,
{
self.task = value
.try_into()
@ -1807,7 +1805,7 @@ pub mod builder {
pub fn task<V>(mut self, value: V) -> Self
where
V: TryInto<String>,
V: std::convert::TryInto<String>,
{
self.task = value
.try_into()
@ -1817,7 +1815,7 @@ pub mod builder {
pub fn output<V>(mut self, value: V) -> Self
where
V: TryInto<String>,
V: std::convert::TryInto<String>,
{
self.output = value
.try_into()
@ -1869,7 +1867,7 @@ pub mod builder {
pub fn body<V>(mut self, value: V) -> Self
where
V: TryInto<types::UserCreate>,
V: std::convert::TryInto<types::UserCreate>,
{
self.body = value
.try_into()
@ -1938,7 +1936,7 @@ pub mod builder {
pub fn body<V>(mut self, value: V) -> Self
where
V: TryInto<types::WorkerBootstrap>,
V: std::convert::TryInto<types::WorkerBootstrap>,
{
self.body = value
.try_into()
@ -2009,7 +2007,7 @@ pub mod builder {
pub fn task<V>(mut self, value: V) -> Self
where
V: TryInto<String>,
V: std::convert::TryInto<String>,
{
self.task = value
.try_into()
@ -2019,7 +2017,7 @@ pub mod builder {
pub fn body<V>(mut self, value: V) -> Self
where
V: TryInto<types::WorkerAppendTask>,
V: std::convert::TryInto<types::WorkerAppendTask>,
{
self.body = value
.try_into()
@ -2068,7 +2066,7 @@ pub mod builder {
pub fn task<V>(mut self, value: V) -> Self
where
V: TryInto<String>,
V: std::convert::TryInto<String>,
{
self.task = value
.try_into()
@ -2078,7 +2076,7 @@ pub mod builder {
pub fn body<B>(mut self, value: B) -> Self
where
B: TryInto<reqwest::Body>,
B: std::convert::TryInto<reqwest::Body>,
{
self.body = value
.try_into()
@ -2135,7 +2133,7 @@ pub mod builder {
pub fn task<V>(mut self, value: V) -> Self
where
V: TryInto<String>,
V: std::convert::TryInto<String>,
{
self.task = value
.try_into()
@ -2145,7 +2143,7 @@ pub mod builder {
pub fn body<V>(mut self, value: V) -> Self
where
V: TryInto<types::WorkerCompleteTask>,
V: std::convert::TryInto<types::WorkerCompleteTask>,
{
self.body = value
.try_into()
@ -2194,7 +2192,7 @@ pub mod builder {
pub fn task<V>(mut self, value: V) -> Self
where
V: TryInto<String>,
V: std::convert::TryInto<String>,
{
self.task = value
.try_into()
@ -2204,7 +2202,7 @@ pub mod builder {
pub fn body<V>(mut self, value: V) -> Self
where
V: TryInto<types::WorkerAddOutput>,
V: std::convert::TryInto<types::WorkerAddOutput>,
{
self.body = value
.try_into()

View File

@ -849,7 +849,7 @@ pub mod builder {
pub fn body<V>(mut self, value: V) -> Self
where
V: TryInto<types::EnrolBody>,
V: std::convert::TryInto<types::EnrolBody>,
{
self.body = value
.try_into()
@ -945,7 +945,7 @@ pub mod builder {
pub fn body<V>(mut self, value: V) -> Self
where
V: TryInto<types::ReportFinishBody>,
V: std::convert::TryInto<types::ReportFinishBody>,
{
self.body = value
.try_into()
@ -987,7 +987,7 @@ pub mod builder {
pub fn body<V>(mut self, value: V) -> Self
where
V: TryInto<types::ReportOutputBody>,
V: std::convert::TryInto<types::ReportOutputBody>,
{
self.body = value
.try_into()
@ -1029,7 +1029,7 @@ pub mod builder {
pub fn body<V>(mut self, value: V) -> Self
where
V: TryInto<types::ReportStartBody>,
V: std::convert::TryInto<types::ReportStartBody>,
{
self.body = value
.try_into()

View File

@ -830,8 +830,6 @@ pub mod builder {
use super::types;
#[allow(unused_imports)]
use super::{encode_path, ByteStream, Error, RequestBuilderExt, ResponseValue};
#[allow(unused_imports)]
use std::convert::TryInto;
///Builder for [`Client::enrol`]
///
///[`Client::enrol`]: super::Client::enrol
@ -851,7 +849,7 @@ pub mod builder {
pub fn body<V>(mut self, value: V) -> Self
where
V: TryInto<types::EnrolBody>,
V: std::convert::TryInto<types::EnrolBody>,
{
self.body = value
.try_into()
@ -947,7 +945,7 @@ pub mod builder {
pub fn body<V>(mut self, value: V) -> Self
where
V: TryInto<types::ReportFinishBody>,
V: std::convert::TryInto<types::ReportFinishBody>,
{
self.body = value
.try_into()
@ -989,7 +987,7 @@ pub mod builder {
pub fn body<V>(mut self, value: V) -> Self
where
V: TryInto<types::ReportOutputBody>,
V: std::convert::TryInto<types::ReportOutputBody>,
{
self.body = value
.try_into()
@ -1031,7 +1029,7 @@ pub mod builder {
pub fn body<V>(mut self, value: V) -> Self
where
V: TryInto<types::ReportStartBody>,
V: std::convert::TryInto<types::ReportStartBody>,
{
self.body = value
.try_into()

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -237,8 +237,6 @@ pub mod builder {
use super::types;
#[allow(unused_imports)]
use super::{encode_path, ByteStream, Error, RequestBuilderExt, ResponseValue};
#[allow(unused_imports)]
use std::convert::TryInto;
///Builder for [`Client::default_params`]
///
///[`Client::default_params`]: super::Client::default_params
@ -258,7 +256,7 @@ pub mod builder {
pub fn body<V>(mut self, value: V) -> Self
where
V: TryInto<types::BodyWithDefaults>,
V: std::convert::TryInto<types::BodyWithDefaults>,
{
self.body = value
.try_into()