amazon_rekognition/api/actions/
action.rs1use 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
24pub 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 _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
97impl<'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#[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}