diff --git a/cloudflare-examples/src/main.rs b/cloudflare-examples/src/main.rs index f332d3da..ea428f77 100644 --- a/cloudflare-examples/src/main.rs +++ b/cloudflare-examples/src/main.rs @@ -215,12 +215,9 @@ fn delete_route(arg_matches: &ArgMatches, api_client: &HttpApiClient) { } /// Add and leak a mock (so it runs for 'static) -fn add_static_mock( - endpoint: &dyn Endpoint, -) where +fn add_static_mock(endpoint: &dyn Endpoint) +where ResultType: ApiResult, - QueryType: Serialize, - BodyType: Serialize, { let body = ApiErrors { errors: vec![ApiError { diff --git a/cloudflare/Cargo.toml b/cloudflare/Cargo.toml index 911604b0..13adead9 100644 --- a/cloudflare/Cargo.toml +++ b/cloudflare/Cargo.toml @@ -13,6 +13,7 @@ default = ["default-tls"] blocking = ["reqwest/blocking"] default-tls = ["reqwest/default-tls"] rustls-tls = ["reqwest/rustls-tls"] +spec = [] [dependencies] chrono = { version = "0.4", default-features = false, features = [ @@ -28,6 +29,7 @@ reqwest = { version = "0.11.4", default-features = false, features = ["json"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" serde_with = { version = "2.0", features = ["base64"] } +serde_urlencoded = "0.7.1" thiserror = "1" url = "2.2" uuid = { version = "1.0", features = ["serde"] } diff --git a/cloudflare/src/endpoints/account/list_accounts.rs b/cloudflare/src/endpoints/account/list_accounts.rs index 5eac712d..4d1f128c 100644 --- a/cloudflare/src/endpoints/account/list_accounts.rs +++ b/cloudflare/src/endpoints/account/list_accounts.rs @@ -1,6 +1,6 @@ use super::Account; -use crate::framework::endpoint::{Endpoint, Method}; +use crate::framework::endpoint::{serialize_query, EndpointSpec, Method}; use crate::framework::OrderDirection; use serde::Serialize; @@ -13,15 +13,16 @@ pub struct ListAccounts { pub params: Option, } -impl Endpoint, ListAccountsParams> for ListAccounts { +impl EndpointSpec> for ListAccounts { fn method(&self) -> Method { Method::GET } fn path(&self) -> String { "accounts".to_string() } - fn query(&self) -> Option { - self.params.clone() + #[inline] + fn query(&self) -> Option { + serialize_query(&self.params) } } diff --git a/cloudflare/src/endpoints/argo_tunnel/create_tunnel.rs b/cloudflare/src/endpoints/argo_tunnel/create_tunnel.rs index 212091d5..d851d36e 100644 --- a/cloudflare/src/endpoints/argo_tunnel/create_tunnel.rs +++ b/cloudflare/src/endpoints/argo_tunnel/create_tunnel.rs @@ -5,7 +5,7 @@ use serde_with::{ serde_as, }; -use crate::framework::endpoint::{Endpoint, Method}; +use crate::framework::endpoint::{EndpointSpec, Method}; use super::Tunnel; @@ -19,15 +19,17 @@ pub struct CreateTunnel<'a> { pub params: Params<'a>, } -impl<'a> Endpoint> for CreateTunnel<'a> { +impl<'a> EndpointSpec for CreateTunnel<'a> { fn method(&self) -> Method { Method::POST } fn path(&self) -> String { format!("accounts/{}/tunnels", self.account_identifier) } - fn body(&self) -> Option> { - Some(self.params.clone()) + #[inline] + fn body(&self) -> Option { + let body = serde_json::to_string(&self.params).unwrap(); + Some(body) } } diff --git a/cloudflare/src/endpoints/argo_tunnel/delete_tunnel.rs b/cloudflare/src/endpoints/argo_tunnel/delete_tunnel.rs index d74501e8..caa6b2fe 100644 --- a/cloudflare/src/endpoints/argo_tunnel/delete_tunnel.rs +++ b/cloudflare/src/endpoints/argo_tunnel/delete_tunnel.rs @@ -1,4 +1,4 @@ -use crate::framework::endpoint::{Endpoint, Method}; +use crate::framework::endpoint::{EndpointSpec, Method}; use super::Tunnel; @@ -12,7 +12,7 @@ pub struct DeleteTunnel<'a> { pub cascade: bool, } -impl<'a> Endpoint for DeleteTunnel<'a> { +impl<'a> EndpointSpec for DeleteTunnel<'a> { fn method(&self) -> Method { Method::DELETE } diff --git a/cloudflare/src/endpoints/argo_tunnel/list_tunnels.rs b/cloudflare/src/endpoints/argo_tunnel/list_tunnels.rs index b020b84f..f112e468 100644 --- a/cloudflare/src/endpoints/argo_tunnel/list_tunnels.rs +++ b/cloudflare/src/endpoints/argo_tunnel/list_tunnels.rs @@ -1,7 +1,7 @@ use chrono::{DateTime, Utc}; use serde::Serialize; -use crate::framework::endpoint::{Endpoint, Method}; +use crate::framework::endpoint::{serialize_query, EndpointSpec, Method}; use super::Tunnel; @@ -13,15 +13,16 @@ pub struct ListTunnels<'a> { pub params: Params, } -impl<'a> Endpoint, Params> for ListTunnels<'a> { +impl<'a> EndpointSpec> for ListTunnels<'a> { fn method(&self) -> Method { Method::GET } fn path(&self) -> String { format!("accounts/{}/tunnels", self.account_identifier) } - fn query(&self) -> Option { - Some(self.params.clone()) + #[inline] + fn query(&self) -> Option { + serialize_query(&self.params) } } diff --git a/cloudflare/src/endpoints/argo_tunnel/route_dns.rs b/cloudflare/src/endpoints/argo_tunnel/route_dns.rs index ac738313..823101d3 100644 --- a/cloudflare/src/endpoints/argo_tunnel/route_dns.rs +++ b/cloudflare/src/endpoints/argo_tunnel/route_dns.rs @@ -1,4 +1,4 @@ -use crate::framework::endpoint::{Endpoint, Method}; +use crate::framework::endpoint::{EndpointSpec, Method}; use super::RouteResult; use serde::Serialize; @@ -16,15 +16,17 @@ pub struct RouteTunnel<'a> { pub params: Params<'a>, } -impl<'a> Endpoint> for RouteTunnel<'a> { +impl<'a> EndpointSpec for RouteTunnel<'a> { fn method(&self) -> Method { Method::PUT } fn path(&self) -> String { format!("zones/{}/tunnels/{}/routes", self.zone_tag, self.tunnel_id) } - fn body(&self) -> Option> { - Some(self.params.clone()) + #[inline] + fn body(&self) -> Option { + let body = serde_json::to_string(&self.params).unwrap(); + Some(body) } } diff --git a/cloudflare/src/endpoints/dns.rs b/cloudflare/src/endpoints/dns.rs index 8b5b73bf..871945ff 100644 --- a/cloudflare/src/endpoints/dns.rs +++ b/cloudflare/src/endpoints/dns.rs @@ -1,5 +1,5 @@ use crate::framework::{ - endpoint::{Endpoint, Method}, + endpoint::{serialize_query, EndpointSpec, Method}, response::ApiResult, }; /// @@ -16,15 +16,16 @@ pub struct ListDnsRecords<'a> { pub zone_identifier: &'a str, pub params: ListDnsRecordsParams, } -impl<'a> Endpoint, ListDnsRecordsParams> for ListDnsRecords<'a> { +impl<'a> EndpointSpec> for ListDnsRecords<'a> { fn method(&self) -> Method { Method::GET } fn path(&self) -> String { format!("zones/{}/dns_records", self.zone_identifier) } - fn query(&self) -> Option { - Some(self.params.clone()) + #[inline] + fn query(&self) -> Option { + serialize_query(&self.params) } } @@ -36,15 +37,17 @@ pub struct CreateDnsRecord<'a> { pub params: CreateDnsRecordParams<'a>, } -impl<'a> Endpoint> for CreateDnsRecord<'a> { +impl<'a> EndpointSpec for CreateDnsRecord<'a> { fn method(&self) -> Method { Method::POST } fn path(&self) -> String { format!("zones/{}/dns_records", self.zone_identifier) } - fn body(&self) -> Option> { - Some(self.params.clone()) + #[inline] + fn body(&self) -> Option { + let body = serde_json::to_string(&self.params).unwrap(); + Some(body) } } @@ -72,7 +75,7 @@ pub struct DeleteDnsRecord<'a> { pub zone_identifier: &'a str, pub identifier: &'a str, } -impl<'a> Endpoint for DeleteDnsRecord<'a> { +impl<'a> EndpointSpec for DeleteDnsRecord<'a> { fn method(&self) -> Method { Method::DELETE } @@ -93,7 +96,7 @@ pub struct UpdateDnsRecord<'a> { pub params: UpdateDnsRecordParams<'a>, } -impl<'a> Endpoint> for UpdateDnsRecord<'a> { +impl<'a> EndpointSpec for UpdateDnsRecord<'a> { fn method(&self) -> Method { Method::PUT } @@ -103,8 +106,10 @@ impl<'a> Endpoint> for UpdateDnsRecord< self.zone_identifier, self.identifier ) } - fn body(&self) -> Option> { - Some(self.params.clone()) + #[inline] + fn body(&self) -> Option { + let body = serde_json::to_string(&self.params).unwrap(); + Some(body) } } diff --git a/cloudflare/src/endpoints/load_balancing/create_lb.rs b/cloudflare/src/endpoints/load_balancing/create_lb.rs index 1fd64f08..c079a781 100644 --- a/cloudflare/src/endpoints/load_balancing/create_lb.rs +++ b/cloudflare/src/endpoints/load_balancing/create_lb.rs @@ -2,7 +2,7 @@ use crate::endpoints::load_balancing::{ LbPoolId, LbPoolMapping, LoadBalancer, SessionAffinity, SessionAffinityAttributes, SteeringPolicy, }; -use crate::framework::endpoint::{Endpoint, Method}; +use crate::framework::endpoint::{EndpointSpec, Method}; use serde::Serialize; @@ -55,14 +55,16 @@ pub struct OptionalParams<'a> { pub session_affinity_ttl: Option, } -impl<'a> Endpoint> for CreateLoadBalancer<'a> { +impl<'a> EndpointSpec for CreateLoadBalancer<'a> { fn method(&self) -> Method { Method::POST } fn path(&self) -> String { format!("zones/{}/load_balancers", self.zone_identifier) } - fn body(&self) -> Option> { - Some(self.params.clone()) + #[inline] + fn body(&self) -> Option { + let body = serde_json::to_string(&self.params).unwrap(); + Some(body) } } diff --git a/cloudflare/src/endpoints/load_balancing/create_pool.rs b/cloudflare/src/endpoints/load_balancing/create_pool.rs index 64150bf0..f0f28fd2 100644 --- a/cloudflare/src/endpoints/load_balancing/create_pool.rs +++ b/cloudflare/src/endpoints/load_balancing/create_pool.rs @@ -1,5 +1,5 @@ use crate::endpoints::load_balancing::{Origin, Pool}; -use crate::framework::endpoint::{Endpoint, Method}; +use crate::framework::endpoint::{EndpointSpec, Method}; use serde::Serialize; @@ -51,14 +51,16 @@ pub struct OptionalParams<'a> { pub notification_email: Option<&'a str>, } -impl<'a> Endpoint> for CreatePool<'a> { +impl<'a> EndpointSpec for CreatePool<'a> { fn method(&self) -> Method { Method::POST } fn path(&self) -> String { format!("accounts/{}/load_balancers/pools", self.account_identifier) } - fn body(&self) -> Option> { - Some(self.params.clone()) + #[inline] + fn body(&self) -> Option { + let body = serde_json::to_string(&self.params).unwrap(); + Some(body) } } diff --git a/cloudflare/src/endpoints/load_balancing/delete_lb.rs b/cloudflare/src/endpoints/load_balancing/delete_lb.rs index d15935f4..6723e0a5 100644 --- a/cloudflare/src/endpoints/load_balancing/delete_lb.rs +++ b/cloudflare/src/endpoints/load_balancing/delete_lb.rs @@ -1,4 +1,4 @@ -use crate::framework::endpoint::{Endpoint, Method}; +use crate::framework::endpoint::{EndpointSpec, Method}; use crate::framework::response::ApiResult; use serde::Deserialize; @@ -13,7 +13,7 @@ pub struct DeleteLoadBalancer<'a> { pub identifier: &'a str, } -impl<'a> Endpoint for DeleteLoadBalancer<'a> { +impl<'a> EndpointSpec for DeleteLoadBalancer<'a> { fn method(&self) -> Method { Method::DELETE } diff --git a/cloudflare/src/endpoints/load_balancing/delete_pool.rs b/cloudflare/src/endpoints/load_balancing/delete_pool.rs index 43cdb249..bae55a63 100644 --- a/cloudflare/src/endpoints/load_balancing/delete_pool.rs +++ b/cloudflare/src/endpoints/load_balancing/delete_pool.rs @@ -1,4 +1,4 @@ -use crate::framework::endpoint::{Endpoint, Method}; +use crate::framework::endpoint::{EndpointSpec, Method}; use crate::framework::response::ApiResult; use serde::Deserialize; @@ -13,7 +13,7 @@ pub struct DeletePool<'a> { pub identifier: &'a str, } -impl<'a> Endpoint for DeletePool<'a> { +impl<'a> EndpointSpec for DeletePool<'a> { fn method(&self) -> Method { Method::DELETE } diff --git a/cloudflare/src/endpoints/load_balancing/list_lb.rs b/cloudflare/src/endpoints/load_balancing/list_lb.rs index 9eec1c77..75066c59 100644 --- a/cloudflare/src/endpoints/load_balancing/list_lb.rs +++ b/cloudflare/src/endpoints/load_balancing/list_lb.rs @@ -1,5 +1,5 @@ use crate::endpoints::load_balancing::LoadBalancer; -use crate::framework::endpoint::{Endpoint, Method}; +use crate::framework::endpoint::{EndpointSpec, Method}; use crate::framework::response::ApiResult; /// List Load Balancers @@ -10,7 +10,7 @@ pub struct ListLoadBalancers<'a> { pub zone_identifier: &'a str, } -impl<'a> Endpoint, ()> for ListLoadBalancers<'a> { +impl<'a> EndpointSpec> for ListLoadBalancers<'a> { fn method(&self) -> Method { Method::GET } diff --git a/cloudflare/src/endpoints/load_balancing/pool_details.rs b/cloudflare/src/endpoints/load_balancing/pool_details.rs index 816a5fa6..8ff0d575 100644 --- a/cloudflare/src/endpoints/load_balancing/pool_details.rs +++ b/cloudflare/src/endpoints/load_balancing/pool_details.rs @@ -1,5 +1,5 @@ use crate::endpoints::load_balancing::Pool; -use crate::framework::endpoint::{Endpoint, Method}; +use crate::framework::endpoint::{EndpointSpec, Method}; /// Pool Details /// @@ -11,7 +11,7 @@ pub struct PoolDetails<'a> { pub identifier: &'a str, } -impl<'a> Endpoint for PoolDetails<'a> { +impl<'a> EndpointSpec for PoolDetails<'a> { fn method(&self) -> Method { Method::GET } diff --git a/cloudflare/src/endpoints/r2.rs b/cloudflare/src/endpoints/r2.rs index 969fc470..44626945 100644 --- a/cloudflare/src/endpoints/r2.rs +++ b/cloudflare/src/endpoints/r2.rs @@ -3,7 +3,7 @@ use chrono::DateTime; use serde::{Deserialize, Serialize}; use std::collections::HashMap; -use crate::framework::endpoint::{Endpoint, Method}; +use crate::framework::endpoint::{EndpointSpec, Method}; use crate::framework::response::ApiResult; /// A Bucket is a collection of Objects stored in R2. @@ -31,7 +31,7 @@ pub struct ListBuckets<'a> { pub account_identifier: &'a str, } -impl<'a> Endpoint for ListBuckets<'a> { +impl<'a> EndpointSpec for ListBuckets<'a> { fn method(&self) -> Method { Method::GET } @@ -49,7 +49,7 @@ pub struct CreateBucket<'a> { pub bucket_name: &'a str, } -impl<'a> Endpoint for CreateBucket<'a> { +impl<'a> EndpointSpec for CreateBucket<'a> { fn method(&self) -> Method { Method::PUT } @@ -68,7 +68,7 @@ pub struct DeleteBucket<'a> { pub bucket_name: &'a str, } -impl<'a> Endpoint for DeleteBucket<'a> { +impl<'a> EndpointSpec for DeleteBucket<'a> { fn method(&self) -> Method { Method::DELETE } diff --git a/cloudflare/src/endpoints/user.rs b/cloudflare/src/endpoints/user.rs index fe491e34..0d4113f1 100644 --- a/cloudflare/src/endpoints/user.rs +++ b/cloudflare/src/endpoints/user.rs @@ -1,4 +1,4 @@ -use crate::framework::endpoint::{Endpoint, Method}; +use crate::framework::endpoint::{EndpointSpec, Method}; use crate::framework::response::ApiResult; use chrono::{DateTime, Utc}; @@ -68,7 +68,7 @@ fn handles_empty_betas_field() { #[derive(Debug)] pub struct GetUserDetails {} -impl Endpoint for GetUserDetails { +impl EndpointSpec for GetUserDetails { fn method(&self) -> Method { Method::GET } @@ -91,7 +91,7 @@ impl ApiResult for UserTokenStatus {} #[derive(Debug)] pub struct GetUserTokenStatus {} -impl Endpoint for GetUserTokenStatus { +impl EndpointSpec for GetUserTokenStatus { fn method(&self) -> Method { Method::GET } diff --git a/cloudflare/src/endpoints/workers/create_route.rs b/cloudflare/src/endpoints/workers/create_route.rs index e65a3bfa..376d191b 100644 --- a/cloudflare/src/endpoints/workers/create_route.rs +++ b/cloudflare/src/endpoints/workers/create_route.rs @@ -1,6 +1,6 @@ use super::WorkersRouteIdOnly; -use crate::framework::endpoint::{Endpoint, Method}; +use crate::framework::endpoint::{EndpointSpec, Method}; use serde::Serialize; @@ -13,15 +13,17 @@ pub struct CreateRoute<'a> { pub params: CreateRouteParams, } -impl<'a> Endpoint for CreateRoute<'a> { +impl<'a> EndpointSpec for CreateRoute<'a> { fn method(&self) -> Method { Method::POST } fn path(&self) -> String { format!("zones/{}/workers/routes", self.zone_identifier) } - fn body(&self) -> Option { - Some(self.params.clone()) + #[inline] + fn body(&self) -> Option { + let body = serde_json::to_string(&self.params).unwrap(); + Some(body) } } diff --git a/cloudflare/src/endpoints/workers/create_secret.rs b/cloudflare/src/endpoints/workers/create_secret.rs index 234ca255..ff97f9a3 100644 --- a/cloudflare/src/endpoints/workers/create_secret.rs +++ b/cloudflare/src/endpoints/workers/create_secret.rs @@ -1,6 +1,6 @@ use super::WorkersSecret; -use crate::framework::endpoint::{Endpoint, Method}; +use crate::framework::endpoint::{EndpointSpec, Method}; use serde::Serialize; @@ -16,7 +16,7 @@ pub struct CreateSecret<'a> { pub params: CreateSecretParams, } -impl<'a> Endpoint for CreateSecret<'a> { +impl<'a> EndpointSpec for CreateSecret<'a> { fn method(&self) -> Method { Method::PUT } @@ -26,8 +26,10 @@ impl<'a> Endpoint for CreateSecret<'a> { self.account_identifier, self.script_name ) } - fn body(&self) -> Option { - Some(self.params.clone()) + #[inline] + fn body(&self) -> Option { + let body = serde_json::to_string(&self.params).unwrap(); + Some(body) } } diff --git a/cloudflare/src/endpoints/workers/create_tail.rs b/cloudflare/src/endpoints/workers/create_tail.rs index 86279b4b..4fe6f43a 100644 --- a/cloudflare/src/endpoints/workers/create_tail.rs +++ b/cloudflare/src/endpoints/workers/create_tail.rs @@ -1,6 +1,6 @@ use super::WorkersTail; -use crate::framework::endpoint::{Endpoint, Method}; +use crate::framework::endpoint::{EndpointSpec, Method}; use serde::Serialize; @@ -20,7 +20,7 @@ pub struct CreateTail<'a> { pub params: CreateTailParams, } -impl<'a> Endpoint for CreateTail<'a> { +impl<'a> EndpointSpec for CreateTail<'a> { fn method(&self) -> Method { Method::POST } @@ -30,9 +30,11 @@ impl<'a> Endpoint for CreateTail<'a> { self.account_identifier, self.script_name ) } - fn body(&self) -> Option { + #[inline] + fn body(&self) -> Option { if self.params.url.is_some() { - Some(self.params.clone()) + let body = serde_json::to_string(&self.params).unwrap(); + Some(body) } else { None } diff --git a/cloudflare/src/endpoints/workers/delete_do.rs b/cloudflare/src/endpoints/workers/delete_do.rs index 4bd29032..ad224cb0 100644 --- a/cloudflare/src/endpoints/workers/delete_do.rs +++ b/cloudflare/src/endpoints/workers/delete_do.rs @@ -1,4 +1,4 @@ -use crate::framework::endpoint::{Endpoint, Method}; +use crate::framework::endpoint::{EndpointSpec, Method}; /// Delete a Durable Object namespace #[derive(Debug)] @@ -9,7 +9,7 @@ pub struct DeleteDurableObject<'a> { pub namespace_id: &'a str, } -impl<'a> Endpoint<(), (), ()> for DeleteDurableObject<'a> { +impl<'a> EndpointSpec<()> for DeleteDurableObject<'a> { fn method(&self) -> Method { Method::DELETE } diff --git a/cloudflare/src/endpoints/workers/delete_route.rs b/cloudflare/src/endpoints/workers/delete_route.rs index 95052466..ca252032 100644 --- a/cloudflare/src/endpoints/workers/delete_route.rs +++ b/cloudflare/src/endpoints/workers/delete_route.rs @@ -1,6 +1,6 @@ use super::WorkersRouteIdOnly; -use crate::framework::endpoint::{Endpoint, Method}; +use crate::framework::endpoint::{EndpointSpec, Method}; /// Delete a Route /// Deletes a route by route id @@ -11,7 +11,7 @@ pub struct DeleteRoute<'a> { pub identifier: &'a str, } -impl<'a> Endpoint for DeleteRoute<'a> { +impl<'a> EndpointSpec for DeleteRoute<'a> { fn method(&self) -> Method { Method::DELETE } diff --git a/cloudflare/src/endpoints/workers/delete_script.rs b/cloudflare/src/endpoints/workers/delete_script.rs index bf570094..e249d3a7 100644 --- a/cloudflare/src/endpoints/workers/delete_script.rs +++ b/cloudflare/src/endpoints/workers/delete_script.rs @@ -1,4 +1,4 @@ -use crate::framework::endpoint::{Endpoint, Method}; +use crate::framework::endpoint::{EndpointSpec, Method}; use crate::framework::response::ApiResult; use serde::{Deserialize, Serialize}; @@ -13,7 +13,7 @@ pub struct DeleteScript<'a> { pub script_name: &'a str, } -impl<'a> Endpoint for DeleteScript<'a> { +impl<'a> EndpointSpec for DeleteScript<'a> { fn method(&self) -> Method { Method::DELETE } diff --git a/cloudflare/src/endpoints/workers/delete_secret.rs b/cloudflare/src/endpoints/workers/delete_secret.rs index 4461eef5..09eee7ba 100644 --- a/cloudflare/src/endpoints/workers/delete_secret.rs +++ b/cloudflare/src/endpoints/workers/delete_secret.rs @@ -1,4 +1,4 @@ -use crate::framework::endpoint::{Endpoint, Method}; +use crate::framework::endpoint::{EndpointSpec, Method}; /// Delete Secret /// @@ -12,7 +12,7 @@ pub struct DeleteSecret<'a> { pub secret_name: &'a str, } -impl<'a> Endpoint<(), (), ()> for DeleteSecret<'a> { +impl<'a> EndpointSpec<()> for DeleteSecret<'a> { fn method(&self) -> Method { Method::DELETE } diff --git a/cloudflare/src/endpoints/workers/delete_tail.rs b/cloudflare/src/endpoints/workers/delete_tail.rs index ac03b0b0..838d6270 100644 --- a/cloudflare/src/endpoints/workers/delete_tail.rs +++ b/cloudflare/src/endpoints/workers/delete_tail.rs @@ -1,4 +1,4 @@ -use crate::framework::endpoint::{Endpoint, Method}; +use crate::framework::endpoint::{EndpointSpec, Method}; /// Delete Tail /// @@ -12,7 +12,7 @@ pub struct DeleteTail<'a> { pub tail_id: &'a str, } -impl<'a> Endpoint<()> for DeleteTail<'a> { +impl<'a> EndpointSpec<()> for DeleteTail<'a> { fn method(&self) -> Method { Method::DELETE } diff --git a/cloudflare/src/endpoints/workers/list_bindings.rs b/cloudflare/src/endpoints/workers/list_bindings.rs index 4e7abcb5..718b4e02 100644 --- a/cloudflare/src/endpoints/workers/list_bindings.rs +++ b/cloudflare/src/endpoints/workers/list_bindings.rs @@ -1,5 +1,5 @@ use super::WorkersBinding; -use crate::framework::endpoint::{Endpoint, Method}; +use crate::framework::endpoint::{EndpointSpec, Method}; /// List Bindings /// Lists all bindings for a given script @@ -11,7 +11,7 @@ pub struct ListBindings<'a> { pub script_name: &'a str, } -impl<'a> Endpoint> for ListBindings<'a> { +impl<'a> EndpointSpec> for ListBindings<'a> { fn method(&self) -> Method { Method::GET } diff --git a/cloudflare/src/endpoints/workers/list_routes.rs b/cloudflare/src/endpoints/workers/list_routes.rs index d0ab573f..bde68257 100644 --- a/cloudflare/src/endpoints/workers/list_routes.rs +++ b/cloudflare/src/endpoints/workers/list_routes.rs @@ -1,6 +1,6 @@ use super::WorkersRoute; -use crate::framework::endpoint::{Endpoint, Method}; +use crate::framework::endpoint::{EndpointSpec, Method}; /// List Routes /// Lists all route mappings for a given zone @@ -10,7 +10,7 @@ pub struct ListRoutes<'a> { pub zone_identifier: &'a str, } -impl<'a> Endpoint> for ListRoutes<'a> { +impl<'a> EndpointSpec> for ListRoutes<'a> { fn method(&self) -> Method { Method::GET } diff --git a/cloudflare/src/endpoints/workers/list_secrets.rs b/cloudflare/src/endpoints/workers/list_secrets.rs index f16d8909..773322dc 100644 --- a/cloudflare/src/endpoints/workers/list_secrets.rs +++ b/cloudflare/src/endpoints/workers/list_secrets.rs @@ -1,6 +1,6 @@ use super::WorkersSecret; -use crate::framework::endpoint::{Endpoint, Method}; +use crate::framework::endpoint::{EndpointSpec, Method}; /// List Secrets /// Lists all secrets mappings for a given script @@ -11,7 +11,7 @@ pub struct ListSecrets<'a> { pub script_name: &'a str, } -impl<'a> Endpoint> for ListSecrets<'a> { +impl<'a> EndpointSpec> for ListSecrets<'a> { fn method(&self) -> Method { Method::GET } diff --git a/cloudflare/src/endpoints/workers/list_tails.rs b/cloudflare/src/endpoints/workers/list_tails.rs index 5c315abb..a7630b28 100644 --- a/cloudflare/src/endpoints/workers/list_tails.rs +++ b/cloudflare/src/endpoints/workers/list_tails.rs @@ -1,6 +1,6 @@ use super::WorkersTail; -use crate::framework::endpoint::{Endpoint, Method}; +use crate::framework::endpoint::{EndpointSpec, Method}; /// List Tails /// Lists all active Tail sessions for a given Worker @@ -11,7 +11,7 @@ pub struct ListTails<'a> { pub script_name: &'a str, } -impl<'a> Endpoint> for ListTails<'a> { +impl<'a> EndpointSpec> for ListTails<'a> { fn method(&self) -> Method { Method::GET } diff --git a/cloudflare/src/endpoints/workers/send_tail_heartbeat.rs b/cloudflare/src/endpoints/workers/send_tail_heartbeat.rs index a106b441..9da8deda 100644 --- a/cloudflare/src/endpoints/workers/send_tail_heartbeat.rs +++ b/cloudflare/src/endpoints/workers/send_tail_heartbeat.rs @@ -1,6 +1,6 @@ use super::WorkersTail; -use crate::framework::endpoint::{Endpoint, Method}; +use crate::framework::endpoint::{EndpointSpec, Method}; /// Send Tail Heartbeat /// @@ -14,7 +14,7 @@ pub struct SendTailHeartbeat<'a> { pub tail_id: &'a str, } -impl<'a> Endpoint for SendTailHeartbeat<'a> { +impl<'a> EndpointSpec for SendTailHeartbeat<'a> { fn method(&self) -> Method { Method::POST } diff --git a/cloudflare/src/endpoints/workerskv/create_namespace.rs b/cloudflare/src/endpoints/workerskv/create_namespace.rs index a5bf830c..849b9974 100644 --- a/cloudflare/src/endpoints/workerskv/create_namespace.rs +++ b/cloudflare/src/endpoints/workerskv/create_namespace.rs @@ -1,6 +1,6 @@ use super::WorkersKvNamespace; -use crate::framework::endpoint::{Endpoint, Method}; +use crate::framework::endpoint::{EndpointSpec, Method}; use serde::Serialize; @@ -15,15 +15,17 @@ pub struct CreateNamespace<'a> { pub params: CreateNamespaceParams, } -impl<'a> Endpoint for CreateNamespace<'a> { +impl<'a> EndpointSpec for CreateNamespace<'a> { fn method(&self) -> Method { Method::POST } fn path(&self) -> String { format!("accounts/{}/storage/kv/namespaces", self.account_identifier) } - fn body(&self) -> Option { - Some(self.params.clone()) + #[inline] + fn body(&self) -> Option { + let body = serde_json::to_string(&self.params).unwrap(); + Some(body) } } diff --git a/cloudflare/src/endpoints/workerskv/delete_bulk.rs b/cloudflare/src/endpoints/workerskv/delete_bulk.rs index 9233b2e6..f29cc448 100644 --- a/cloudflare/src/endpoints/workerskv/delete_bulk.rs +++ b/cloudflare/src/endpoints/workerskv/delete_bulk.rs @@ -1,4 +1,4 @@ -use crate::framework::endpoint::{Endpoint, Method}; +use crate::framework::endpoint::{EndpointSpec, Method}; /// Delete Key-Value Pairs in Bulk /// Deletes multiple key-value pairs from Workers KV at once. @@ -11,7 +11,7 @@ pub struct DeleteBulk<'a> { pub bulk_keys: Vec, } -impl<'a> Endpoint<(), (), Vec> for DeleteBulk<'a> { +impl<'a> EndpointSpec<()> for DeleteBulk<'a> { fn method(&self) -> Method { Method::DELETE } @@ -21,8 +21,10 @@ impl<'a> Endpoint<(), (), Vec> for DeleteBulk<'a> { self.account_identifier, self.namespace_identifier ) } - fn body(&self) -> Option> { - Some(self.bulk_keys.clone()) + #[inline] + fn body(&self) -> Option { + let body = serde_json::to_string(&self.bulk_keys).unwrap(); + Some(body) } // default content-type is already application/json } diff --git a/cloudflare/src/endpoints/workerskv/delete_key.rs b/cloudflare/src/endpoints/workerskv/delete_key.rs index 2b1571c3..fe43d875 100644 --- a/cloudflare/src/endpoints/workerskv/delete_key.rs +++ b/cloudflare/src/endpoints/workerskv/delete_key.rs @@ -1,4 +1,4 @@ -use crate::framework::endpoint::{Endpoint, Method}; +use crate::framework::endpoint::{EndpointSpec, Method}; /// Delete a key-value pair from Workers KV /// Deletes a given key from the given namespace in Workers KV. @@ -11,7 +11,7 @@ pub struct DeleteKey<'a> { pub key: &'a str, } -impl<'a> Endpoint<(), (), ()> for DeleteKey<'a> { +impl<'a> EndpointSpec<()> for DeleteKey<'a> { fn method(&self) -> Method { Method::DELETE } diff --git a/cloudflare/src/endpoints/workerskv/list_namespace_keys.rs b/cloudflare/src/endpoints/workerskv/list_namespace_keys.rs index 6924a9dc..9d75db67 100644 --- a/cloudflare/src/endpoints/workerskv/list_namespace_keys.rs +++ b/cloudflare/src/endpoints/workerskv/list_namespace_keys.rs @@ -1,6 +1,6 @@ use super::Key; -use crate::framework::endpoint::{Endpoint, Method}; +use crate::framework::endpoint::{serialize_query, EndpointSpec, Method}; use serde::Serialize; @@ -13,7 +13,7 @@ pub struct ListNamespaceKeys<'a> { pub params: ListNamespaceKeysParams, } -impl<'a> Endpoint, ListNamespaceKeysParams> for ListNamespaceKeys<'a> { +impl<'a> EndpointSpec> for ListNamespaceKeys<'a> { fn method(&self) -> Method { Method::GET } @@ -23,8 +23,9 @@ impl<'a> Endpoint, ListNamespaceKeysParams> for ListNamespaceKeys<'a> { self.account_identifier, self.namespace_identifier ) } - fn query(&self) -> Option { - Some(self.params.clone()) + #[inline] + fn query(&self) -> Option { + serialize_query(&self.params) } } diff --git a/cloudflare/src/endpoints/workerskv/list_namespaces.rs b/cloudflare/src/endpoints/workerskv/list_namespaces.rs index 83a25992..adc57a9c 100644 --- a/cloudflare/src/endpoints/workerskv/list_namespaces.rs +++ b/cloudflare/src/endpoints/workerskv/list_namespaces.rs @@ -1,6 +1,6 @@ use super::WorkersKvNamespace; -use crate::framework::endpoint::{Endpoint, Method}; +use crate::framework::endpoint::{serialize_query, EndpointSpec, Method}; use serde::Serialize; @@ -13,15 +13,16 @@ pub struct ListNamespaces<'a> { pub params: ListNamespacesParams, } -impl<'a> Endpoint, ListNamespacesParams> for ListNamespaces<'a> { +impl<'a> EndpointSpec> for ListNamespaces<'a> { fn method(&self) -> Method { Method::GET } fn path(&self) -> String { format!("accounts/{}/storage/kv/namespaces", self.account_identifier) } - fn query(&self) -> Option { - Some(self.params.clone()) + #[inline] + fn query(&self) -> Option { + serialize_query(&self.params) } } diff --git a/cloudflare/src/endpoints/workerskv/remove_namespace.rs b/cloudflare/src/endpoints/workerskv/remove_namespace.rs index 1f68ecab..3c9c87d0 100644 --- a/cloudflare/src/endpoints/workerskv/remove_namespace.rs +++ b/cloudflare/src/endpoints/workerskv/remove_namespace.rs @@ -1,4 +1,4 @@ -use crate::framework::endpoint::{Endpoint, Method}; +use crate::framework::endpoint::{EndpointSpec, Method}; /// Remove a Namespace /// Deletes the namespace corresponding to the given ID. @@ -9,7 +9,7 @@ pub struct RemoveNamespace<'a> { pub namespace_identifier: &'a str, } -impl<'a> Endpoint for RemoveNamespace<'a> { +impl<'a> EndpointSpec<()> for RemoveNamespace<'a> { fn method(&self) -> Method { Method::DELETE } diff --git a/cloudflare/src/endpoints/workerskv/rename_namespace.rs b/cloudflare/src/endpoints/workerskv/rename_namespace.rs index 9deb8d07..17dc9b7d 100644 --- a/cloudflare/src/endpoints/workerskv/rename_namespace.rs +++ b/cloudflare/src/endpoints/workerskv/rename_namespace.rs @@ -1,4 +1,4 @@ -use crate::framework::endpoint::{Endpoint, Method}; +use crate::framework::endpoint::{EndpointSpec, Method}; use serde::Serialize; @@ -12,7 +12,7 @@ pub struct RenameNamespace<'a> { pub params: RenameNamespaceParams, } -impl<'a> Endpoint<(), (), RenameNamespaceParams> for RenameNamespace<'a> { +impl<'a> EndpointSpec<()> for RenameNamespace<'a> { fn method(&self) -> Method { Method::PUT } @@ -22,8 +22,10 @@ impl<'a> Endpoint<(), (), RenameNamespaceParams> for RenameNamespace<'a> { self.account_identifier, self.namespace_identifier ) } - fn body(&self) -> Option { - Some(self.params.clone()) + #[inline] + fn body(&self) -> Option { + let body = serde_json::to_string(&self.params).unwrap(); + Some(body) } } diff --git a/cloudflare/src/endpoints/workerskv/write_bulk.rs b/cloudflare/src/endpoints/workerskv/write_bulk.rs index 5d9092f8..c59f889a 100644 --- a/cloudflare/src/endpoints/workerskv/write_bulk.rs +++ b/cloudflare/src/endpoints/workerskv/write_bulk.rs @@ -1,4 +1,4 @@ -use crate::framework::endpoint::{Endpoint, Method}; +use crate::framework::endpoint::{EndpointSpec, Method}; use serde::{Deserialize, Serialize}; @@ -13,7 +13,7 @@ pub struct WriteBulk<'a> { pub bulk_key_value_pairs: Vec, } -impl<'a> Endpoint<(), (), Vec> for WriteBulk<'a> { +impl<'a> EndpointSpec<()> for WriteBulk<'a> { fn method(&self) -> Method { Method::PUT } @@ -23,8 +23,11 @@ impl<'a> Endpoint<(), (), Vec> for WriteBulk<'a> { self.account_identifier, self.namespace_identifier ) } - fn body(&self) -> Option> { - Some(self.bulk_key_value_pairs.clone()) + + #[inline] + fn body(&self) -> Option { + let body = serde_json::to_string(&self.bulk_key_value_pairs).unwrap(); + Some(body) } // default content-type is already application/json } diff --git a/cloudflare/src/endpoints/zone.rs b/cloudflare/src/endpoints/zone.rs index e4a22c1b..1ab3f125 100644 --- a/cloudflare/src/endpoints/zone.rs +++ b/cloudflare/src/endpoints/zone.rs @@ -1,6 +1,7 @@ use crate::endpoints::{account::AccountDetails, plan::Plan}; +use crate::framework::endpoint::serialize_query; use crate::framework::{ - endpoint::{Endpoint, Method}, + endpoint::{EndpointSpec, Method}, response::ApiResult, }; use crate::framework::{OrderDirection, SearchMatch}; @@ -16,15 +17,16 @@ pub struct ListZones { pub params: ListZonesParams, } -impl Endpoint, ListZonesParams> for ListZones { +impl EndpointSpec> for ListZones { fn method(&self) -> Method { Method::GET } fn path(&self) -> String { "zones".to_string() } - fn query(&self) -> Option { - Some(self.params.clone()) + #[inline] + fn query(&self) -> Option { + serialize_query(&self.params) } } @@ -34,7 +36,7 @@ impl Endpoint, ListZonesParams> for ListZones { pub struct ZoneDetails<'a> { pub identifier: &'a str, } -impl<'a> Endpoint for ZoneDetails<'a> { +impl<'a> EndpointSpec for ZoneDetails<'a> { fn method(&self) -> Method { Method::GET } @@ -48,7 +50,7 @@ impl<'a> Endpoint for ZoneDetails<'a> { pub struct CreateZone<'a> { pub params: CreateZoneParams<'a>, } -impl<'a> Endpoint<(), (), CreateZoneParams<'a>> for CreateZone<'a> { +impl<'a> EndpointSpec<()> for CreateZone<'a> { fn method(&self) -> Method { Method::POST } @@ -57,8 +59,10 @@ impl<'a> Endpoint<(), (), CreateZoneParams<'a>> for CreateZone<'a> { "zones".to_string() } - fn body(&self) -> Option> { - Some(self.params.clone()) + #[inline] + fn body(&self) -> Option { + let body = serde_json::to_string(&self.params).unwrap(); + Some(body) } } diff --git a/cloudflare/src/framework/async_api.rs b/cloudflare/src/framework/async_api.rs index da110bb4..de245d31 100644 --- a/cloudflare/src/framework/async_api.rs +++ b/cloudflare/src/framework/async_api.rs @@ -6,7 +6,6 @@ use crate::framework::{ response::{ApiResponse, ApiResult}, Environment, HttpApiClientConfig, }; -use serde::Serialize; use std::net::SocketAddr; /// A Cloudflare API client that makes requests asynchronously. @@ -59,24 +58,24 @@ impl Client { } /// Issue an API request of the given type. - pub async fn request( + pub async fn request( &self, - endpoint: &(dyn Endpoint), + endpoint: &(dyn Endpoint), ) -> ApiResponse where ResultType: ApiResult, - QueryType: Serialize, - BodyType: Serialize, { // Build the request let mut request = self .http_client - .request(endpoint.method(), endpoint.url(&self.environment)) - .query(&endpoint.query()); + .request(endpoint.method(), endpoint.url(&self.environment)); if let Some(body) = endpoint.body() { - request = request.body(serde_json::to_string(&body).unwrap()); - request = request.header(reqwest::header::CONTENT_TYPE, endpoint.content_type()); + request = request.body(body); + request = request.header( + reqwest::header::CONTENT_TYPE, + endpoint.content_type().as_ref(), + ); } request = request.auth(&self.credentials); diff --git a/cloudflare/src/framework/blocking_api.rs b/cloudflare/src/framework/blocking_api.rs index 7a3c5162..c3a24f62 100644 --- a/cloudflare/src/framework/blocking_api.rs +++ b/cloudflare/src/framework/blocking_api.rs @@ -1,5 +1,4 @@ use reqwest::blocking::RequestBuilder; -use serde::Serialize; use std::net::SocketAddr; use crate::framework::auth::Credentials; @@ -38,24 +37,24 @@ impl HttpApiClient { // TODO: This should probably just implement request for the Reqwest client itself :) // TODO: It should also probably be called `ReqwestApiClient` rather than `HttpApiClient`. /// Synchronously send a request to the Cloudflare API. - pub fn request( + pub fn request( &self, - endpoint: &dyn endpoint::Endpoint, + endpoint: &dyn endpoint::Endpoint, ) -> response::ApiResponse where ResultType: response::ApiResult, - QueryType: Serialize, - BodyType: Serialize, { // Build the request let mut request = self .http_client - .request(endpoint.method(), endpoint.url(&self.environment)) - .query(&endpoint.query()); + .request(endpoint.method(), endpoint.url(&self.environment)); if let Some(body) = endpoint.body() { - request = request.body(serde_json::to_string(&body).unwrap()); - request = request.header(reqwest::header::CONTENT_TYPE, endpoint.content_type()); + request = request.body(body); + request = request.header( + reqwest::header::CONTENT_TYPE, + endpoint.content_type().as_ref(), + ); } request = request.auth(&self.credentials); diff --git a/cloudflare/src/framework/endpoint.rs b/cloudflare/src/framework/endpoint.rs index d67620a2..ce579c68 100644 --- a/cloudflare/src/framework/endpoint.rs +++ b/cloudflare/src/framework/endpoint.rs @@ -1,28 +1,79 @@ use crate::framework::response::ApiResult; use crate::framework::Environment; use serde::Serialize; +use std::borrow::Cow; use url::Url; pub use http::Method; -pub trait Endpoint -where - ResultType: ApiResult, - QueryType: Serialize, - BodyType: Serialize, -{ - fn method(&self) -> Method; - fn path(&self) -> String; - fn query(&self) -> Option { - None - } - fn body(&self) -> Option { - None - } - fn url(&self, environment: &Environment) -> Url { - Url::from(environment).join(&self.path()).unwrap() - } - fn content_type(&self) -> String { - "application/json".to_owned() +#[cfg(feature = "endpoint-spec")] +pub use spec::EndpointSpec; +#[cfg(not(feature = "endpoint-spec"))] +pub(crate) use spec::EndpointSpec; + +// This is the internal-only representation. To avoid bloat from monomorphization, the query +// string, body, etc are generally not exposed publicly, though it can be exposed via the +// "endpoint-spec" feature. +mod spec { + use super::*; + + /// Represents a specification for an API call that can be built into an HTTP request and sent. + /// New endpoints should implement this trait. + /// + /// If the request succeeds, the call will resolve to a `ResultType`. + pub trait EndpointSpec + where + ResultType: ApiResult, + { + /// The HTTP Method used for this endpoint (e.g. GET, PATCH, DELETE) + fn method(&self) -> http::Method; + + /// The relative URL path for this endpoint + fn path(&self) -> String; + + /// The url-encoded query string associated with this endpoint. Defaults to `None`. + /// + /// Implementors should inline this. + #[inline] + fn query(&self) -> Option { + None + } + + /// The HTTP body associated with this endpoint. If not implemented, defaults to `None`. + /// + /// Implementors should inline this. + #[inline] + fn body(&self) -> Option { + None + } + + /// Builds and returns a formatted full URL, including query, for the endpoint. + /// + /// Implementors should generally not override this. + fn url(&self, environment: &Environment) -> Url { + let mut url = Url::from(environment).join(&self.path()).unwrap(); + url.set_query(self.query().as_deref()); + url + } + + /// If `body` is populated, indicates the body MIME type (defaults to JSON). + /// + /// Implementors generally do not need to override this. + fn content_type(&self) -> Cow<'static, str> { + Cow::Borrowed("application/json") + } } } +// Auto-implement the public Endpoint trait for EndpointInternal implementors. +impl> Endpoint for U {} + +/// An API call that can be built into an HTTP request and sent. +/// +/// If the request succeeds, the call will resolve to a `ResultType`. +pub trait Endpoint: spec::EndpointSpec {} + +/// A utility function for serializing parameters into a URL query string. +#[inline] +pub(crate) fn serialize_query(q: &Q) -> Option { + serde_urlencoded::to_string(q).ok() +}