Repository: svix/svix-webhooks (language: rust)

Issue:
Bump rust lib to Hyper 1.0
It's actually about the libs

## Old report

## Feature Request

Upgrade axum to 0.7

### Motivation

I have an axum server that uses svix, both as an emitter and a consumer. With the recent changed to 1.0 of the hyper and http crates, a bunch of breaking changes were introduced in the ecosystem. Axum's 0.7 version now supports these 1.0 releases. However, since it uses a different http create version, the validation of webhooks fails due to the HeaderMap's version difference in the function signature.

I have put a lot of effort into migrating our codebase to axum 0.7 and this is the last piece of the puzzle.

### Proposal

Having skimmed through the lib's code, this should not introduce breaking changes, but will probably require custom feature flags like `axum06` and `axum07`.

Since I'm blocked, and you guys are probably busy, I can do these changes and submit a PR; but I might need some guidance into any standards or ways of doing things you guys have.

### Alternatives

Te alternative is to go back to 0.6, which will work for the time being, but I'm assuming this will eventually be needed


Discussion / hints:
It sounds like you're referring to the Rust lib, not the server itself, right?
Yes, the rust lib.

Having just gone through the upgrade on a server with a pretty similar setup (axum+aide), I know the server will be a huge undertaking.

I understand if you guys are planning to upgrade the whole think in one go
Good to know it's a huge undertaking. We were going to do it but the changelog looked significant so we decided to defer it for a bit. :)

We can probably upgrade the libs for now anyway and worry about the server at a later stage.

P.S, what was the most annoying part of the upgrade?
Quick glance: one of our deps (Reqwest) is incompatible with Hyper 1.0 still.
We can take a look whether using the OpenAPI generator with Hyper works when forcing it to use Hyper 1.0.
Hey @aldofunes, you mentioned you'd be open to opening a PR, is it still the case? Just need to update the openapi spec generator to generate Hyper instead of reqwest, and then up the dep requirements of Hyper to >=1 and hope it works. :P
Any updates on this? at least update the lib to be able to use `webhook.verify`
If we don't hear back from @aldofunes about this, we'll take a jab at it next week. Thanks for bumping the issue!
@tasn Any news?
We started looking at it last week. It looks like some of our dependencies, namely the AWS Rust SDK depends on Hyper 0.10 (tracking ticket: https://github.com/awslabs/aws-sdk-rust/issues/977).

Though we'll figure out a way to support both. We have a new team member joining the team on Thursday and I plan for him to fix it.

Fix patch:
diff --git a/rust/.openapi-generator-ignore b/rust/.openapi-generator-ignore
index 159cba8d5..fbac26c1d 100644
--- a/rust/.openapi-generator-ignore
+++ b/rust/.openapi-generator-ignore
@@ -30,5 +30,8 @@
 /.gitlab-ci.yml
 /.travis.yml
 /src/lib.rs
+/src/apis/client.rs
+/src/apis/configuration.rs
+/src/apis/request.rs
 
 /docs/
diff --git a/rust/Cargo.toml b/rust/Cargo.toml
index 8074298b6..153daf6a2 100644
--- a/rust/Cargo.toml
+++ b/rust/Cargo.toml
@@ -11,23 +11,37 @@ license = "MIT"
 keywords = ["svix", "webhooks", "diahook"]
 categories = ["development-tools", "asynchronous", "network-programming", "web-programming"]
 
-# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+[features]
+default = ["http1", "native-tls"]
+
+http1 = ["hyper-util/http1", "hyper-rustls?/http1"]
+http2 = ["hyper-util/http2", "hyper-rustls?/http2"]
+native-tls = ["dep:hyper-tls"]
+rustls-tls = ["dep:hyper-rustls", "hyper-rustls?/rustls-native-certs"]
 
 [dependencies]
-hmac-sha256 = "1"
 base64 = "0.13"
-time = "0.3"
-thiserror = "1.0.30"
-http = "0.2"
-
-# API stuff
+hmac-sha256 = "1"
+http02 = { package = "http", version = "0.2.0" }
+http1 = { package = "http", version = "1.0.0" }
+http-body-util = "0.1.0"
+hyper = "1.1.0"
+hyper-rustls = { version = "0.26.0", optional = true }
+hyper-tls = { version = "0.6.0", optional = true }
+hyper-util = { version = "0.1.3", features = ["client", "client-legacy", "tokio"] }
 serde = "1.0"
 serde_derive = "1.0"
 serde_json = "1.0"
 serde_repr = "0.1"
+thiserror = "1.0.30"
+time = "0.3"
 url = "2.2"
-reqwest = { version = "0.11", features = ["json", "multipart"], default-features = false }
 
-[features]
-default = ["reqwest/default-tls"]
-rustls-tls = ["reqwest/rustls-tls"]
+[package.metadata.cargo-public-api-crates]
+allowed = [
+    "http",
+    "serde",
+    "serde_json",
+    # Used in WebhookError, breaking change to remove from public API.
+    "base64",
+]
diff --git a/rust/openapi-generator-config.json b/rust/openapi-generator-config.json
index 00d05525b..42fdc705b 100644
--- a/rust/openapi-generator-config.json
+++ b/rust/openapi-generator-config.json
@@ -1,4 +1,5 @@
 {
   "packageName": "svix_openapi_internal",
+  "library": "hyper",
   "useSingleRequestParameter": true
 }
diff --git a/rust/src/api.rs b/rust/src/api.rs
index e475279ed..b1651cb63 100644
--- a/rust/src/api.rs
+++ b/rust/src/api.rs
@@ -1,10 +1,13 @@
-use crate::apis::configuration::Configuration;
+use hyper_util::client::legacy::Client as HyperClient;
+use hyper_util::rt::TokioExecutor;
+
 use crate::apis::{
     application_api, authentication_api, background_tasks_api, endpoint_api, event_type_api,
     integration_api, message_api, message_attempt_api, statistics_api,
 };
 use crate::error::Result;
 pub use crate::models::*;
+use crate::Configuration;
 
 const CRATE_VERSION: &str = env!("CARGO_PKG_VERSION");
 
@@ -14,6 +17,7 @@ pub struct SvixOptions {
     pub server_url: Option<String>,
 }
 
+/// Svix API client.
 pub struct Svix {
     cfg: Configuration,
 }
@@ -33,7 +37,7 @@ impl Svix {
             base_path,
             user_agent: Some(format!("svix-libs/{CRATE_VERSION}/rust")),
             bearer_access_token: Some(token),
-            ..Configuration::default()
+            client: HyperClient::builder(TokioExecutor::new()).build(crate::default_connector()),
         };
 
         Self { cfg }
@@ -66,6 +70,10 @@ impl Svix {
     pub fn message_attempt(&self) -> MessageAttempt<'_> {
         MessageAttempt::new(&self.cfg)
     }
+
+    pub fn statistics(&self) -> Statistics<'_> {
+        Statistics::new(&self.cfg)
+    }
 }
 
 #[derive(Default)]
@@ -88,14 +96,14 @@ impl<'a> Authentication<'a> {
         options: Option<PostOptions>,
     ) -> Result<DashboardAccessOut> {
         let options = options.unwrap_or_default();
-        Ok(authentication_api::v1_authentication_dashboard_access(
+        authentication_api::v1_authentication_dashboard_access(
             self.cfg,
             authentication_api::V1AuthenticationDashboardAccessParams {
                 app_id,
                 idempotency_key: options.idempotency_key,
             },
         )
-        .await?)
+        .await
     }
 
     pub async fn app_portal_access(
@@ -105,7 +113,7 @@ impl<'a> Authentication<'a> {
         options: Option<PostOptions>,
     ) -> Result<AppPortalAccessOut> {
         let options = options.unwrap_or_default();
-        Ok(authentication_api::v1_authentication_app_portal_access(
+        authentication_api::v1_authentication_app_portal_access(
             self.cfg,
             authentication_api::V1AuthenticationAppPortalAccessParams {
                 app_id,
@@ -113,16 +121,16 @@ impl<'a> Authentication<'a> {
                 idempotency_key: options.idempotency_key,
             },
         )
-        .await?)
+        .await
     }
 
     pub async fn logout(&self, options: Option<PostOptions>) -> Result<()> {
         let PostOptions { idempotency_key } = options.unwrap_or_default();
-        Ok(authentication_api::v1_authentication_logout(
+        authentication_api::v1_authentication_logout(
             self.cfg,
             authentication_api::V1AuthenticationLogoutParams { idempotency_key },
         )
-        .await?)
+        .await
     }
 }
 
@@ -157,7 +165,7 @@ impl<'a> Application<'a> {
             limit,
             order,
         } = options.unwrap_or_default();
-        Ok(application_api::v1_application_list(
+        application_api::v1_application_list(
             self.cfg,
             application_api::V1ApplicationListParams {
                 iterator,
@@ -165,7 +173,7 @@ impl<'a> Application<'a> {
                 order,
             },
         )
-        .await?)
+        .await
     }
 
     pub async fn create(
@@ -174,7 +182,7 @@ impl<'a> Application<'a> {
         options: Option<PostOptions>,
     ) -> Result<ApplicationOut> {
         let PostOptions { idempotency_key } = options.unwrap_or_default();
-        Ok(application_api::v1_application_create(
+        application_api::v1_application_create(
             self.cfg,
             application_api::V1ApplicationCreateParams {
                 application_in,
@@ -182,7 +190,7 @@ impl<'a> Application<'a> {
                 get_if_exists: None,
             },
         )
-        .await?)
+        .await
     }
 
     pub async fn get_or_create(
@@ -191,7 +199,7 @@ impl<'a> Application<'a> {
         options: Option<PostOptions>,
     ) -> Result<ApplicationOut> {
         let PostOptions { idempotency_key } = options.unwrap_or_default();
-        Ok(application_api::v1_application_create(
+        application_api::v1_application_create(
             self.cfg,
             application_api::V1ApplicationCreateParams {
                 application_in,
@@ -199,15 +207,15 @@ impl<'a> Application<'a> {
                 get_if_exists: Some(true),
             },
         )
-        .await?)
+        .await
     }
 
     pub async fn get(&self, app_id: String) -> Result<ApplicationOut> {
-        Ok(application_api::v1_application_get(
+        application_api::v1_application_get(
             self.cfg,
             application_api::V1ApplicationGetParams { app_id },
         )
-        .await?)
+        .await
     }
 
     pub async fn update(
@@ -216,14 +224,14 @@ impl<'a> Application<'a> {
         application_in: ApplicationIn,
         _: Option<PostOptions>,
     ) -> Result<ApplicationOut> {
-        Ok(application_api::v1_application_update(
+        application_api::v1_application_update(
             self.cfg,
             application_api::V1ApplicationUpdateParams {
                 app_id,
                 application_in,
             },
         )
-        .await?)
+        .await
     }
 
     pub async fn patch(
@@ -232,22 +240,22 @@ impl<'a> Application<'a> {
         application_patch: ApplicationPatch,
         _: Option<PostOptions>,
     ) -> Result<ApplicationOut> {
-        Ok(application_api::v1_application_patch(
+        application_api::v1_application_patch(
             self.cfg,
             application_api::V1ApplicationPatchParams {
                 app_id,
                 application_patch,
             },
         )
-        .await?)
+        .await
     }
 
     pub async fn delete(&self, app_id: String) -> Result<()> {
-        Ok(application_api::v1_application_delete(
+        application_api::v1_application_delete(
             self.cfg,
             application_api::V1ApplicationDeleteParams { app_id },
         )
-        .await?)
+        .await
     }
 }
 
@@ -283,7 +291,7 @@ impl<'a> Endpoint<'a> {
             limit,
             order,
         } = options.unwrap_or_default();
-        Ok(endpoint_api::v1_endpoint_list(
+        endpoint_api::v1_endpoint_list(
             self.cfg,
             endpoint_api::V1EndpointListParams {
                 app_id,
@@ -292,7 +300,7 @@ impl<'a> Endpoint<'a> {
                 limit,
             },
         )
-        .await?)
+        .await
     }
 
     pub async fn create(
@@ -302,7 +310,7 @@ impl<'a> Endpoint<'a> {
         options: Option<PostOptions>,
     ) -> Result<EndpointOut> {
         let PostOptions { idempotency_key } = options.unwrap_or_default();
-        Ok(endpoint_api::v1_endpoint_create(
+        endpoint_api::v1_endpoint_create(
             self.cfg,
             endpoint_api::V1EndpointCreateParams {
                 app_id,
@@ -310,18 +318,18 @@ impl<'a> Endpoint<'a> {
                 idempotency_key,
             },
         )
-        .await?)
+        .await
     }
 
     pub async fn get(&self, app_id: String, endpoint_id: String) -> Result<EndpointOut> {
-        Ok(endpoint_api::v1_endpoint_get(
+        endpoint_api::v1_endpoint_get(
             self.cfg,
             endpoint_api::V1EndpointGetParams {
                 app_id,
                 endpoint_id,
             },
         )
-        .await?)
+        .await
     }
 
     pub async fn update(
@@ -331,7 +339,7 @@ impl<'a> Endpoint<'a> {
         endpoint_update: EndpointUpdate,
         _: Option<PostOptions>,
     ) -> Result<EndpointOut> {
-        Ok(endpoint_api::v1_endpoint_update(
+        endpoint_api::v1_endpoint_update(
             self.cfg,
             endpoint_api::V1EndpointUpdateParams {
                 app_id,
@@ -339,7 +347,7 @@ impl<'a> Endpoint<'a> {
                 endpoint_update,
             },
         )
-        .await?)
+        .await
     }
 
     pub async fn patch(
@@ -349,7 +357,7 @@ impl<'a> Endpoint<'a> {
         endpoint_patch: EndpointPatch,
         _: Option<PostOptions>,
     ) -> Result<EndpointOut> {
-        Ok(endpoint_api::v1_endpoint_patch(
+        endpoint_api::v1_endpoint_patch(
             self.cfg,
             endpoint_api::V1EndpointPatchParams {
                 app_id,
@@ -357,18 +365,18 @@ impl<'a> Endpoint<'a> {
                 endpoint_patch,
             },
         )
-        .await?)
+        .await
     }
 
     pub async fn delete(&self, app_id: String, endpoint_id: String) -> Result<()> {
-        Ok(endpoint_api::v1_endpoint_delete(
+        endpoint_api::v1_endpoint_delete(
             self.cfg,
             endpoint_api::V1EndpointDeleteParams {
                 app_id,
                 endpoint_id,
             },
         )
-        .await?)
+        .await
     }
 
     pub async fn get_secret(
@@ -376,14 +384,14 @@ impl<'a> Endpoint<'a> {
         app_id: String,
         endpoint_id: String,
     ) -> Result<EndpointSecretOut> {
-        Ok(endpoint_api::v1_endpoint_get_secret(
+        endpoint_api::v1_endpoint_get_secret(
             self.cfg,
             endpoint_api::V1EndpointGetSecretParams {
                 app_id,
                 endpoint_id,
             },
         )
-        .await?)
+        .await
     }
 
     pub async fn rotate_secret(
@@ -392,7 +400,7 @@ impl<'a> Endpoint<'a> {
         endpoint_id: String,
         endpoint_secret_rotate_in: EndpointSecretRotateIn,
     ) -> Result<()> {
-        Ok(endpoint_api::v1_endpoint_rotate_secret(
+        endpoint_api::v1_endpoint_rotate_secret(
             self.cfg,
             endpoint_api::V1EndpointRotateSecretParams {
                 app_id,
@@ -401,7 +409,7 @@ impl<'a> Endpoint<'a> {
                 idempotency_key: None,
             },
         )
-        .await?)
+        .await
     }
 
     pub async fn recover(
@@ -428,14 +436,14 @@ impl<'a> Endpoint<'a> {
         app_id: String,
         endpoint_id: String,
     ) -> Result<EndpointHeadersOut> {
-        Ok(endpoint_api::v1_endpoint_get_headers(
+        endpoint_api::v1_endpoint_get_headers(
             self.cfg,
             endpoint_api::V1EndpointGetHeadersParams {
                 app_id,
                 endpoint_id,
             },
         )
-        .await?)
+        .await
     }
 
     pub async fn update_headers(
@@ -444,7 +452,7 @@ impl<'a> Endpoint<'a> {
         endpoint_id: String,
         endpoint_headers_in: EndpointHeadersIn,
     ) -> Result<()> {
-        Ok(endpoint_api::v1_endpoint_update_headers(
+        endpoint_api::v1_endpoint_update_headers(
             self.cfg,
             endpoint_api::V1EndpointUpdateHeadersParams {
                 app_id,
@@ -452,7 +460,7 @@ impl<'a> Endpoint<'a> {
                 endpoint_headers_in,
             },
         )
-        .await?)
+        .await
     }
 
     pub async fn patch_headers(
@@ -461,7 +469,7 @@ impl<'a> Endpoint<'a> {
         endpoint_id: String,
         endpoint_headers_patch_in: EndpointHeadersPatchIn,
     ) -> Result<()> {
-        Ok(endpoint_api::v1_endpoint_patch_headers(
+        endpoint_api::v1_endpoint_patch_headers(
             self.cfg,
             endpoint_api::V1EndpointPatchHeadersParams {
                 app_id,
@@ -469,7 +477,7 @@ impl<'a> Endpoint<'a> {
                 endpoint_headers_patch_in,
             },
         )
-        .await?)
+        .await
     }
 
     pub async fn get_stats(
@@ -479,7 +487,7 @@ impl<'a> Endpoint<'a> {
         options: Option<EndpointStatsOptions>,
     ) -> Result<EndpointStats> {
         let EndpointStatsOptions { since, until } = options.unwrap_or_default();
-        Ok(endpoint_api::v1_endpoint_get_stats(
+        endpoint_api::v1_endpoint_get_stats(
             self.cfg,
             endpoint_api::V1EndpointGetStatsParams {
                 app_id,
@@ -488,7 +496,7 @@ impl<'a> Endpoint<'a> {
                 until,
             },
         )
-        .await?)
+        .await
     }
 
     pub async fn replay_missing(
@@ -517,14 +525,14 @@ impl<'a> Endpoint<'a> {
         app_id: String,
         endpoint_id: String,
     ) -> Result<EndpointTransformationOut> {
-        Ok(endpoint_api::v1_endpoint_transformation_get(
+        endpoint_api::v1_endpoint_transformation_get(
             self.cfg,
             endpoint_api::V1EndpointTransformationGetParams {
                 app_id,
                 endpoint_id,
             },
         )
-        .await?)
+        .await
     }
 
     pub async fn transformation_partial_update(
@@ -553,7 +561,7 @@ impl<'a> Endpoint<'a> {
         options: Option<PostOptions>,
     ) -> Result<MessageOut> {
         let PostOptions { idempotency_key } = options.unwrap_or_default();
-        Ok(endpoint_api::v1_endpoint_send_example(
+        endpoint_api::v1_endpoint_send_example(
             self.cfg,
             endpoint_api::V1EndpointSendExampleParams {
                 app_id,
@@ -562,7 +570,7 @@ impl<'a> Endpoint<'a> {
                 idempotency_key,
             },
         )
-        .await?)
+        .await
     }
 }
 
@@ -583,7 +591,7 @@ impl<'a> Integration<'a> {
         options: Option<IntegrationListOptions>,
     ) -> Result<ListResponseIntegrationOut> {
         let IntegrationListOptions { iterator, limit } = options.unwrap_or_default();
-        Ok(integration_api::v1_integration_list(
+        integration_api::v1_integration_list(
             self.cfg,
             integration_api::V1IntegrationListParams {
                 app_id,
@@ -591,7 +599,7 @@ impl<'a> Integration<'a> {
                 limit,
             },
         )
-        .await?)
+        .await
     }
 
     pub async fn create(
@@ -601,7 +609,7 @@ impl<'a> Integration<'a> {
         options: Option<PostOptions>,
     ) -> Result<IntegrationOut> {
         let PostOptions { idempotency_key } = options.unwrap_or_default();
-        Ok(integration_api::v1_integration_create(
+        integration_api::v1_integration_create(
             self.cfg,
             integration_api::V1IntegrationCreateParams {
                 app_id,
@@ -609,15 +617,15 @@ impl<'a> Integration<'a> {
                 idempotency_key,
             },
         )
-        .await?)
+        .await
     }
 
     pub async fn get(&self, app_id: String, integ_id: String) -> Result<IntegrationOut> {
-        Ok(integration_api::v1_integration_get(
+        integration_api::v1_integration_get(
             self.cfg,
             integration_api::V1IntegrationGetParams { app_id, integ_id },
         )
-        .await?)
+        .await
     }
 
     pub async fn update(
@@ -627,7 +635,7 @@ impl<'a> Integration<'a> {
         integration_update: IntegrationUpdate,
         _: Option<PostOptions>,
     ) -> Result<IntegrationOut> {
-        Ok(integration_api::v1_integration_update(
+        integration_api::v1_integration_update(
             self.cfg,
             integration_api::V1IntegrationUpdateParams {
                 app_id,
@@ -635,27 +643,27 @@ impl<'a> Integration<'a> {
                 integration_update,
             },
         )
-        .await?)
+        .await
     }
 
     pub async fn delete(&self, app_id: String, integ_id: String) -> Result<()> {
-        Ok(integration_api::v1_integration_delete(
+        integration_api::v1_integration_delete(
             self.cfg,
             integration_api::V1IntegrationDeleteParams { app_id, integ_id },
         )
-        .await?)
+        .await
     }
 
     pub async fn get_key(&self, app_id: String, integ_id: String) -> Result<IntegrationKeyOut> {
-        Ok(integration_api::v1_integration_get_key(
+        integration_api::v1_integration_get_key(
             self.cfg,
             integration_api::V1IntegrationGetKeyParams { app_id, integ_id },
         )
-        .await?)
+        .await
     }
 
     pub async fn rotate_key(&self, app_id: String, integ_id: String) -> Result<IntegrationKeyOut> {
-        Ok(integration_api::v1_integration_rotate_key(
+        integration_api::v1_integration_rotate_key(
             self.cfg,
             integration_api::V1IntegrationRotateKeyParams {
                 app_id,
@@ -663,7 +671,7 @@ impl<'a> Integration<'a> {
                 idempotency_key: None,
             },
         )
-        .await?)
+        .await
     }
 }
 
@@ -694,7 +702,7 @@ impl<'a> EventType<'a> {
             with_content,
             include_archived,
         } = options.unwrap_or_default();
-        Ok(event_type_api::v1_event_type_list(
+        event_type_api::v1_event_type_list(
             self.cfg,
             event_type_api::V1EventTypeListParams {
                 iterator,
@@ -704,7 +712,7 @@ impl<'a> EventType<'a> {
                 order: None,
             },
         )
-        .await?)
+        .await
     }
 
     pub async fn create(
@@ -713,22 +721,22 @@ impl<'a> EventType<'a> {
         options: Option<PostOptions>,
     ) -> Result<EventTypeOut> {
         let PostOptions { idempotency_key } = options.unwrap_or_default();
-        Ok(event_type_api::v1_event_type_create(
+        event_type_api::v1_event_type_create(
             self.cfg,
             event_type_api::V1EventTypeCreateParams {
                 event_type_in,
                 idempotency_key,
             },
         )
-        .await?)
+        .await
     }
 
     pub async fn get(&self, event_type_name: String) -> Result<EventTypeOut> {
-        Ok(event_type_api::v1_event_type_get(
+        event_type_api::v1_event_type_get(
             self.cfg,
             event_type_api::V1EventTypeGetParams { event_type_name },
         )
-        .await?)
+        .await
     }
 
     pub async fn update(
@@ -737,14 +745,14 @@ impl<'a> EventType<'a> {
         event_type_update: EventTypeUpdate,
         _: Option<PostOptions>,
     ) -> Result<EventTypeOut> {
-        Ok(event_type_api::v1_event_type_update(
+        event_type_api::v1_event_type_update(
             self.cfg,
             event_type_api::V1EventTypeUpdateParams {
                 event_type_name,
                 event_type_update,
             },
         )
-        .await?)
+        .await
     }
 
     pub async fn patch(
@@ -753,25 +761,25 @@ impl<'a> EventType<'a> {
         event_type_patch: EventTypePatch,
         _: Option<PostOptions>,
     ) -> Result<EventTypeOut> {
-        Ok(event_type_api::v1_event_type_patch(
+        event_type_api::v1_event_type_patch(
             self.cfg,
             event_type_api::V1EventTypePatchParams {
                 event_type_name,
                 event_type_patch,
             },
         )
-        .await?)
+        .await
     }
 
     pub async fn delete(&self, event_type_name: String) -> Result<()> {
-        Ok(event_type_api::v1_event_type_delete(
+        event_type_api::v1_event_type_delete(
             self.cfg,
             event_type_api::V1EventTypeDeleteParams {
                 event_type_name,
                 expunge: None,
             },
         )
-        .await?)
+        .await
     }
 
     pub async fn import_openapi(
@@ -780,14 +788,14 @@ impl<'a> EventType<'a> {
         options: Option<PostOptions>,
     ) -> Result<EventTypeImportOpenApiOut> {
         let PostOptions { idempotency_key } = options.unwrap_or_default();
-        Ok(event_type_api::v1_event_type_import_openapi(
+        event_type_api::v1_event_type_import_openapi(
             self.cfg,
             event_type_api::V1EventTypeImportOpenapiParams {
                 event_type_import_open_api_in,
                 idempotency_key,
             },
         )
-        .await?)
+        .await
     }
 }
 
@@ -830,7 +838,7 @@ impl<'a> Message<'a> {
             with_content,
             tag,
         } = options.unwrap_or_default();
-        Ok(message_api::v1_message_list(
+        message_api::v1_message_list(
             self.cfg,
             message_api::V1MessageListParams {
                 app_id,
@@ -844,7 +852,7 @@ impl<'a> Message<'a> {
                 tag,
             },
         )
-        .await?)
+        .await
     }
 
     pub async fn create(
@@ -854,7 +862,7 @@ impl<'a> Message<'a> {
         options: Option<PostOptions>,
     ) -> Result<MessageOut> {
         let PostOptions { idempotency_key } = options.unwrap_or_default();
-        Ok(message_api::v1_message_create(
+        message_api::v1_message_create(
             self.cfg,
             message_api::V1MessageCreateParams {
                 app_id,
@@ -863,11 +871,11 @@ impl<'a> Message<'a> {
                 with_content: None,
             },
         )
-        .await?)
+        .await
     }
 
     pub async fn get(&self, app_id: String, msg_id: String) -> Result<MessageOut> {
-        Ok(message_api::v1_message_get(
+        message_api::v1_message_get(
             self.cfg,
             message_api::V1MessageGetParams {
                 app_id,
@@ -875,15 +883,15 @@ impl<'a> Message<'a> {
                 with_content: None,
             },
         )
-        .await?)
+        .await
     }
 
     pub async fn expunge_content(&self, app_id: String, msg_id: String) -> Result<()> {
-        Ok(message_api::v1_message_expunge_content(
+        message_api::v1_message_expunge_content(
             self.cfg,
             message_api::V1MessageExpungeContentParams { msg_id, app_id },
         )
-        .await?)
+        .await
     }
 }
 
@@ -952,7 +960,7 @@ impl<'a> MessageAttempt<'a> {
             endpoint_id,
             with_content,
         } = options.unwrap_or_default();
-        Ok(message_attempt_api::v1_message_attempt_list_by_msg(
+        message_attempt_api::v1_message_attempt_list_by_msg(
             self.cfg,
             message_attempt_api::V1MessageAttemptListByMsgParams {
                 app_id,
@@ -970,7 +978,7 @@ impl<'a> MessageAttempt<'a> {
                 with_content,
             },
         )
-        .await?)
+        .await
     }
 
     pub async fn list_by_endpoint(
@@ -993,7 +1001,7 @@ impl<'a> MessageAttempt<'a> {
             with_content,
             with_msg,
         } = options.unwrap_or_default();
-        Ok(message_attempt_api::v1_message_attempt_list_by_endpoint(
+        message_attempt_api::v1_message_attempt_list_by_endpoint(
             self.cfg,
             message_attempt_api::V1MessageAttemptListByEndpointParams {
                 app_id,
@@ -1011,7 +1019,7 @@ impl<'a> MessageAttempt<'a> {
                 with_msg,
             },
         )
-        .await?)
+        .await
     }
 
     pub async fn list_attempted_messages(
@@ -1033,25 +1041,23 @@ impl<'a> MessageAttempt<'a> {
             with_content,
             endpoint_id: _,
         } = options.unwrap_or_default();
-        Ok(
-            message_attempt_api::v1_message_attempt_list_attempted_messages(
-                self.cfg,
-                message_attempt_api::V1MessageAttemptListAttemptedMessagesParams {
-                    app_id,
-                    endpoint_id,
-                    iterator,
-                    limit,
-                    before,
-                    after,
-                    channel,
-                    tag,
-                    status,
-                    with_content,
-                    event_types,
-                },
-            )
-            .await?,
+        message_attempt_api::v1_message_attempt_list_attempted_messages(
+            self.cfg,
+            message_attempt_api::V1MessageAttemptListAttemptedMessagesParams {
+                app_id,
+                endpoint_id,
+                iterator,
+                limit,
+                before,
+                after,
+                channel,
+                tag,
+                status,
+                with_content,
+                event_types,
+            },
         )
+        .await
     }
 
     pub async fn list_attempted_destinations(
@@ -1061,18 +1067,16 @@ impl<'a> MessageAttempt<'a> {
         options: Option<ListOptions>,
     ) -> Result<ListResponseMessageEndpointOut> {
         let ListOptions { iterator, limit } = options.unwrap_or_default();
-        Ok(
-            message_attempt_api::v1_message_attempt_list_attempted_destinations(
-                self.cfg,
-                message_attempt_api::V1MessageAttemptListAttemptedDestinationsParams {
-                    app_id,
-                    msg_id,
-                    iterator,
-                    limit,
-                },
-            )
-            .await?,
+        message_attempt_api::v1_message_attempt_list_attempted_destinations(
+            self.cfg,
+            message_attempt_api::V1MessageAttemptListAttemptedDestinationsParams {
+                app_id,
+                msg_id,
+                iterator,
+                limit,
+            },
         )
+        .await
     }
 
     pub async fn list_attempts_for_endpoint(
@@ -1095,25 +1099,23 @@ impl<'a> MessageAttempt<'a> {
             endpoint_id: _,
             with_content: _,
         } = options.unwrap_or_default();
-        Ok(
-            message_attempt_api::v1_message_attempt_list_by_endpoint_deprecated(
-                self.cfg,
-                message_attempt_api::V1MessageAttemptListByEndpointDeprecatedParams {
-                    app_id,
-                    endpoint_id,
-                    msg_id,
-                    iterator,
-                    limit,
-                    event_types,
-                    before,
-                    after,
-                    channel,
-                    tag,
-                    status,
-                },
-            )
-            .await?,
+        message_attempt_api::v1_message_attempt_list_by_endpoint_deprecated(
+            self.cfg,
+            message_attempt_api::V1MessageAttemptListByEndpointDeprecatedParams {
+                app_id,
+                endpoint_id,
+                msg_id,
+                iterator,
+                limit,
+                event_types,
+                before,
+                after,
+                channel,
+                tag,
+                status,
+            },
         )
+        .await
     }
 
     pub async fn get(
@@ -1122,7 +1124,7 @@ impl<'a> MessageAttempt<'a> {
         msg_id: String,
         attempt_id: String,
     ) -> Result<MessageAttemptOut> {
-        Ok(message_attempt_api::v1_message_attempt_get(
+        message_attempt_api::v1_message_attempt_get(
             self.cfg,
             message_attempt_api::V1MessageAttemptGetParams {
                 app_id,
@@ -1130,11 +1132,11 @@ impl<'a> MessageAttempt<'a> {
                 attempt_id,
             },
         )
-        .await?)
+        .await
     }
 
     pub async fn resend(&self, app_id: String, msg_id: String, endpoint_id: String) -> Result<()> {
-        Ok(message_attempt_api::v1_message_attempt_resend(
+        message_attempt_api::v1_message_attempt_resend(
             self.cfg,
             message_attempt_api::V1MessageAttemptResendParams {
                 app_id,
@@ -1143,7 +1145,7 @@ impl<'a> MessageAttempt<'a> {
                 idempotency_key: None,
             },
         )
-        .await?)
+        .await
     }
 
     pub async fn expunge_content(
@@ -1152,7 +1154,7 @@ impl<'a> MessageAttempt<'a> {
         msg_id: String,
         attempt_id: String,
     ) -> Result<()> {
-        Ok(message_attempt_api::v1_message_attempt_expunge_content(
+        message_attempt_api::v1_message_attempt_expunge_content(
             self.cfg,
             message_attempt_api::V1MessageAttemptExpungeContentParams {
                 app_id,
@@ -1160,7 +1162,7 @@ impl<'a> MessageAttempt<'a> {
                 attempt_id,
             },
         )
-        .await?)
+        .await
     }
 }
 
@@ -1189,7 +1191,7 @@ impl<'a> BackgroundTask<'a> {
             status,
             task,
         } = options.unwrap_or_default();
-        Ok(background_tasks_api::list_background_tasks(
+        background_tasks_api::list_background_tasks(
             self.cfg,
             background_tasks_api::ListBackgroundTasksParams {
                 status,
@@ -1199,15 +1201,15 @@ impl<'a> BackgroundTask<'a> {
                 order,
             },
         )
-        .await?)
+        .await
     }
 
     pub async fn get(&self, task_id: String) -> Result<BackgroundTaskOut> {
-        Ok(background_tasks_api::get_background_task(
+        background_tasks_api::get_background_task(
             self.cfg,
             background_tasks_api::GetBackgroundTaskParams { task_id },
         )
-        .await?)
+        .await
     }
 }
 
@@ -1222,7 +1224,7 @@ pub struct AggregateAppStatsOptions {
 }
 
 impl<'a> Statistics<'a> {
-    pub fn new(cfg: &'a Configuration) -> Self {
+    fn new(cfg: &'a Configuration) -> Self {
         Self { cfg }
     }
 
@@ -1244,10 +1246,10 @@ impl<'a> Statistics<'a> {
             },
             idempotency_key: options.idempotency_key,
         };
-        Ok(statistics_api::v1_statistics_aggregate_app_stats(self.cfg, params).await?)
+        statistics_api::v1_statistics_aggregate_app_stats(self.cfg, params).await
     }
 
     pub async fn aggregate_event_types(&self) -> Result<AggregateEventTypesOut> {
-        Ok(statistics_api::v1_statistics_aggregate_event_types(self.cfg).await?)
+        statistics_api::v1_statistics_aggregate_event_types(self.cfg).await
     }
 }
diff --git a/rust/src/error.rs b/rust/src/error.rs
index e045b73cf..a673c8637 100644
--- a/rust/src/error.rs
+++ b/rust/src/error.rs
@@ -1,10 +1,12 @@
 // SPDX-FileCopyrightText: © 2022 Svix Authors
 // SPDX-License-Identifier: MIT
 
-use std::error;
 use std::fmt;
 
-use http::status;
+use http_body_util::BodyExt;
+use hyper::body::Incoming;
+
+use crate::http1_to_02_status_code;
 
 pub type Result<T> = std::result::Result<T, Error>;
 
@@ -19,51 +21,53 @@ pub enum Error {
     Validation(HttpErrorContent<crate::models::HttpValidationError>),
 }
 
-#[derive(Debug, Clone)]
-pub struct HttpErrorContent<T> {
-    pub status: reqwest::StatusCode,
-    pub payload: Option<T>,
-}
+impl Error {
+    pub(crate) fn generic(err: impl std::error::Error) -> Self {
+        Self::Generic(err.to_string())
+    }
 
-impl fmt::Display for Error {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        match self {
-            Error::Generic(s) => s.fmt(f),
-            Error::Http(e) => format!("Http error (status={}) {:?}", e.status, e.payload).fmt(f),
-            Error::Validation(e) => format!("Validation error {:?}", e.payload).fmt(f),
+    pub(crate) async fn from_response(status_code: http1::StatusCode, body: Incoming) -> Self {
+        match body.collect().await {
+            Ok(collected) => {
+                let bytes = collected.to_bytes();
+                if status_code == http1::StatusCode::UNPROCESSABLE_ENTITY {
+                    Self::Validation(HttpErrorContent {
+                        status: http02::StatusCode::UNPROCESSABLE_ENTITY,
+                        payload: serde_json::from_slice(&bytes).ok(),
+                    })
+                } else {
+                    Error::Http(HttpErrorContent {
+                        status: http1_to_02_status_code(status_code),
+                        payload: serde_json::from_slice(&bytes).ok(),
+                    })
+                }
+            }
+            Err(e) => Self::Generic(e.to_string()),
         }
     }
 }
 
+// TODO: Remove for v2.0 of the library (very uncommon impl for an error type)
 impl From<Error> for String {
-    fn from(err: Error) -> String {
+    fn from(err: Error) -> Self {
         err.to_string()
     }
 }
 
-impl error::Error for Error {
-    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
-        None
+impl fmt::Display for Error {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        match self {
+            Error::Generic(s) => s.fmt(f),
+            Error::Http(e) => format!("Http error (status={}) {:?}", e.status, e.payload).fmt(f),
+            Error::Validation(e) => format!("Validation error {:?}", e.payload).fmt(f),
+        }
     }
 }
 
-impl<T> From<crate::apis::Error<T>> for Error {
-    fn from(err: crate::apis::Error<T>) -> Error {
-        match err {
-            crate::apis::Error::ResponseError(e) => {
-                if e.status == status::StatusCode::UNPROCESSABLE_ENTITY {
-                    Error::Validation(HttpErrorContent {
-                        status: e.status,
-                        payload: serde_json::from_str(&e.content).ok(),
-                    })
-                } else {
-                    Error::Http(HttpErrorContent {
-                        status: e.status,
-                        payload: serde_json::from_str(&e.content).ok(),
-                    })
-                }
-            }
-            _ => Error::Generic(err.to_string()),
-        }
-    }
+impl std::error::Error for Error {}
+
+#[derive(Debug, Clone)]
+pub struct HttpErrorContent<T> {
+    pub status: http02::StatusCode,
+    pub payload: Option<T>,
 }
diff --git a/rust/src/lib.rs b/rust/src/lib.rs
index 9ea869d7d..80cde286a 100644
--- a/rust/src/lib.rs
+++ b/rust/src/lib.rs
@@ -1,9 +1,17 @@
 // SPDX-FileCopyrightText: © 2022 Svix Authors
 // SPDX-License-Identifier: MIT
 
+//! Rust client library for Svix.
+//!
+//! The main entry points of this library are the API client [`api::Svix`], and
+//! [`webhooks::Webhook`].
+
 #![warn(clippy::all)]
 #![forbid(unsafe_code)]
 
+use hyper::body::Bytes;
+use hyper_util::client::legacy::{connect::HttpConnector, Client as HyperClient};
+
 #[macro_use]
 extern crate serde_derive;
 #[macro_use]
@@ -11,11 +19,61 @@ extern crate serde_repr;
 
 pub mod api;
 pub mod error;
+mod request;
 pub mod webhooks;
 
 #[rustfmt::skip]
-#[allow(dead_code)]
+#[allow(dead_code, clippy::all)]
 mod apis;
 #[rustfmt::skip]
-#[allow(dead_code)]
+#[allow(dead_code, clippy::all)]
 mod models;
+
+struct Configuration {
+    pub base_path: String,
+    pub user_agent: Option<String>,
+    pub client: HyperClient<Connector, http_body_util::Full<Bytes>>,
+    pub bearer_access_token: Option<String>,
+}
+
+// If no TLS backend is enabled, use plain http connector.
+#[cfg(not(any(feature = "native-tls", feature = "rustls-tls")))]
+type Connector = HttpConnector;
+
+// If only native TLS is enabled, use that.
+#[cfg(all(feature = "native-tls", not(feature = "rustls-tls")))]
+type Connector = hyper_tls::HttpsConnector<HttpConnector>;
+
+// If rustls is enabled, use that.
+#[cfg(feature = "rustls-tls")]
+type Connector = hyper_rustls::HttpsConnector<HttpConnector>;
+
+fn default_connector() -> Connector {
+    #[cfg(not(any(feature = "native-tls", feature = "rustls-tls")))]
+    return hyper_util::client::legacy::connect::HttpConnector::new();
+
+    #[cfg(all(feature = "native-tls", not(feature = "rustls-tls")))]
+    return hyper_tls::HttpsConnector::new();
+
+    #[cfg(feature = "rustls-tls")]
+    {
+        let builder = hyper_rustls::HttpsConnectorBuilder::new()
+            .with_native_roots()
+            .unwrap()
+            .https_or_http();
+
+        #[cfg(feature = "http1")]
+        let builder = builder.enable_http1();
+
+        #[cfg(feature = "http2")]
+        let builder = builder.enable_http2();
+
+        builder.build()
+    }
+}
+
+/// Convert a `StatusCode` from the http crate v1 to one from the http crate v0.2.
+fn http1_to_02_status_code(code: http1::StatusCode) -> http02::StatusCode {
+    http02::StatusCode::from_u16(code.as_u16())
+        .expect("both versions of the http crate enforce the same numerical limits for StatusCode")
+}
diff --git a/rust/src/request.rs b/rust/src/request.rs
new file mode 100644
index 000000000..f1bb033ae
--- /dev/null
+++ b/rust/src/request.rs
@@ -0,0 +1,183 @@
+// Modified version of the file openapi-generator would usually put in apis/request.rs
+
+use std::collections::HashMap;
+
+use http1::header::{HeaderValue, AUTHORIZATION, CONTENT_LENGTH, CONTENT_TYPE, USER_AGENT};
+use http_body_util::{BodyExt as _, Full};
+use serde::de::DeserializeOwned;
+
+use crate::{error::Error, Configuration};
+
+#[allow(dead_code)]
+pub(crate) enum Auth {
+    None,
+    Bearer,
+}
+
+/// If the authorization type is unspecified then it will be automatically detected based
+/// on the configuration. This functionality is useful when the OpenAPI definition does not
+/// include an authorization scheme.
+pub(crate) struct Request {
+    auth: Option<Auth>,
+    method: http1::Method,
+    path: String,
+    query_params: HashMap<String, String>,
+    no_return_type: bool,
+    path_params: HashMap<String, String>,
+    form_params: HashMap<String, String>,
+    header_params: HashMap<String, String>,
+    // TODO: multiple body params are possible technically, but not supported here.
+    serialized_body: Option<String>,
+}
+
+#[allow(dead_code)]
+impl Request {
+    pub fn new(method: http1::Method, path: String) -> Self {
+        Request {
+            auth: None,
+            method,
+            path,
+            query_params: HashMap::new(),
+            path_params: HashMap::new(),
+            form_params: HashMap::new(),
+            header_params: HashMap::new(),
+            serialized_body: None,
+            no_return_type: false,
+        }
+    }
+
+    pub fn with_body_param<T: serde::Serialize>(mut self, param: T) -> Self {
+        self.serialized_body = Some(serde_json::to_string(&param).unwrap());
+        self
+    }
+
+    pub fn with_header_param(mut self, basename: String, param: String) -> Self {
+        self.header_params.insert(basename, param);
+        self
+    }
+
+    #[allow(unused)]
+    pub fn with_query_param(mut self, basename: String, param: String) -> Self {
+        self.query_params.insert(basename, param);
+        self
+    }
+
+    #[allow(unused)]
+    pub fn with_path_param(mut self, basename: String, param: String) -> Self {
+        self.path_params.insert(basename, param);
+        self
+    }
+
+    #[allow(unused)]
+    pub fn with_form_param(mut self, basename: String, param: String) -> Self {
+        self.form_params.insert(basename, param);
+        self
+    }
+
+    pub fn returns_nothing(mut self) -> Self {
+        self.no_return_type = true;
+        self
+    }
+
+    pub fn with_auth(mut self, auth: Auth) -> Self {
+        self.auth = Some(auth);
+        self
+    }
+
+    pub async fn execute<T: DeserializeOwned>(self, conf: &Configuration) -> Result<T, Error> {
+        let mut query_string = ::url::form_urlencoded::Serializer::new("".to_owned());
+
+        let mut path = self.path;
+        for (k, v) in self.path_params {
+            // replace {id} with the value of the id path param
+            path = path.replace(&format!("{{{k}}}"), &v);
+        }
+
+        for (key, val) in self.query_params {
+            query_string.append_pair(&key, &val);
+        }
+
+        let mut uri = format!("{}{}", conf.base_path, path);
+
+        let query_string_str = query_string.finish();
+        if !query_string_str.is_empty() {
+            uri += "?";
+            uri += &query_string_str;
+        }
+
+        let mut req_builder = http1::Request::builder().uri(uri).method(self.method);
+
+        // Detect the authorization type if it hasn't been set.
+        let auth = self.auth.unwrap_or_else(|| {
+            if conf.bearer_access_token.is_some() {
+                Auth::Bearer
+            } else {
+                Auth::None
+            }
+        });
+        match auth {
+            Auth::Bearer => {
+                if let Some(token) = &conf.bearer_access_token {
+                    req_builder = req_builder.header(AUTHORIZATION, format!("Bearer {token}"));
+                }
+            }
+            Auth::None => {}
+        }
+
+        if let Some(user_agent) = &conf.user_agent {
+            req_builder = req_builder.header(
+                USER_AGENT,
+                HeaderValue::from_str(user_agent).map_err(Error::generic)?,
+            );
+        }
+
+        for (k, v) in self.header_params {
+            req_builder = req_builder.header(&k, v);
+        }
+
+        let req_headers = req_builder.headers_mut().unwrap();
+        let request = if !self.form_params.is_empty() {
+            req_headers.insert(
+                CONTENT_TYPE,
+                HeaderValue::from_static("application/x-www-form-urlencoded"),
+            );
+            let mut enc = ::url::form_urlencoded::Serializer::new(String::new());
+            for (k, v) in self.form_params {
+                enc.append_pair(&k, &v);
+            }
+            req_builder
+                .body(Full::from(enc.finish()))
+                .map_err(Error::generic)?
+        } else if let Some(body) = self.serialized_body {
+            req_headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));
+            req_headers.insert(CONTENT_LENGTH, body.len().into());
+            req_builder.body(Full::from(body)).map_err(Error::generic)?
+        } else {
+            req_builder.body(Full::default()).map_err(Error::generic)?
+        };
+
+        let no_return_type = self.no_return_type;
+        let response = conf.client.request(request).await.map_err(Error::generic)?;
+
+        let status = response.status();
+        if !status.is_success() {
+            Err(Error::from_response(status, response.into_body()).await)
+        } else if no_return_type {
+            // This is a hack; if there's no_ret_type, U is (), but serde_json gives an
+            // error when deserializing "" into (), so deserialize 'null' into it
+            // instead.
+            // An alternate option would be to require U: Default, and then return
+            // U::default() here instead since () implements that, but then we'd
+            // need to impl default for all models.
+            Ok(serde_json::from_str("null").expect("serde null value"))
+        } else {
+            let bytes = response
+                .into_body()
+                .collect()
+                .await
+                .map_err(Error::generic)?
+                .to_bytes();
+            Ok(serde_json::from_slice(&bytes).map_err(Error::generic)?)
+        }
+    }
+}
diff --git a/rust/src/webhooks.rs b/rust/src/webhooks.rs
index acf8bfe39..c227b707f 100644
--- a/rust/src/webhooks.rs
+++ b/rust/src/webhooks.rs
@@ -1,7 +1,6 @@
 // SPDX-FileCopyrightText: © 2022 Svix Authors
 // SPDX-License-Identifier: MIT
 
-use http::HeaderMap;
 use time::OffsetDateTime;
 
 #[derive(thiserror::Error, Debug)]
@@ -57,7 +56,7 @@ impl Webhook {
         Ok(Webhook { key: secret })
     }
 
-    pub fn verify(&self, payload: &[u8], headers: &HeaderMap) -> Result<(), WebhookError> {
+    pub fn verify<HM: HeaderMap>(&self, payload: &[u8], headers: &HM) -> Result<(), WebhookError> {
         let msg_id = Self::get_header(headers, SVIX_MSG_ID_KEY, UNBRANDED_MSG_ID_KEY, "id")?;
         let msg_signature = Self::get_header(
             headers,
@@ -112,18 +111,20 @@ impl Webhook {
         Ok(format!("{SIGNATURE_VERSION},{encoded}"))
     }
 
-    fn get_header<'a>(
-        headers: &'a HeaderMap,
+    fn get_header<'a, HM: HeaderMap>(
+        headers: &'a HM,
         svix_hdr: &'static str,
         unbranded_hdr: &'static str,
         err_name: &'static str,
     ) -> Result<&'a str, WebhookError> {
+        use private::HeaderValueSealed as _;
+
         headers
-            .get(svix_hdr)
-            .or_else(|| headers.get(unbranded_hdr))
+            ._get(svix_hdr)
+            .or_else(|| headers._get(unbranded_hdr))
             .ok_or(WebhookError::MissingHeader(err_name))?
-            .to_str()
-            .map_err(|_| WebhookError::InvalidHeader(err_name))
+            ._to_str()
+            .ok_or(WebhookError::InvalidHeader(err_name))
     }
 
     fn parse_timestamp(hdr: &str) -> Result<i64, WebhookError> {
@@ -142,14 +143,55 @@ impl Webhook {
     }
 }
 
+/// Trait to abstract over the `HeaderMap` types from both v0.2 and v1.0 of the `http` crate.
+pub trait HeaderMap: private::HeaderMapSealed {}
+
+impl HeaderMap for http02::HeaderMap {}
+impl HeaderMap for http1::HeaderMap {}
+
+mod private {
+    pub trait HeaderMapSealed {
+        type HeaderValue: HeaderValueSealed;
+        fn _get(&self, name: &str) -> Option<&Self::HeaderValue>;
+    }
+
+    impl HeaderMapSealed for http02::HeaderMap {
+        type HeaderValue = http02::HeaderValue;
+        fn _get(&self, name: &str) -> Option<&Self::HeaderValue> {
+            self.get(name)
+        }
+    }
+    impl HeaderMapSealed for http1::HeaderMap {
+        type HeaderValue = http1::HeaderValue;
+        fn _get(&self, name: &str) -> Option<&Self::HeaderValue> {
+            self.get(name)
+        }
+    }
+
+    pub trait HeaderValueSealed {
+        fn _to_str(&self) -> Option<&str>;
+    }
+
+    impl HeaderValueSealed for http02::HeaderValue {
+        fn _to_str(&self) -> Option<&str> {
+            self.to_str().ok()
+        }
+    }
+    impl HeaderValueSealed for http1::HeaderValue {
+        fn _to_str(&self) -> Option<&str> {
+            self.to_str().ok()
+        }
+    }
+}
+
 #[cfg(test)]
 mod tests {
 
     use super::*;
-    use http::HeaderMap;
+    use http02::HeaderMap;
 
     fn get_svix_headers(msg_id: &str, signature: &str) -> HeaderMap {
-        let mut headers = http::header::HeaderMap::new();
+        let mut headers = HeaderMap::new();
         headers.insert(SVIX_MSG_ID_KEY, msg_id.parse().unwrap());
         headers.insert(SVIX_MSG_SIGNATURE_KEY, signature.parse().unwrap());
         headers.insert(
@@ -164,7 +206,7 @@ mod tests {
     }
 
     fn get_unbranded_headers(msg_id: &str, signature: &str) -> HeaderMap {
-        let mut headers = http::header::HeaderMap::new();
+        let mut headers = HeaderMap::new();
         headers.insert(UNBRANDED_MSG_ID_KEY, msg_id.parse().unwrap());
         headers.insert(UNBRANDED_MSG_SIGNATURE_KEY, signature.parse().unwrap());
         headers.insert(
diff --git a/rust/templates/hyper/api.mustache b/rust/templates/hyper/api.mustache
new file mode 100644
index 000000000..6301aef1c
--- /dev/null
+++ b/rust/templates/hyper/api.mustache
@@ -0,0 +1,222 @@
+{{>partial_header}}
+
+use crate::Configuration;
+use crate::error::Error;
+
+{{#operations}}
+{{#operation}}
+{{#vendorExtensions.x-group-parameters}}
+{{#allParams}}
+{{#-first}}
+/// struct for passing parameters to the method [`{{operationId}}`]
+#[derive(Clone, Debug)]
+pub struct {{{operationIdCamelCase}}}Params {
+{{/-first}}
+    {{#description}}
+    /// {{{.}}}
+    {{/description}}
+    pub {{{paramName}}}: {{^required}}Option<{{/required}}{{#required}}{{#isNullable}}Option<{{/isNullable}}{{/required}}{{^isUuid}}{{#isString}}{{#isArray}}Vec<{{/isArray}}String{{#isArray}}>{{/isArray}}{{/isString}}{{/isUuid}}{{#isUuid}}{{#isArray}}Vec<{{/isArray}}String{{#isArray}}>{{/isArray}}{{/isUuid}}{{^isString}}{{^isUuid}}{{^isPrimitiveType}}{{^isContainer}}{{#isBodyParam}}crate::models::{{/isBodyParam}}{{/isContainer}}{{/isPrimitiveType}}{{{dataType}}}{{/isUuid}}{{/isString}}{{^required}}>{{/required}}{{#required}}{{#isNullable}}>{{/isNullable}}{{/required}}{{^-last}},{{/-last}}
+{{#-last}}
+}
+
+{{/-last}}
+{{/allParams}}
+{{/vendorExtensions.x-group-parameters}}
+{{/operation}}
+{{/operations}}
+
+{{#supportMultipleResponses}}
+{{#operations}}
+{{#operation}}
+/// struct for typed successes of method [`{{operationId}}`]
+#[derive(Debug, Clone, Serialize, Deserialize)]
+#[serde(untagged)]
+pub enum {{{operationIdCamelCase}}}Success {
+    {{#responses}}
+    {{#is2xx}}
+    Status{{code}}({{#isEnum}}{{{enumName}}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}),
+    {{/is2xx}}
+    {{#is3xx}}
+    Status{{code}}({{#isEnum}}{{{enumName}}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}),
+    {{/is3xx}}
+    {{/responses}}
+    UnknownValue(serde_json::Value),
+}
+
+{{/operation}}
+{{/operations}}
+{{/supportMultipleResponses}}
+{{#operations}}
+{{#operation}}
+/// struct for typed errors of method [`{{operationId}}`]
+#[derive(Debug, Clone, Serialize, Deserialize)]
+#[serde(untagged)]
+pub enum {{{operationIdCamelCase}}}Error {
+    {{#responses}}
+    {{#is4xx}}
+    Status{{code}}({{#isEnum}}{{{enumName}}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}),
+    {{/is4xx}}
+    {{#is5xx}}
+    Status{{code}}({{#isEnum}}{{{enumName}}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}),
+    {{/is5xx}}
+    {{#isDefault}}
+    DefaultResponse({{#isEnum}}{{{enumName}}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}),
+    {{/isDefault}}
+    {{/responses}}
+    UnknownValue(serde_json::Value),
+}
+
+{{/operation}}
+{{/operations}}
+
+{{#operations}}
+{{#operation}}
+{{#description}}
+/// {{{.}}}
+{{/description}}
+{{#notes}}
+/// {{{.}}}
+{{/notes}}
+{{#vendorExtensions.x-group-parameters}}
+pub {{#supportAsync}}async {{/supportAsync}}fn {{{operationId}}}(configuration: &Configuration{{#allParams}}{{#-first}}, params: {{{operationIdCamelCase}}}Params{{/-first}}{{/allParams}}) -> Result<{{#supportMultipleResponses}}super::ResponseContent<{{{operationIdCamelCase}}}Success>{{/supportMultipleResponses}}{{^supportMultipleResponses}}{{^returnType}}(){{/returnType}}{{{returnType}}}{{/supportMultipleResponses}}, Error> {
+    // unbox the parameters
+    {{#allParams}}
+    let {{paramName}} = params.{{paramName}};
+    {{/allParams}}
+
+{{/vendorExtensions.x-group-parameters}}
+{{^vendorExtensions.x-group-parameters}}
+pub {{#supportAsync}}async {{/supportAsync}}fn {{{operationId}}}(configuration: &Configuration, {{#allParams}}{{{paramName}}}: {{^required}}Option<{{/required}}{{#required}}{{#isNullable}}Option<{{/isNullable}}{{/required}}{{#isString}}{{#isArray}}Vec<{{/isArray}}{{^isUuid}}&str{{/isUuid}}{{#isArray}}>{{/isArray}}{{/isString}}{{#isUuid}}{{#isArray}}Vec<{{/isArray}}&str{{#isArray}}>{{/isArray}}{{/isUuid}}{{^isString}}{{^isUuid}}{{^isPrimitiveType}}{{^isContainer}}{{#isBodyParam}}crate::models::{{/isBodyParam}}{{/isContainer}}{{/isPrimitiveType}}{{{dataType}}}{{/isUuid}}{{/isString}}{{^required}}>{{/required}}{{#required}}{{#isNullable}}>{{/isNullable}}{{/required}}{{^-last}}, {{/-last}}{{/allParams}}) -> Result<{{#supportMultipleResponses}}super::ResponseContent<{{{operationIdCamelCase}}}Success>{{/supportMultipleResponses}}{{^supportMultipleResponses}}{{^returnType}}(){{/returnType}}{{{returnType}}}{{/supportMultipleResponses}}, Error> {
+{{/vendorExtensions.x-group-parameters}}
+
+    #[allow(unused_mut)]
+    let mut req = crate::request::Request::new(http1::Method::{{{httpMethod.toUpperCase}}}, "{{{path}}}".to_string())
+    {{#hasAuthMethods}}
+    {{#authMethods}}
+    {{#isApiKey}}
+        .with_auth(crate::request::Auth::ApiKey(crate::request::ApiKey{
+            in_header: {{#isKeyInHeader}}true{{/isKeyInHeader}}{{^isKeyInHeader}}false{{/isKeyInHeader}},
+            in_query: {{#isKeyInQuery}}true{{/isKeyInQuery}}{{^isKeyInQuery}}false{{/isKeyInQuery}},
+            param_name: "{{{keyParamName}}}".to_owned(),
+        }))
+    {{/isApiKey}}
+    {{#isBasicBasic}}
+        .with_auth(crate::request::Auth::Basic)
+    {{/isBasicBasic}}
+    {{#isOAuth}}
+        .with_auth(crate::request::Auth::Oauth)
+    {{/isOAuth}}
+    {{/authMethods}}
+    {{/hasAuthMethods}}
+    ;
+    {{#queryParams}}
+    {{#required}}
+    {{^isNullable}}
+    req = req.with_query_param("{{{baseName}}}".to_string(), {{{paramName}}}{{#isArray}}.join(","){{/isArray}}.to_string());
+    {{/isNullable}}
+    {{#isNullable}}
+    match {{{paramName}}} {
+        Some(param_value) => { req = req.with_query_param("{{{baseName}}}".to_string(), param_value{{#isArray}}.join(","){{/isArray}}.to_string()); },
+        None => { req = req.with_query_param("{{{baseName}}}".to_string(), "".to_string()); },
+    }
+    {{/isNullable}}
+    {{/required}}
+    {{^required}}
+    if let Some(ref s) = {{{paramName}}} {
+        let query_value = {{#isArray}}s.iter().map(|s| s.to_string()).collect::<Vec<String>>().join(","){{/isArray}}{{^isArray}}s.to_string(){{/isArray}};
+        req = req.with_query_param("{{{baseName}}}".to_string(), query_value);
+    }
+    {{/required}}
+    {{/queryParams}}
+    {{#pathParams}}
+    {{#required}}
+    {{^isNullable}}
+    req = req.with_path_param("{{{baseName}}}".to_string(), {{{paramName}}}{{#isArray}}.join(","){{/isArray}}.to_string());
+    {{/isNullable}}
+    {{#isNullable}}
+    match {{{paramName}}} {
+        Some(param_value) => { req = req.with_path_param("{{{baseName}}}".to_string(), param_value{{#isArray}}.join(","){{/isArray}}.to_string()); },
+        None => { req = req.with_path_param("{{{baseName}}}".to_string(), "".to_string()); },
+    }
+    {{/isNullable}}
+    {{/required}}
+    {{^required}}
+    if let Some(param_value) = {{{paramName}}} {
+        req = req.with_path_param("{{{baseName}}}".to_string(), param_value{{#isArray}}.join(","){{/isArray}}.to_string());
+    }
+    {{/required}}
+    {{/pathParams}}
+    {{#hasHeaderParams}}
+    {{#headerParams}}
+    {{#required}}
+    {{^isNullable}}
+    req = req.with_header_param("{{{baseName}}}".to_string(), {{{paramName}}}{{#isArray}}.join(","){{/isArray}}.to_string());
+    {{/isNullable}}
+    {{#isNullable}}
+    match {{{paramName}}} {
+        Some(param_value) => { req = req.with_header_param("{{{baseName}}}".to_string(), param_value{{#isArray}}.join(","){{/isArray}}.to_string()); },
+        None => { req = req.with_header_param("{{{baseName}}}".to_string(), "".to_string()); },
+    }
+    {{/isNullable}}
+    {{/required}}
+    {{^required}}
+    if let Some(param_value) = {{{paramName}}} {
+        req = req.with_header_param("{{{baseName}}}".to_string(), param_value{{#isArray}}.join(","){{/isArray}}.to_string());
+    }
+    {{/required}}
+    {{/headerParams}}
+    {{/hasHeaderParams}}
+    {{#hasFormParams}}
+    {{#formParams}}
+    {{#isFile}}
+    {{#required}}
+    {{^isNullable}}
+    req = req.with_form_param("{{{baseName}}}".to_string(), unimplemented!());
+    {{/isNullable}}
+    {{#isNullable}}
+    match {{{paramName}}} {
+        Some(param_value) => { req = req.with_form_param("{{{baseName}}}".to_string(), unimplemented!()); },
+        None => { req = req.with_form_param("{{{baseName}}}".to_string(), unimplemented!()); },
+    }
+    {{/isNullable}}
+    {{/required}}
+    {{^required}}
+    if let Some(param_value) = {{{paramName}}} {
+        req = req.with_form_param("{{{baseName}}}".to_string(), unimplemented!());
+    }
+    {{/required}}
+    {{/isFile}}
+    {{^isFile}}
+    {{#required}}
+    {{^isNullable}}
+    req = req.with_form_param("{{{baseName}}}".to_string(), {{{paramName}}}{{#isArray}}.join(","){{/isArray}}.to_string());
+    {{/isNullable}}
+    {{#isNullable}}
+    match {{{paramName}}} {
+        Some(param_value) => { req = req.with_form_param("{{{baseName}}}".to_string(), param_value{{#isArray}}.join(","){{/isArray}}.to_string()); },
+        None => { req = req.with_form_param("{{{baseName}}}".to_string(), "".to_string()); },
+    }
+    {{/isNullable}}
+    {{/required}}
+    {{^required}}
+    if let Some(param_value) = {{{paramName}}} {
+        req = req.with_form_param("{{{baseName}}}".to_string(), param_value{{#isArray}}.join(","){{/isArray}}.to_string());
+    }
+    {{/required}}
+    {{/isFile}}
+    {{/formParams}}
+    {{/hasFormParams}}
+    {{#hasBodyParam}}
+    {{#bodyParams}}
+    req = req.with_body_param({{{paramName}}});
+    {{/bodyParams}}
+    {{/hasBodyParam}}
+    {{^returnType}}
+    req = req.returns_nothing();
+    {{/returnType}}
+
+    req.execute(configuration).await
+}
+
+{{/operation}}
+{{/operations}}
diff --git a/rust/templates/hyper/api_mod.mustache b/rust/templates/hyper/api_mod.mustache
new file mode 100644
index 000000000..1d60adc59
--- /dev/null
+++ b/rust/templates/hyper/api_mod.mustache
@@ -0,0 +1,19 @@
+#[derive(Debug, Clone)]
+pub(crate) struct ResponseContent<T> {
+    pub status: http02::StatusCode,
+    pub content: String,
+    pub entity: Option<T>,
+}
+
+{{#apiInfo}}
+{{#apis}}
+pub(crate) mod {{{classFilename}}};
+{{#operations}}
+{{#operation}}
+{{#-last}}
+//pub use self::{{{classFilename}}}::{ {{{classname}}}, {{{classname}}}Client };
+{{/-last}}
+{{/operation}}
+{{/operations}}
+{{/apis}}
+{{/apiInfo}}
diff --git a/rust/templates/model_mod.mustache b/rust/templates/model_mod.mustache
index 6f8fd43fd..0465bca27 100644
--- a/rust/templates/model_mod.mustache
+++ b/rust/templates/model_mod.mustache
@@ -1,7 +1,6 @@
-#![allow(clippy::all)]
-
 {{#models}}
 {{#model}}
+// TODO: Make modules private for v2.0 of the library
 pub mod {{{classFilename}}};
 pub use self::{{{classFilename}}}::{{{classname}}};
 {{/model}}
diff --git a/rust/templates/reqwest/api_mod.mustache b/rust/templates/reqwest/api_mod.mustache
deleted file mode 100644
index bdf7cad10..000000000
--- a/rust/templates/reqwest/api_mod.mustache
+++ /dev/null
@@ -1,84 +0,0 @@
-#![allow(clippy::all)]
-
-use std::error;
-use std::fmt;
-{{#withAWSV4Signature}}
-use aws_sigv4;
-{{/withAWSV4Signature}}
-
-#[derive(Debug, Clone)]
-pub struct ResponseContent<T> {
-    pub status: reqwest::StatusCode,
-    pub content: String,
-    pub entity: Option<T>,
-}
-
-#[derive(Debug)]
-pub enum Error<T> {
-    Reqwest(reqwest::Error),
-    Serde(serde_json::Error),
-    Io(std::io::Error),
-    ResponseError(ResponseContent<T>),
-    {{#withAWSV4Signature}}
-    AWSV4SignatureError(aws_sigv4::http_request::Error),
-    {{/withAWSV4Signature}}
-}
-
-impl <T> fmt::Display for Error<T> {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        let (module, e) = match self {
-            Error::Reqwest(e) => ("reqwest", e.to_string()),
-            Error::Serde(e) => ("serde", e.to_string()),
-            Error::Io(e) => ("IO", e.to_string()),
-            Error::ResponseError(e) => ("response", format!("status code {}", e.status)),
-	    {{#withAWSV4Signature}}
-            Error::AWSV4SignatureError(e) => ("aws v4 signature", e.to_string()),
-	    {{/withAWSV4Signature}}
-        };
-        write!(f, "error in {}: {}", module, e)
-    }
-}
-
-impl <T: fmt::Debug> error::Error for Error<T> {
-    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
-        Some(match self {
-            Error::Reqwest(e) => e,
-            Error::Serde(e) => e,
-            Error::Io(e) => e,
-            Error::ResponseError(_) => return None,
-	    {{#withAWSV4Signature}}
-	    Error::AWSV4SignatureError(_) => return None,
-	    {{/withAWSV4Signature}}
-        })
-    }
-}
-
-impl <T> From<reqwest::Error> for Error<T> {
-    fn from(e: reqwest::Error) -> Self {
-        Error::Reqwest(e)
-    }
-}
-
-impl <T> From<serde_json::Error> for Error<T> {
-    fn from(e: serde_json::Error) -> Self {
-        Error::Serde(e)
-    }
-}
-
-impl <T> From<std::io::Error> for Error<T> {
-    fn from(e: std::io::Error) -> Self {
-        Error::Io(e)
-    }
-}
-
-pub fn urlencode<T: AsRef<str>>(s: T) -> String {
-    ::url::form_urlencoded::byte_serialize(s.as_ref().as_bytes()).collect()
-}
-
-{{#apiInfo}}
-{{#apis}}
-pub mod {{{classFilename}}};
-{{/apis}}
-{{/apiInfo}}
-
-pub mod configuration;


Test patch: