use parse_rust_core::{ErrorCode, ParseError, ParseMap, ParseValue};
use parse_rust_rest::{FindOptions, ParsedClause, ParsedWhere};
use parse_rust_storage::{Constraint, StorageAdapter};
use serde_json::{json, Value as Json};
use crate::auth::Authority;
use crate::params::Params;
use crate::request::RequestContext;
use crate::state::AppState;
pub const SESSION_CLASS: &str = "_Session";
pub fn body_of(row: &ParseMap) -> Json {
let row = parse_rust_rest::to_response_body(row);
serde_json::from_str(&ParseValue::Object(row).to_json()).unwrap_or(Json::Null)
}
fn decode_body(
value: &Json,
path: parse_rust_core::op::OpPath,
) -> Result<parse_rust_rest::WriteBody, ParseError> {
let body = parse_rust_rest::decode_write_body(value, path)?;
parse_rust_rest::reject_reserved_keys_in(body.keys().map(String::as_str))?;
Ok(body)
}
pub async fn find_core(
state: &AppState,
rc: &RequestContext,
authority: &Authority,
class_name: &str,
params: &Params,
) -> Result<Json, ParseError> {
parse_rust_rest::enforce_class_security(
class_name,
authority.is_privileged(),
"find",
rc.options.error_detail,
)?;
params.reject_unknown_find_keys()?;
let where_ = params.parse_where()?;
let options = params.find_options()?;
let wants_count = params.wants_count();
let ctx = rc.ctx(state.storage());
let results = parse_rust_rest::find(&ctx, class_name, where_.clone(), options).await?;
let mut body = json!({ "results": results.iter().map(body_of).collect::<Vec<_>>() });
if wants_count {
let n = parse_rust_rest::count(&ctx, class_name, where_).await?;
body["count"] = json!(n);
}
Ok(body)
}
pub async fn get_core(
state: &AppState,
rc: &RequestContext,
authority: &Authority,
class_name: &str,
object_id: &str,
params: &Params,
) -> Result<Json, ParseError> {
parse_rust_rest::enforce_class_security(
class_name,
authority.is_privileged(),
"get",
rc.options.error_detail,
)?;
params.reject_unknown_get_keys()?;
let options = FindOptions {
limit: Some(1),
..params.get_options()?
};
let ctx = rc.ctx(state.storage());
let row = parse_rust_rest::get(&ctx, class_name, object_id, options).await?;
Ok(body_of(&row))
}
pub async fn create_core(
state: &AppState,
rc: &RequestContext,
authority: &Authority,
class_name: &str,
body: &Json,
) -> Result<Json, ParseError> {
parse_rust_rest::enforce_class_security(
class_name,
authority.is_privileged(),
"create",
rc.options.error_detail,
)?;
let mut body = decode_body(body, parse_rust_core::op::OpPath::Create)?;
parse_rust_rest::enforce_object_id_policy(&body, state.config().allow_custom_object_id)?;
if class_name == crate::routes::users::USER_CLASS {
crate::routes::users::reject_role_prefixed_object_id(&body, rc)?;
crate::routes::users::require_create_credentials(&body)?;
crate::routes::users::validate_user_identity(state, rc, &body, "").await?;
crate::routes::users::prepare_user_write(&mut body, true).await?;
}
let ctx = rc.ctx(state.storage());
let res = parse_rust_rest::create(&ctx, class_name, body)
.await
.map_err(|e| {
if class_name == crate::routes::users::USER_CLASS {
crate::routes::users::map_duplicate(e)
} else {
e
}
})?;
let mut out = json!({
"objectId": res.object_id,
"createdAt": res.created_at.to_iso(),
});
merge_echo(&mut out, res.echoed, rc, class_name);
Ok(out)
}
pub async fn update_core(
state: &AppState,
rc: &RequestContext,
authority: &Authority,
class_name: &str,
object_id: &str,
body: &Json,
) -> Result<Json, ParseError> {
parse_rust_rest::enforce_class_security(
class_name,
authority.is_privileged(),
"update",
rc.options.error_detail,
)?;
let mut body = decode_body(body, parse_rust_core::op::OpPath::Update)?;
let is_user = class_name == crate::routes::users::USER_CLASS;
let changes_password = is_user
&& matches!(
body.get("password"),
Some(parse_rust_core::FieldWrite::Value(ParseValue::String(_)))
);
if is_user {
crate::routes::users::enforce_user_update_policy(&body, rc, authority, object_id)?;
crate::routes::users::validate_user_identity(state, rc, &body, object_id).await?;
crate::routes::users::force_owner_into_acl(&mut body, object_id, authority.is_privileged());
crate::routes::users::prepare_user_write(&mut body, false).await?;
}
let ctx = rc.ctx(state.storage());
let res = parse_rust_rest::update(&ctx, class_name, object_id, body)
.await
.map_err(|e| {
if is_user {
crate::routes::users::map_duplicate(e)
} else {
e
}
})?;
let mut out = json!({ "updatedAt": res.updated_at.to_iso() });
merge_echo(&mut out, res.echoed, rc, class_name);
if changes_password {
parse_rust_auth::revoke_all_for_user(state.storage(), object_id).await?;
if !authority.is_privileged() {
let session = parse_rust_auth::create_session(
state.storage(),
&state.config().session,
parse_rust_auth::NewSession {
user_object_id: object_id,
created_with: None,
installation_id: rc.installation_id.as_deref(),
},
)
.await?;
out["sessionToken"] = json!(session.session_token);
}
}
Ok(out)
}
pub async fn delete_core(
state: &AppState,
rc: &RequestContext,
authority: &Authority,
class_name: &str,
object_id: &str,
) -> Result<Json, ParseError> {
parse_rust_rest::enforce_class_security(
class_name,
authority.is_privileged(),
"delete",
rc.options.error_detail,
)?;
let ctx = rc.ctx(state.storage());
parse_rust_rest::delete(&ctx, class_name, object_id).await?;
Ok(json!({}))
}
fn merge_echo(out: &mut Json, echoed: ParseMap, rc: &RequestContext, class_name: &str) {
if echoed.is_empty() {
return;
}
let mut echoed = echoed;
if !rc.save_response_exempt && !rc.is_master() {
if let Some(plan) = parse_rust_rest::clp::plan_protected_fields(
class_name,
rc.snapshot.clp(class_name),
&rc.scope,
None,
&rc.options,
) {
for field in plan.strip {
echoed.shift_remove(&field);
}
}
}
let Json::Object(map) = out else { return };
if let Json::Object(rendered) = body_of(&echoed) {
for (key, value) in rendered {
map.insert(key, value);
}
}
}
pub async fn delete_session_core(
state: &AppState,
rc: &RequestContext,
object_id: &str,
) -> Result<Json, ParseError> {
let mut where_ = ParsedWhere::default();
where_.push(ParsedClause::Field(Constraint::equal(
"objectId",
ParseValue::String(object_id.to_string()),
)));
let ctx = rc.ctx(state.storage());
let rows = parse_rust_rest::find(
&ctx,
SESSION_CLASS,
where_,
FindOptions {
limit: Some(1),
..Default::default()
},
)
.await?;
if rows.is_empty() {
return Err(ParseError::new(
ErrorCode::ObjectNotFound,
"Object not found for delete.",
));
}
let schema = rc.snapshot.get_or_default(SESSION_CLASS);
let query = parse_rust_storage::Query::from_constraints(vec![Constraint::equal(
"objectId",
ParseValue::String(object_id.to_string()),
)]);
state.storage().delete(&schema, &query).await?;
Ok(json!({}))
}