diff --git a/phono-server/src/presentation_form.rs b/phono-server/src/presentation_form.rs index 469d789..f5edce7 100644 --- a/phono-server/src/presentation_form.rs +++ b/phono-server/src/presentation_form.rs @@ -8,7 +8,12 @@ use serde::Deserialize; use crate::errors::AppError; /// A subset of an HTTP form that represents a [`Presentation`] in its component -/// parts. It may be merged into a larger HTTP form using `serde(flatten)`. +/// parts. +/// +/// WARNING: Though this was originally designed to be combined with +/// `#[serde(flatten)]`, Serde's buffering strategy causes `flatten` to break +/// deserialization of `Vec<_>` fields when provided with a sequence of length +/// == 1. `#[serde(flatten)]` should be avoided. #[derive(Clone, Debug, Deserialize)] pub(crate) struct PresentationForm { pub(crate) presentation_tag: String, diff --git a/phono-server/src/routes/relations_single/add_field_handler.rs b/phono-server/src/routes/relations_single/add_field_handler.rs index 224db1b..3fe97d9 100644 --- a/phono-server/src/routes/relations_single/add_field_handler.rs +++ b/phono-server/src/routes/relations_single/add_field_handler.rs @@ -34,13 +34,37 @@ pub(super) struct PathParams { } // FIXME: validate name, prevent leading underscore -#[derive(Debug, Deserialize)] +#[derive(Clone, Debug, Deserialize)] pub(super) struct FormBody { name: String, + table_label: String, - #[serde(flatten)] - presentation_form: PresentationForm, + presentation_tag: String, + + #[serde(default)] + dropdown_option_colors: Vec, + + #[serde(default)] + dropdown_option_values: Vec, + + #[serde(default)] + text_input_mode: String, + + #[serde(default)] + timestamp_format: String, +} + +impl From for PresentationForm { + fn from(value: FormBody) -> Self { + Self { + presentation_tag: value.presentation_tag, + dropdown_option_colors: value.dropdown_option_colors, + dropdown_option_values: value.dropdown_option_values, + text_input_mode: value.text_input_mode, + timestamp_format: value.timestamp_format, + } + } } /// HTTP POST handler for adding a [`Field`] to a [`Portal`]. If the field name @@ -84,7 +108,7 @@ pub(super) async fn post( .fetch_one() .await?; - let presentation = Presentation::try_from(form.presentation_form)?; + let presentation = Presentation::try_from(Into::::into(form.clone()))?; query(&format!( "alter table {ident} add column if not exists {col} {typ}", diff --git a/phono-server/src/routes/relations_single/update_field_handler.rs b/phono-server/src/routes/relations_single/update_field_handler.rs index 1c836b7..c1b674a 100644 --- a/phono-server/src/routes/relations_single/update_field_handler.rs +++ b/phono-server/src/routes/relations_single/update_field_handler.rs @@ -21,13 +21,36 @@ pub(super) struct PathParams { workspace_id: Uuid, } -#[derive(Debug, Deserialize, Validate)] +#[derive(Clone, Debug, Deserialize, Validate)] pub(super) struct FormBody { field_id: Uuid, table_label: String, - #[serde(flatten)] - presentation_form: PresentationForm, + presentation_tag: String, + + #[serde(default)] + dropdown_option_colors: Vec, + + #[serde(default)] + dropdown_option_values: Vec, + + #[serde(default)] + text_input_mode: String, + + #[serde(default)] + timestamp_format: String, +} + +impl From for PresentationForm { + fn from(value: FormBody) -> Self { + Self { + presentation_tag: value.presentation_tag, + dropdown_option_colors: value.dropdown_option_colors, + dropdown_option_values: value.dropdown_option_values, + text_input_mode: value.text_input_mode, + timestamp_format: value.timestamp_format, + } + } } /// HTTP POST handler for updating an existing [`Field`]. @@ -44,11 +67,7 @@ pub(super) async fn post( rel_oid, workspace_id, }): Path, - ValidatedForm(FormBody { - field_id, - table_label, - presentation_form, - }): ValidatedForm, + ValidatedForm(form): ValidatedForm, ) -> Result { // FIXME CSRF @@ -57,18 +76,20 @@ pub(super) async fn post( // Ensure field exists and belongs to portal. Field::belonging_to_portal(portal_id) - .with_id(field_id) + .with_id(form.field_id) .fetch_one(&mut app_db) .await?; Field::update() - .id(field_id) - .table_label(if table_label.is_empty() { + .id(form.field_id) + .table_label(if form.table_label.is_empty() { None } else { - Some(table_label) + Some(form.table_label.clone()) }) - .presentation(Presentation::try_from(presentation_form)?) + .presentation(Presentation::try_from(Into::::into( + form, + ))?) .build()? .execute(&mut app_db) .await?;