amazon_rekognition/api/actions/
action.rs

1//! https://stackoverflow.com/questions/48527517/an-example-of-calling-aws-rekognition-http-api-from-php
2
3use std::{fmt, marker::PhantomData};
4
5use amazon_sigv4_kit::{sign_http_request, SignHttpRequestError};
6use http_api_client_endpoint::{
7    http::{
8        header::{ACCEPT, CONTENT_TYPE},
9        Error as HttpError, Method, StatusCode,
10    },
11    Body, Endpoint, Request, Response,
12};
13use serde::{de::DeserializeOwned, Deserialize, Serialize};
14use serde_json::Error as SerdeJsonError;
15
16use crate::{
17    api::utils::{
18        required_header_x_amz_target_value, REQUIRED_HEADER_CONTENT_TYPE_VALUE,
19        REQUIRED_HEADER_X_AMZ_TARGET_KEY,
20    },
21    ServiceEndpoint, SERVICE_NAME,
22};
23
24//
25//
26//
27pub struct Action<'a, ReqB, ResOkB>
28where
29    ReqB: Serialize,
30    ResOkB: DeserializeOwned + fmt::Debug + Clone,
31{
32    pub access_key_id: &'a str,
33    pub secret_access_key: &'a str,
34    pub service_endpoint: ServiceEndpoint<'a>,
35    pub request_body: ReqB,
36    pub operation_name: &'a str,
37    //
38    _phantom: PhantomData<ResOkB>,
39}
40
41impl<'a, ReqB, ResOkB> fmt::Debug for Action<'a, ReqB, ResOkB>
42where
43    ReqB: Serialize + fmt::Debug,
44    ResOkB: DeserializeOwned + fmt::Debug + Clone,
45{
46    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
47        f.debug_struct("Action")
48            .field("access_key_id", &self.access_key_id)
49            .field("secret_access_key", &format_args!("******"))
50            .field("service_endpoint", &self.service_endpoint)
51            .field("request_body", &self.request_body)
52            .field("operation_name", &self.operation_name)
53            .finish()
54    }
55}
56
57impl<'a, ReqB, ResOkB> Clone for Action<'a, ReqB, ResOkB>
58where
59    ReqB: Serialize + Clone,
60    ResOkB: DeserializeOwned + fmt::Debug + Clone,
61{
62    fn clone(&self) -> Self {
63        Self {
64            access_key_id: self.access_key_id,
65            secret_access_key: self.secret_access_key,
66            service_endpoint: self.service_endpoint.clone(),
67            request_body: self.request_body.clone(),
68            operation_name: self.operation_name,
69            _phantom: PhantomData,
70        }
71    }
72}
73
74impl<'a, ReqB, ResOkB> Action<'a, ReqB, ResOkB>
75where
76    ReqB: Serialize,
77    ResOkB: DeserializeOwned + fmt::Debug + Clone,
78{
79    pub fn new(
80        access_key_id: &'a str,
81        secret_access_key: &'a str,
82        service_endpoint: ServiceEndpoint<'a>,
83        request_body: ReqB,
84        operation_name: &'a str,
85    ) -> Self {
86        Self {
87            access_key_id,
88            secret_access_key,
89            service_endpoint,
90            request_body,
91            operation_name,
92            _phantom: PhantomData,
93        }
94    }
95}
96
97//
98impl<'a, ReqB, ResOkB> Endpoint for Action<'a, ReqB, ResOkB>
99where
100    ReqB: Serialize,
101    ResOkB: DeserializeOwned + fmt::Debug + Clone,
102{
103    type RenderRequestError = ActionEndpointError;
104
105    type ParseResponseOutput = ActionEndpointRet<ResOkB>;
106
107    type ParseResponseError = ActionEndpointError;
108
109    fn render_request(&self) -> Result<Request<Body>, Self::RenderRequestError> {
110        let url = self.service_endpoint.url();
111
112        let body = serde_json::to_vec(&self.request_body)
113            .map_err(ActionEndpointError::SerRequestBodyFailed)?;
114
115        let request = Request::builder()
116            .method(Method::POST)
117            .uri(url)
118            .header(CONTENT_TYPE, REQUIRED_HEADER_CONTENT_TYPE_VALUE)
119            .header(ACCEPT, REQUIRED_HEADER_CONTENT_TYPE_VALUE)
120            .header(
121                REQUIRED_HEADER_X_AMZ_TARGET_KEY,
122                required_header_x_amz_target_value(self.operation_name),
123            )
124            .body(body)
125            .map_err(ActionEndpointError::MakeRequestFailed)?;
126
127        let request = sign_http_request(
128            request,
129            self.access_key_id,
130            self.secret_access_key,
131            self.service_endpoint.region(),
132            SERVICE_NAME,
133            |ss| ss,
134            |sp| sp,
135        )
136        .map_err(ActionEndpointError::SignHttpRequestError)?;
137
138        Ok(request)
139    }
140
141    fn parse_response(
142        &self,
143        response: Response<Body>,
144    ) -> Result<Self::ParseResponseOutput, Self::ParseResponseError> {
145        let status = response.status();
146        match status {
147            StatusCode::OK => {
148                let ok_json = serde_json::from_slice::<ResOkB>(response.body())
149                    .map_err(ActionEndpointError::DeResponseOkBodyFailed)?;
150
151                Ok(ActionEndpointRet::Ok(ok_json))
152            }
153            status => match serde_json::from_slice::<ActionResponseErrBody>(response.body()) {
154                Ok(err_json) => Ok(ActionEndpointRet::Other((status, Ok(err_json)))),
155                Err(_) => Ok(ActionEndpointRet::Other((
156                    status,
157                    Err(response.body().to_owned()),
158                ))),
159            },
160        }
161    }
162}
163
164//
165//
166//
167#[derive(Debug, Clone)]
168pub enum ActionEndpointRet<T>
169where
170    T: fmt::Debug + Clone,
171{
172    Ok(T),
173    Other((StatusCode, Result<ActionResponseErrBody, Body>)),
174}
175
176#[derive(Serialize, Deserialize, Debug, Clone)]
177#[serde(rename_all = "PascalCase")]
178pub struct ActionResponseErrBody {
179    #[serde(rename = "__type")]
180    pub r#type: String,
181    pub message: String,
182}
183
184#[derive(thiserror::Error, Debug)]
185pub enum ActionEndpointError {
186    #[error("SerRequestBodyFailed {0}")]
187    SerRequestBodyFailed(SerdeJsonError),
188    #[error("MakeRequestFailed {0}")]
189    MakeRequestFailed(HttpError),
190    #[error("SignHttpRequestError {0}")]
191    SignHttpRequestError(SignHttpRequestError),
192    #[error("DeResponseOkBodyFailed {0}")]
193    DeResponseOkBodyFailed(SerdeJsonError),
194}
195
196#[cfg(test)]
197mod tests {
198    use super::*;
199
200    #[test]
201    fn de_response_err_body() {
202        match serde_json::from_str::<ActionResponseErrBody>(include_str!(
203            "../../../tests/response_body_json_files/detect_labels_err.json"
204        )) {
205            Ok(err_json) => {
206                assert_eq!(err_json.r#type, "SerializationException");
207            }
208            Err(err) => panic!("{}", err),
209        }
210    }
211}