cloudflare_but_works/framework/endpoint.rs
1use crate::framework::response::ApiResult;
2use crate::framework::Environment;
3use serde::Serialize;
4use std::borrow::Cow;
5use url::Url;
6
7pub use http::Method;
8
9pub(crate) use spec::EndpointSpec;
10
11pub enum RequestBody<'a> {
12 Json(String),
13 Raw(Vec<u8>),
14 MultiPart(&'a dyn MultipartBody),
15}
16
17pub enum MultipartPart {
18 Text(String),
19 Bytes(Vec<u8>),
20}
21
22/// Helper trait for endpoints that require a multipart body.
23///
24/// Mainly exists to allow for client-agnostic multipart body implementations, until reqwest has a
25/// conversion between blocking::multipart::Form/Part and async_impl::multipart::Form/Part.
26pub trait MultipartBody {
27 /// Returns a list of parts to be included in a multipart request.
28 /// Each part is a tuple of the part name and the part data.
29 //
30 // Client-agnostic implementation, because of the non-interoperability
31 // between reqwest's blocking::multipart::Form/Part and async_impl::multipart::Form/Part.
32 // Refactor this when reqwest has some sort of conversion between the two.
33 fn parts(&self) -> Vec<(String, MultipartPart)>;
34}
35
36pub mod spec {
37 use super::*;
38
39 /// Represents a specification for an API call that can be built into an HTTP request and sent.
40 /// New endpoints should implement this trait.
41 ///
42 /// If the request succeeds, the call will resolve to a `ResultType`.
43 pub trait EndpointSpec {
44 /// If the body of the response is raw bytes (Vec<u8>), set this to `true`. Defaults to `false`.
45 const IS_RAW_BODY: bool = false;
46
47 /// The JSON response type for this endpoint, if any.
48 ///
49 /// For endpoints that return either raw bytes or nothing, this should be `()`.
50 type JsonResponse: ApiResult;
51 /// The final response type for this endpoint.
52 ///
53 /// For endpoints that return raw bytes, this should be `Vec<u8>`.
54 ///
55 /// For endpoints that return JSON, this should be `ApiSuccess<Self::JsonResponse>`.
56 type ResponseType;
57
58 /// The HTTP Method used for this endpoint (e.g. GET, PATCH, DELETE)
59 fn method(&self) -> Method;
60
61 /// The relative URL path for this endpoint
62 fn path(&self) -> String;
63
64 /// The url-encoded query string associated with this endpoint. Defaults to `None`.
65 ///
66 /// Implementors should inline this.
67 #[inline]
68 fn query(&self) -> Option<String> {
69 None
70 }
71
72 /// The HTTP body associated with this endpoint. If not implemented, defaults to `None`.
73 ///
74 /// Implementors should inline this.
75 #[inline]
76 fn body(&self) -> Option<RequestBody> {
77 None
78 }
79
80 /// Builds and returns a formatted full URL, including query, for the endpoint.
81 ///
82 /// Implementors should generally not override this.
83 fn url(&self, environment: &Environment) -> Url {
84 let mut url = Url::from(environment).join(&self.path()).unwrap();
85 url.set_query(self.query().as_deref());
86 url
87 }
88
89 //noinspection RsConstantConditionIf
90 /// If `body` is populated, indicates the body MIME type (defaults to JSON).
91 ///
92 /// Implementors generally do not need to override this.
93 fn content_type(&self) -> Option<Cow<'static, str>> {
94 match Self::body(self) {
95 Some(RequestBody::Json(_)) => Some(Cow::Borrowed("application/json")),
96 Some(RequestBody::Raw(_)) => Some(Cow::Borrowed("application/octet-stream")),
97 Some(RequestBody::MultiPart(_)) => Some(Cow::Borrowed("multipart/form-data")),
98 None => None,
99 }
100 }
101 }
102}
103// Auto-implement the public Endpoint trait for EndpointInternal implementors.
104impl<T: ApiResult, U: EndpointSpec> Endpoint<T> for U {}
105
106/// An API call that can be built into an HTTP request and sent.
107///
108/// If the request succeeds, the call will resolve to a `ResultType`.
109pub trait Endpoint<ResultType: ApiResult>: EndpointSpec {}
110
111/// A utility function for serializing parameters into a URL query string.
112#[inline]
113pub fn serialize_query<Q: Serialize>(q: &Q) -> Option<String> {
114 serde_urlencoded::to_string(q).ok()
115}