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}