Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 14 additions & 1 deletion docs/src/clients/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,20 @@ The demo repository includes extra project scaffolding around some generated cli
### Rust

- Generates typed async client methods.
- Integrates with `reflectapi::rt::Client`.
- Integrates with `reflectapi::rt::Client`. The transport carries the
base URL (`Client::base_url`); the per-request `Request` DTO carries
only `path`, `headers`, and `body` — same shape as TypeScript and
Python.
- Built-in transports: `reflectapi::rt::ReqwestClient` (a thin wrapper
around `reqwest::Client` + base URL) and the type alias
`ReqwestMiddlewareClient` for `reqwest_middleware::ClientWithMiddleware`.
- Generated `Interface<C>` exposes:
- `Interface::new(client: C)` — generic, takes any `Client` impl.
- `Interface::try_new(reqwest::Client, base_url) -> Result<Self, UrlParseError>` —
convenience constructor that hides the `ReqwestClient` adapter for
the most common case. Available when the generated crate enables
its own `reqwest` feature (which should re-export
`reflectapi/reqwest`).
- Supports optional tracing instrumentation through `--instrument`.
- Generates serde-compatible types and request helpers for JSON-based transport.

Expand Down
7 changes: 7 additions & 0 deletions reflectapi-demo/clients/rust/generated/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,14 @@ workspace = true

[dependencies]
reflectapi = { workspace = true, features = ["rt", "rt-sse", "reqwest", "chrono"] }
reqwest = { version = "0.12.2", optional = true }
chrono = { version = "0.4.37", features = ["serde"] }
tracing = "0.1"
serde = { version = "1.0.218", features = ["derive"] }

[features]
default = ["reqwest"]
# Enables `Interface::try_new(reqwest::Client, base_url)` — a convenience
# constructor that hides the `reflectapi::rt::ReqwestClient` adapter.
reqwest = ["dep:reqwest"]

136 changes: 32 additions & 104 deletions reflectapi-demo/clients/rust/generated/src/generated.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,43 +17,42 @@ pub mod interface {
pub health: HealthInterface<C>,
pub pets: PetsInterface<C>,
client: C,
base_url: reflectapi::rt::Url,
}

impl<C: reflectapi::rt::Client + Clone> Interface<C> {
pub fn new(client: C) -> Self {
Self {
health: HealthInterface::new(client.clone()),
pets: PetsInterface::new(client.clone()),
client,
}
}
}

#[cfg(feature = "reqwest")]
impl Interface<reflectapi::rt::ReqwestClient<reqwest::Client>> {
/// Convenience: build the client backed by a bare `reqwest::Client`
/// and the given base URL. Hides the
/// [`reflectapi::rt::ReqwestClient`] adapter so callers don't need
/// to name it.
pub fn try_new(
client: C,
client: reqwest::Client,
base_url: reflectapi::rt::Url,
) -> std::result::Result<Self, reflectapi::rt::UrlParseError> {
if base_url.cannot_be_a_base() {
return Err(reflectapi::rt::UrlParseError::RelativeUrlWithCannotBeABaseBase);
}

Ok(Self {
health: HealthInterface::try_new(client.clone(), base_url.clone())?,
pets: PetsInterface::try_new(client.clone(), base_url.clone())?,
client,
base_url,
})
Ok(Self::new(reflectapi::rt::ReqwestClient::try_new(
client, base_url,
)?))
}
}

#[derive(Debug)]
pub struct HealthInterface<C: reflectapi::rt::Client + Clone> {
client: C,
base_url: reflectapi::rt::Url,
}

impl<C: reflectapi::rt::Client + Clone> HealthInterface<C> {
pub fn try_new(
client: C,
base_url: reflectapi::rt::Url,
) -> std::result::Result<Self, reflectapi::rt::UrlParseError> {
if base_url.cannot_be_a_base() {
return Err(reflectapi::rt::UrlParseError::RelativeUrlWithCannotBeABaseBase);
}

Ok(Self { client, base_url })
pub fn new(client: C) -> Self {
Self { client }
}
/// Check the health of the service
#[tracing::instrument(name = "/health.check", skip(self, headers))]
Expand All @@ -65,34 +64,18 @@ pub mod interface {
reflectapi::Empty,
reflectapi::rt::Error<super::types::myapi::HealthCheckFail, C::Error>,
> {
reflectapi::rt::__request_impl(
&self.client,
self.base_url
.join("/health.check")
.expect("checked base_url already and path is valid"),
input,
headers,
)
.await
reflectapi::rt::__request_impl(&self.client, "/health.check", input, headers).await
}
}

#[derive(Debug)]
pub struct PetsInterface<C: reflectapi::rt::Client + Clone> {
client: C,
base_url: reflectapi::rt::Url,
}

impl<C: reflectapi::rt::Client + Clone> PetsInterface<C> {
pub fn try_new(
client: C,
base_url: reflectapi::rt::Url,
) -> std::result::Result<Self, reflectapi::rt::UrlParseError> {
if base_url.cannot_be_a_base() {
return Err(reflectapi::rt::UrlParseError::RelativeUrlWithCannotBeABaseBase);
}

Ok(Self { client, base_url })
pub fn new(client: C) -> Self {
Self { client }
}
/// List available pets
#[tracing::instrument(name = "/pets.list", skip(self, headers))]
Expand All @@ -104,15 +87,7 @@ pub mod interface {
super::types::myapi::proto::Paginated<super::types::myapi::model::output::Pet>,
reflectapi::rt::Error<super::types::myapi::proto::PetsListError, C::Error>,
> {
reflectapi::rt::__request_impl(
&self.client,
self.base_url
.join("/pets.list")
.expect("checked base_url already and path is valid"),
input,
headers,
)
.await
reflectapi::rt::__request_impl(&self.client, "/pets.list", input, headers).await
}
/// Create a new pet
#[tracing::instrument(name = "/pets.create", skip(self, headers))]
Expand All @@ -124,15 +99,7 @@ pub mod interface {
reflectapi::Empty,
reflectapi::rt::Error<super::types::myapi::proto::PetsCreateError, C::Error>,
> {
reflectapi::rt::__request_impl(
&self.client,
self.base_url
.join("/pets.create")
.expect("checked base_url already and path is valid"),
input,
headers,
)
.await
reflectapi::rt::__request_impl(&self.client, "/pets.create", input, headers).await
}
/// Update an existing pet
#[tracing::instrument(name = "/pets.update", skip(self, headers))]
Expand All @@ -144,15 +111,7 @@ pub mod interface {
reflectapi::Empty,
reflectapi::rt::Error<super::types::myapi::proto::PetsUpdateError, C::Error>,
> {
reflectapi::rt::__request_impl(
&self.client,
self.base_url
.join("/pets.update")
.expect("checked base_url already and path is valid"),
input,
headers,
)
.await
reflectapi::rt::__request_impl(&self.client, "/pets.update", input, headers).await
}
/// Remove an existing pet
#[tracing::instrument(name = "/pets.remove", skip(self, headers))]
Expand All @@ -164,15 +123,7 @@ pub mod interface {
reflectapi::Empty,
reflectapi::rt::Error<super::types::myapi::proto::PetsRemoveError, C::Error>,
> {
reflectapi::rt::__request_impl(
&self.client,
self.base_url
.join("/pets.remove")
.expect("checked base_url already and path is valid"),
input,
headers,
)
.await
reflectapi::rt::__request_impl(&self.client, "/pets.remove", input, headers).await
}
#[deprecated(note = "Use pets.remove instead")]
/// Remove an existing pet
Expand All @@ -185,15 +136,7 @@ pub mod interface {
reflectapi::Empty,
reflectapi::rt::Error<super::types::myapi::proto::PetsRemoveError, C::Error>,
> {
reflectapi::rt::__request_impl(
&self.client,
self.base_url
.join("/pets.delete")
.expect("checked base_url already and path is valid"),
input,
headers,
)
.await
reflectapi::rt::__request_impl(&self.client, "/pets.delete", input, headers).await
}
/// Fetch first pet, if any exists
#[tracing::instrument(name = "/pets.get-first", skip(self, headers))]
Expand All @@ -205,15 +148,7 @@ pub mod interface {
std::option::Option<super::types::myapi::model::output::Pet>,
reflectapi::rt::Error<super::types::myapi::proto::UnauthorizedError, C::Error>,
> {
reflectapi::rt::__request_impl(
&self.client,
self.base_url
.join("/pets.get-first")
.expect("checked base_url already and path is valid"),
input,
headers,
)
.await
reflectapi::rt::__request_impl(&self.client, "/pets.get-first", input, headers).await
}
/// Stream of change data capture events for pets
#[tracing::instrument(name = "/pets.cdc-events", skip(self, headers))]
Expand All @@ -229,15 +164,8 @@ pub mod interface {
where
C::Error: Send + 'static,
{
reflectapi::rt::__stream_request_impl(
&self.client,
self.base_url
.join("/pets.cdc-events")
.expect("checked base_url already and path is valid"),
input,
headers,
)
.await
reflectapi::rt::__stream_request_impl(&self.client, "/pets.cdc-events", input, headers)
.await
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion reflectapi-demo/clients/rust/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use reflectapi_demo_client_generated::types::myapi::model::Kind;
use reflectapi_demo_client_generated::types::myapi::proto::Headers;
use reflectapi_demo_client_generated::DemoServerClient;

type Client = DemoServerClient<reqwest::Client>;
type Client = DemoServerClient<reflectapi::rt::ReqwestClient>;

fn headers() -> Headers {
Headers {
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading