1use axum::extract::{Path, Query, State};
8use axum::response::{IntoResponse, Response};
9use axum::Json;
10use parse_rust_core::{ErrorCode, ParseError, ParseMap, ParseValue};
11use parse_rust_rest::AclScope;
12use parse_rust_storage::QueryOptions;
13use serde_json::{json, Value as Json_};
14use std::collections::HashMap;
15
16use crate::auth::Authority;
17use crate::response::ParseErrorResponse;
18use crate::state::AppState;
19
20fn scope_for(authority: &Authority, state: &AppState) -> Result<AclScope, ParseError> {
26 Ok(match authority {
27 Authority::Master | Authority::Maintenance => AclScope::Unrestricted,
28 Authority::Client { session_token } => match session_token {
29 Some(token) => state.scope_for_session(token)?,
31 None => AclScope::Anonymous,
32 },
33 })
34}
35
36fn body_of(row: &ParseMap) -> Json_ {
41 let row = parse_rust_rest::to_response_body(row);
42 serde_json::from_str(&ParseValue::Object(row).to_json()).unwrap_or(Json_::Null)
43}
44
45fn err(e: ParseError) -> Response {
46 ParseErrorResponse(e).into_response()
47}
48
49fn decode_body(value: Json_) -> Result<ParseMap, ParseError> {
51 let map = match parse_rust_core::classify(value)? {
52 ParseValue::Object(map) => map,
53 _ => return Err(ParseError::invalid_json("body must be an object")),
54 };
55 parse_rust_rest::reject_reserved_keys(&map)?;
58 Ok(map)
59}
60
61pub async fn create(
62 State(state): State<AppState>,
63 authority: Authority,
64 Path(class_name): Path<String>,
65 Json(body): Json<Json_>,
66) -> Response {
67 if let Err(e) = enforce_class_security(&class_name, &authority, "create") {
68 return err(e);
69 }
70 let scope = match scope_for(&authority, &state) {
71 Ok(s) => s,
72 Err(e) => return err(e),
73 };
74 let mut body = match decode_body(body) {
75 Ok(b) => b,
76 Err(e) => return err(e),
77 };
78 if class_name == "_User" {
79 if let Err(e) = super::users::hash_user_password(&mut body) {
80 return err(e);
81 }
82 super::users::ensure_user_identity_and_acl(&mut body);
83 }
84 match parse_rust_rest::create(state.storage(), &class_name, body, &scope).await {
85 Ok(res) => (
86 axum::http::StatusCode::CREATED,
87 Json(json!({
88 "objectId": res.object_id,
89 "createdAt": res.created_at.to_iso(),
90 })),
91 )
92 .into_response(),
93 Err(e) => err(e),
94 }
95}
96
97pub async fn find(
98 State(state): State<AppState>,
99 authority: Authority,
100 Path(class_name): Path<String>,
101 Query(params): Query<HashMap<String, String>>,
102) -> Response {
103 if let Err(e) = enforce_class_security(&class_name, &authority, "find") {
104 return err(e);
105 }
106 let scope = match scope_for(&authority, &state) {
107 Ok(s) => s,
108 Err(e) => return err(e),
109 };
110
111 let constraints = match params.get("where") {
112 Some(raw) => match serde_json::from_str::<Json_>(raw) {
113 Ok(v) => match parse_rust_rest::parse_where(&v) {
114 Ok(c) => c,
115 Err(e) => return err(e),
116 },
117 Err(_) => {
118 return err(ParseError::invalid_query(
119 "where must be valid JSON".to_string(),
120 ))
121 }
122 },
123 None => Vec::new(),
124 };
125
126 let wants_count = params.get("count").map(|c| c == "1").unwrap_or(false);
128
129 let options = QueryOptions {
130 limit: Some(
134 params
135 .get("limit")
136 .and_then(|v| v.parse::<u32>().ok())
137 .unwrap_or(parse_rust_storage::query::DEFAULT_LIMIT),
138 ),
139 skip: params.get("skip").and_then(|v| v.parse().ok()),
140 order: params
141 .get("order")
142 .map(|o| QueryOptions::parse_order(o))
143 .unwrap_or_default(),
144 keys: params
145 .get("keys")
146 .map(|k| k.split(',').map(str::trim).map(str::to_string).collect()),
147 };
148
149 let results = match parse_rust_rest::find(
150 state.storage(),
151 &class_name,
152 constraints.clone(),
153 options,
154 &scope,
155 )
156 .await
157 {
158 Ok(r) => r,
159 Err(e) => return err(e),
160 };
161
162 let mut body = json!({ "results": results.iter().map(body_of).collect::<Vec<_>>() });
163 if wants_count {
164 match parse_rust_rest::count(state.storage(), &class_name, constraints, &scope).await {
165 Ok(n) => {
166 body["count"] = json!(n);
167 }
168 Err(e) => return err(e),
169 }
170 }
171 Json(body).into_response()
172}
173
174pub async fn get(
175 State(state): State<AppState>,
176 authority: Authority,
177 Path((class_name, object_id)): Path<(String, String)>,
178) -> Response {
179 if let Err(e) = enforce_class_security(&class_name, &authority, "get") {
180 return err(e);
181 }
182 let scope = match scope_for(&authority, &state) {
183 Ok(s) => s,
184 Err(e) => return err(e),
185 };
186 match parse_rust_rest::get(state.storage(), &class_name, &object_id, &scope).await {
187 Ok(row) => Json(body_of(&row)).into_response(),
188 Err(e) => err(e),
189 }
190}
191
192pub async fn update(
193 State(state): State<AppState>,
194 authority: Authority,
195 Path((class_name, object_id)): Path<(String, String)>,
196 Json(body): Json<Json_>,
197) -> Response {
198 if let Err(e) = enforce_class_security(&class_name, &authority, "update") {
199 return err(e);
200 }
201 let scope = match scope_for(&authority, &state) {
202 Ok(s) => s,
203 Err(e) => return err(e),
204 };
205 let mut body = match decode_body(body) {
206 Ok(b) => b,
207 Err(e) => return err(e),
208 };
209 if class_name == "_User" {
210 if let Err(e) = super::users::hash_user_password(&mut body) {
211 return err(e);
212 }
213 }
214 match parse_rust_rest::update(state.storage(), &class_name, &object_id, body, &scope).await {
215 Ok(res) => Json(json!({ "updatedAt": res.updated_at.to_iso() })).into_response(),
216 Err(e) => err(e),
217 }
218}
219
220pub async fn delete(
221 State(state): State<AppState>,
222 authority: Authority,
223 Path((class_name, object_id)): Path<(String, String)>,
224) -> Response {
225 if let Err(e) = enforce_class_security(&class_name, &authority, "delete") {
226 return err(e);
227 }
228 let scope = match scope_for(&authority, &state) {
229 Ok(s) => s,
230 Err(e) => return err(e),
231 };
232 match parse_rust_rest::delete(state.storage(), &class_name, &object_id, &scope).await {
233 Ok(()) => Json(json!({})).into_response(),
235 Err(e) => err(e),
236 }
237}
238
239fn enforce_class_security(
248 class_name: &str,
249 authority: &Authority,
250 operation: &str,
251) -> Result<(), ParseError> {
252 if matches!(authority, Authority::Master | Authority::Maintenance) {
253 return Ok(());
254 }
255 let write_only_forbidden =
265 class_name == "_User" && matches!(operation, "create" | "update" | "delete");
266
267 let forbidden = write_only_forbidden
268 || class_name.starts_with("_Join:")
269 || matches!(
270 class_name,
271 "_Session"
272 | "_Role"
273 | "_Installation"
274 | "_JobStatus"
275 | "_PushStatus"
276 | "_Hooks"
277 | "_GlobalConfig"
278 | "_GraphQLConfig"
279 | "_JobSchedule"
280 | "_Audience"
281 | "_Idempotency"
282 );
283 if forbidden {
284 return Err(ParseError::new(
285 ErrorCode::OperationForbidden,
286 format!(
287 "Clients aren't allowed to perform the {operation} operation on the {class_name} collection."
288 ),
289 ));
290 }
291 Ok(())
292}
293
294pub async fn dispatch_collection(
300 state: State<AppState>,
301 authority: Authority,
302 path: Path<String>,
303 query: Query<HashMap<String, String>>,
304 method: Option<axum::Extension<crate::body_credentials::MethodOverride>>,
305 body: Option<Json<Json_>>,
306) -> Response {
307 match method.map(|m| m.0 .0) {
308 Some(m) if m == axum::http::Method::GET => find(state, authority, path, query).await,
309 None => match body {
311 Some(b) => create(state, authority, path, b).await,
312 None => err(ParseError::invalid_json("body must be a JSON object")),
316 },
317 Some(other) => err(ParseError::new(
319 ErrorCode::CommandUnavailable,
320 format!("unsupported method override: {other}"),
321 )),
322 }
323}
324
325pub async fn dispatch_object(
327 state: State<AppState>,
328 authority: Authority,
329 path: Path<(String, String)>,
330 method: Option<axum::Extension<crate::body_credentials::MethodOverride>>,
331 body: Option<Json<Json_>>,
332) -> Response {
333 let Some(m) = method.map(|m| m.0 .0) else {
336 return err(ParseError::new(
337 ErrorCode::CommandUnavailable,
338 "POST is not supported on an object route; use PUT, DELETE, or _method",
339 ));
340 };
341 if m == axum::http::Method::GET {
342 return get(state, authority, path).await;
343 }
344 if m == axum::http::Method::DELETE {
345 return delete(state, authority, path).await;
346 }
347 if m != axum::http::Method::PUT {
348 return err(ParseError::new(
349 ErrorCode::CommandUnavailable,
350 format!("unsupported method override: {m}"),
351 ));
352 }
353 match body {
354 Some(b) => update(state, authority, path, b).await,
355 None => err(ParseError::invalid_json("body must be a JSON object")),
356 }
357}