Procedures

The execute_procedure function is responsible for executing a single procedure:

fn execute_procedure(
    state: &mut AppState,
    name: &str,
    arguments: &BTreeMap<String, serde_json::Value>,
    fields: &Option<models::NestedField>,
    collection_relationships: &BTreeMap<String, 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 {
        "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<String, serde_json::Value>,
    fields: &Option<models::NestedField>,
    collection_relationships: &BTreeMap<String, 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 = article.as_object().ok_or((
        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 new_row = article_obj
        .iter()
        .map(|(k, v)| (k.clone(), v.clone()))
        .collect::<BTreeMap<_, _>>();
    let old_row = state.articles.insert(id_int, new_row);

    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<String, serde_json::Value>,
    fields: &Option<models::NestedField>,
    collection_relationships: &BTreeMap<String, 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,
    })
}