Procedures
The execute_procedure
function is responsible for executing a single procedure:
fn execute_procedure(
state: &mut AppState,
name: &models::ProcedureName,
arguments: &BTreeMap<models::ArgumentName, serde_json::Value>,
fields: &Option<models::NestedField>,
collection_relationships: &BTreeMap<models::RelationshipName, models::Relationship>,
) -> std::result::Result<models::MutationOperationResults, (StatusCode, Json<models::ErrorResponse>)>
The function receives the application state
, along with the name
of the procedure to invoke, a list of arguments
, a list of fields
to return, and a list of collection_relationships
.
The function matches on the name of the procedure, and fails if the name is not recognized. We will walk through each procedure in turn.
{
match name.as_str() {
"upsert_article" => {
execute_upsert_article(state, arguments, fields, collection_relationships)
}
"delete_articles" => {
execute_delete_articles(state, arguments, fields, collection_relationships)
}
_ => Err((
StatusCode::BAD_REQUEST,
Json(models::ErrorResponse {
message: "unknown procedure".into(),
details: serde_json::Value::Null,
}),
)),
}
}
upsert_article
The upsert_article
procedure is implemented by the execute_upsert_article
function.
The execute_upsert_article
function reads the article
argument from the arguments
list, failing if it is not found or invalid.
It then inserts or updates that article in the application state, depending on whether or not an article with that id
already exists or not.
Finally, it delegates to the eval_nested_field
function to evaluate any nested fields, and returns the selected fields in the result:
fn execute_upsert_article(
state: &mut AppState,
arguments: &BTreeMap<models::ArgumentName, serde_json::Value>,
fields: &Option<models::NestedField>,
collection_relationships: &BTreeMap<models::RelationshipName, models::Relationship>,
) -> std::result::Result<models::MutationOperationResults, (StatusCode, Json<models::ErrorResponse>)>
{
let article = arguments.get("article").ok_or((
StatusCode::BAD_REQUEST,
Json(models::ErrorResponse {
message: "Expected argument 'article'".into(),
details: serde_json::Value::Null,
}),
))?;
let article_obj: Row = serde_json::from_value(article.clone()).map_err(|_| {
(
StatusCode::BAD_REQUEST,
Json(models::ErrorResponse {
message: "article must be an object".into(),
details: serde_json::Value::Null,
}),
)
})?;
let id = article_obj.get("id").ok_or((
StatusCode::BAD_REQUEST,
Json(models::ErrorResponse {
message: "article missing field 'id'".into(),
details: serde_json::Value::Null,
}),
))?;
let id_int = id
.as_i64()
.ok_or((
StatusCode::BAD_REQUEST,
Json(models::ErrorResponse {
message: "id must be an integer".into(),
details: serde_json::Value::Null,
}),
))?
.try_into()
.map_err(|_| {
(
StatusCode::BAD_REQUEST,
Json(models::ErrorResponse {
message: "id out of range".into(),
details: serde_json::Value::Null,
}),
)
})?;
let old_row = state.articles.insert(id_int, article_obj);
Ok(models::MutationOperationResults::Procedure {
result: old_row.map_or(Ok(serde_json::Value::Null), |old_row| {
let old_row_value = serde_json::to_value(old_row).map_err(|_| {
(
StatusCode::INTERNAL_SERVER_ERROR,
Json(models::ErrorResponse {
message: "cannot encode response".into(),
details: serde_json::Value::Null,
}),
)
})?;
let old_row_fields = match fields {
None => Ok(models::RowFieldValue(old_row_value)),
Some(nested_field) => eval_nested_field(
collection_relationships,
&BTreeMap::new(),
state,
old_row_value,
nested_field,
),
}?;
Ok(old_row_fields.0)
})?,
})
}
delete_articles
The delete_articles
procedure is implemented by the execute_delete_articles
function.
It is provided as an example of a procedure with a predicate type as the type of an argument.
The execute_delete_articles
function reads the where
argument from the arguments
list, failing if it is not found or invalid.
It then deletes all articles in the application state which match the predicate, and returns a list of the deleted rows.
This function delegates to the eval_nested_field
function to evaluate any nested fields, and returns the selected fields in the result:
fn execute_delete_articles(
state: &mut AppState,
arguments: &BTreeMap<models::ArgumentName, serde_json::Value>,
fields: &Option<models::NestedField>,
collection_relationships: &BTreeMap<models::RelationshipName, models::Relationship>,
) -> std::result::Result<models::MutationOperationResults, (StatusCode, Json<models::ErrorResponse>)>
{
let predicate_value = arguments.get("where").ok_or((
StatusCode::BAD_REQUEST,
Json(models::ErrorResponse {
message: "Expected argument 'where'".into(),
details: serde_json::Value::Null,
}),
))?;
let predicate: models::Expression =
serde_json::from_value(predicate_value.clone()).map_err(|_| {
(
StatusCode::BAD_REQUEST,
Json(models::ErrorResponse {
message: "Bad predicate".into(),
details: serde_json::Value::Null,
}),
)
})?;
let mut removed: Vec<Row> = vec![];
let state_snapshot = state.clone();
for article in state.articles.values_mut() {
if eval_expression(
&BTreeMap::new(),
&BTreeMap::new(),
&state_snapshot,
&predicate,
article,
article,
)? {
removed.push(article.clone());
}
}
let removed_value = serde_json::to_value(removed).map_err(|_| {
(
StatusCode::INTERNAL_SERVER_ERROR,
Json(models::ErrorResponse {
message: "cannot encode response".into(),
details: serde_json::Value::Null,
}),
)
})?;
let removed_fields = match fields {
None => Ok(models::RowFieldValue(removed_value)),
Some(nested_field) => eval_nested_field(
collection_relationships,
&BTreeMap::new(),
&state_snapshot,
removed_value,
nested_field,
),
}?;
Ok(models::MutationOperationResults::Procedure {
result: removed_fields.0,
})
}