mas_handlers/admin/v1/policy_data/
get_latest.rs1use aide::{OperationIo, transform::TransformOperation};
6use axum::{Json, response::IntoResponse};
7use hyper::StatusCode;
8
9use crate::{
10 admin::{
11 call_context::CallContext,
12 model::PolicyData,
13 response::{ErrorResponse, SingleResponse},
14 },
15 impl_from_error_for_route,
16};
17
18#[derive(Debug, thiserror::Error, OperationIo)]
19#[aide(output_with = "Json<ErrorResponse>")]
20pub enum RouteError {
21 #[error(transparent)]
22 Internal(Box<dyn std::error::Error + Send + Sync + 'static>),
23
24 #[error("No policy data found")]
25 NotFound,
26}
27
28impl_from_error_for_route!(mas_storage::RepositoryError);
29
30impl IntoResponse for RouteError {
31 fn into_response(self) -> axum::response::Response {
32 let error = ErrorResponse::from_error(&self);
33 let status = match self {
34 Self::Internal(_) => StatusCode::INTERNAL_SERVER_ERROR,
35 Self::NotFound => StatusCode::NOT_FOUND,
36 };
37 (status, Json(error)).into_response()
38 }
39}
40
41pub fn doc(operation: TransformOperation) -> TransformOperation {
42 operation
43 .id("getLatestPolicyData")
44 .summary("Get the latest policy data")
45 .tag("policy-data")
46 .response_with::<200, Json<SingleResponse<PolicyData>>, _>(|t| {
47 let [sample, ..] = PolicyData::samples();
48 let response = SingleResponse::new_canonical(sample);
49 t.description("Latest policy data was found")
50 .example(response)
51 })
52 .response_with::<404, RouteError, _>(|t| {
53 let response = ErrorResponse::from_error(&RouteError::NotFound);
54 t.description("No policy data was found").example(response)
55 })
56}
57
58#[tracing::instrument(name = "handler.admin.v1.policy_data.get_latest", skip_all, err)]
59pub async fn handler(
60 CallContext { mut repo, .. }: CallContext,
61) -> Result<Json<SingleResponse<PolicyData>>, RouteError> {
62 let policy_data = repo
63 .policy_data()
64 .get()
65 .await?
66 .ok_or(RouteError::NotFound)?;
67
68 Ok(Json(SingleResponse::new_canonical(policy_data.into())))
69}
70
71#[cfg(test)]
72mod tests {
73 use hyper::{Request, StatusCode};
74 use insta::assert_json_snapshot;
75 use sqlx::PgPool;
76
77 use crate::test_utils::{RequestBuilderExt, ResponseExt, TestState, setup};
78
79 #[sqlx::test(migrator = "mas_storage_pg::MIGRATOR")]
80 async fn test_get_latest(pool: PgPool) {
81 setup();
82 let mut state = TestState::from_pool(pool).await.unwrap();
83 let token = state.token_with_scope("urn:mas:admin").await;
84
85 let mut rng = state.rng();
86 let mut repo = state.repository().await.unwrap();
87
88 repo.policy_data()
89 .set(
90 &mut rng,
91 &state.clock,
92 serde_json::json!({"hello": "world"}),
93 )
94 .await
95 .unwrap();
96
97 repo.save().await.unwrap();
98
99 let request = Request::get("/api/admin/v1/policy-data/latest")
100 .bearer(&token)
101 .empty();
102 let response = state.request(request).await;
103 response.assert_status(StatusCode::OK);
104 let body: serde_json::Value = response.json();
105 assert_json_snapshot!(body, @r###"
106 {
107 "data": {
108 "type": "policy-data",
109 "id": "01FSHN9AG0MZAA6S4AF7CTV32E",
110 "attributes": {
111 "created_at": "2022-01-16T14:40:00Z",
112 "data": {
113 "hello": "world"
114 }
115 },
116 "links": {
117 "self": "/api/admin/v1/policy-data/01FSHN9AG0MZAA6S4AF7CTV32E"
118 }
119 },
120 "links": {
121 "self": "/api/admin/v1/policy-data/01FSHN9AG0MZAA6S4AF7CTV32E"
122 }
123 }
124 "###);
125 }
126
127 #[sqlx::test(migrator = "mas_storage_pg::MIGRATOR")]
128 async fn test_get_no_latest(pool: PgPool) {
129 setup();
130 let mut state = TestState::from_pool(pool).await.unwrap();
131 let token = state.token_with_scope("urn:mas:admin").await;
132
133 let request = Request::get("/api/admin/v1/policy-data/latest")
134 .bearer(&token)
135 .empty();
136 let response = state.request(request).await;
137 response.assert_status(StatusCode::NOT_FOUND);
138 let body: serde_json::Value = response.json();
139 assert_json_snapshot!(body, @r###"
140 {
141 "errors": [
142 {
143 "title": "No policy data found"
144 }
145 ]
146 }
147 "###);
148 }
149}