From 78345d406c06b0b9123c608d09c2c64d08db79a1 Mon Sep 17 00:00:00 2001 From: nicomiguelino Date: Sun, 6 Sep 2026 16:34:42 -0700 Subject: [PATCH 1/5] Fix help text column showing raw JSON for structured settings Extract the human readable string from structured help_text values instead of printing the raw JSON in edge-app setting list output. --- src/api/edge_app/setting.rs | 12 ++++++++++++ src/commands/mod.rs | 6 ++++++ 2 files changed, 18 insertions(+) diff --git a/src/api/edge_app/setting.rs b/src/api/edge_app/setting.rs index b2de3287..99baddbb 100644 --- a/src/api/edge_app/setting.rs +++ b/src/api/edge_app/setting.rs @@ -270,6 +270,18 @@ fn is_structured_help_text(help_text: &str) -> bool { serde_json::from_str::(help_text).is_ok_and(|value| value.is_object()) } +pub fn extract_display_help_text(help_text: &str) -> String { + match serde_json::from_str::(help_text) { + Ok(Value::Object(object)) => object + .get("properties") + .and_then(|properties| properties.get("help_text")) + .and_then(|value| value.as_str()) + .map(|value| value.to_string()) + .unwrap_or_else(|| help_text.to_string()), + _ => help_text.to_string(), + } +} + fn has_malformed_properties(help_text: &str) -> bool { let Ok(Value::Object(object)) = serde_json::from_str::(help_text) else { return false; diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 8d9d0e39..edfbd62a 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -429,6 +429,12 @@ impl Formatter for EdgeAppSettings { } return Cell::new(""); } + if field_name.eq("help_text") { + let help_text = field_value.as_str().unwrap_or_default(); + return Cell::new( + &crate::api::edge_app::setting::extract_display_help_text(help_text), + ); + } debug!("field_name: {field_name}, field_value: {field_value:?}"); Cell::new(field_value.as_str().unwrap_or_default()) }, From 47d587766e799c01da74133f2cc4de7bf51cc948 Mon Sep 17 00:00:00 2001 From: nicomiguelino Date: Sun, 6 Sep 2026 19:53:11 -0700 Subject: [PATCH 2/5] Address review feedback on help text extraction Scope extract_display_help_text to the crate and add a formatter test covering both structured and plain help_text values. --- src/api/edge_app/setting.rs | 2 +- src/commands/mod.rs | 38 +++++++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/src/api/edge_app/setting.rs b/src/api/edge_app/setting.rs index 99baddbb..6844d359 100644 --- a/src/api/edge_app/setting.rs +++ b/src/api/edge_app/setting.rs @@ -270,7 +270,7 @@ fn is_structured_help_text(help_text: &str) -> bool { serde_json::from_str::(help_text).is_ok_and(|value| value.is_object()) } -pub fn extract_display_help_text(help_text: &str) -> String { +pub(crate) fn extract_display_help_text(help_text: &str) -> String { match serde_json::from_str::(help_text) { Ok(Value::Object(object)) => object .get("properties") diff --git a/src/commands/mod.rs b/src/commands/mod.rs index edfbd62a..532141cf 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -697,6 +697,44 @@ mod tests { assert!(!EdgeAppSettings::supports_csv()); } + #[test] + fn test_edge_app_settings_formatter_extracts_structured_help_text() { + let data = r#"[ + { + "name": "enable_analytics", + "title": "Enable Analytics", + "edge_app_setting_values": [], + "default_value": "true", + "optional": true, + "type": "string", + "help_text": "{\"properties\":{\"display_order\":0,\"help_text\":\"Enable or disable Sentry and Google Analytics integrations.\"},\"schema_version\":1}" + }, + { + "name": "override_locale", + "title": "Override Locale", + "edge_app_setting_values": [], + "default_value": "en", + "optional": true, + "type": "string", + "help_text": "Override the default locale with a supported language code." + } + ]"#; + let settings = EdgeAppSettings::new(serde_json::from_str(data).unwrap()); + + let output = settings.format(OutputType::HumanReadable); + assert_eq!( + output, + r#"+------------------+------------------+-------+---------------+----------+--------+-------------------------------------------------------------+ +| Name | Title | Value | Default value | Optional | Type | Help text | ++------------------+------------------+-------+---------------+----------+--------+-------------------------------------------------------------+ +| enable_analytics | Enable Analytics | | true | Yes | string | Enable or disable Sentry and Google Analytics integrations. | ++------------------+------------------+-------+---------------+----------+--------+-------------------------------------------------------------+ +| override_locale | Override Locale | | en | Yes | string | Override the default locale with a supported language code. | ++------------------+------------------+-------+---------------+----------+--------+-------------------------------------------------------------+ +"# + ); + } + #[test] fn test_edge_app_instance_formatter_format_output_properly() { let data = r#"[{ From 09087458d4ae561a5616f1bc2a8c571254063d49 Mon Sep 17 00:00:00 2001 From: nicomiguelino Date: Mon, 7 Sep 2026 07:44:10 -0700 Subject: [PATCH 3/5] Handle structured help_text with a missing properties.help_text Render an empty cell instead of the raw JSON when the object has no properties.help_text, and accept the field as an object value too. --- src/api/edge_app/setting.rs | 57 +++++++++++++++++++++++++++++++++---- src/commands/mod.rs | 3 +- 2 files changed, 52 insertions(+), 8 deletions(-) diff --git a/src/api/edge_app/setting.rs b/src/api/edge_app/setting.rs index 518df597..9abc1576 100644 --- a/src/api/edge_app/setting.rs +++ b/src/api/edge_app/setting.rs @@ -222,15 +222,23 @@ fn is_structured_help_text(help_text: &str) -> bool { serde_json::from_str::(help_text).is_ok_and(|value| value.is_object()) } -pub(crate) fn extract_display_help_text(help_text: &str) -> String { - match serde_json::from_str::(help_text) { - Ok(Value::Object(object)) => object +pub(crate) fn extract_display_help_text(help_text: &Value) -> String { + let object = match help_text { + Value::Object(_) => Some(help_text.clone()), + Value::String(raw) => serde_json::from_str::(raw) + .ok() + .filter(|value| value.is_object()), + _ => None, + }; + + match object { + Some(object) => object .get("properties") .and_then(|properties| properties.get("help_text")) .and_then(|value| value.as_str()) - .map(|value| value.to_string()) - .unwrap_or_else(|| help_text.to_string()), - _ => help_text.to_string(), + .unwrap_or_default() + .to_string(), + None => help_text.as_str().unwrap_or_default().to_string(), } } @@ -606,6 +614,43 @@ mod display_order_tests { assert_eq!(value["properties"]["display_order"], json!(1)); } + #[test] + fn extract_display_help_text_returns_the_nested_help_text() { + let structured = json!({ + "schema_version": 1, + "properties": { "help_text": "Say hello", "display_order": 0 } + }) + .to_string(); + + assert_eq!(extract_display_help_text(&json!(structured)), "Say hello"); + } + + #[test] + fn extract_display_help_text_returns_empty_when_properties_help_text_is_missing() { + let structured = json!({ + "schema_version": 1, + "properties": { "type": "number", "display_order": 0 } + }) + .to_string(); + + assert_eq!(extract_display_help_text(&json!(structured)), ""); + } + + #[test] + fn extract_display_help_text_accepts_a_nested_object_value() { + let structured = json!({ + "schema_version": 1, + "properties": { "help_text": "Say hello", "display_order": 0 } + }); + + assert_eq!(extract_display_help_text(&structured), "Say hello"); + } + + #[test] + fn extract_display_help_text_returns_plain_string_verbatim() { + assert_eq!(extract_display_help_text(&json!("Say hello")), "Say hello"); + } + #[test] fn overridden_names_are_left_untouched() { assert_eq!( diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 38d964ea..b5480a17 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -426,9 +426,8 @@ impl Formatter for EdgeAppSettings { return Cell::new(""); } if field_name.eq("help_text") { - let help_text = field_value.as_str().unwrap_or_default(); return Cell::new( - &crate::api::edge_app::setting::extract_display_help_text(help_text), + &crate::api::edge_app::setting::extract_display_help_text(field_value), ); } debug!("field_name: {field_name}, field_value: {field_value:?}"); From e5190fd148e3744b1dca1f1861a08a831cac2958 Mon Sep 17 00:00:00 2001 From: nicomiguelino Date: Mon, 7 Sep 2026 07:55:21 -0700 Subject: [PATCH 4/5] Apply minor perf and readability nitpicks from Copilot review Skip JSON parsing for help_text strings that clearly aren't objects, and bind the extracted help text to a local before building the cell. --- src/api/edge_app/setting.rs | 8 +++++--- src/commands/mod.rs | 6 +++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/api/edge_app/setting.rs b/src/api/edge_app/setting.rs index 9abc1576..fde73f2b 100644 --- a/src/api/edge_app/setting.rs +++ b/src/api/edge_app/setting.rs @@ -225,9 +225,11 @@ fn is_structured_help_text(help_text: &str) -> bool { pub(crate) fn extract_display_help_text(help_text: &Value) -> String { let object = match help_text { Value::Object(_) => Some(help_text.clone()), - Value::String(raw) => serde_json::from_str::(raw) - .ok() - .filter(|value| value.is_object()), + Value::String(raw) if raw.trim_start().starts_with('{') => { + serde_json::from_str::(raw) + .ok() + .filter(|value| value.is_object()) + } _ => None, }; diff --git a/src/commands/mod.rs b/src/commands/mod.rs index b5480a17..4856598f 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -426,9 +426,9 @@ impl Formatter for EdgeAppSettings { return Cell::new(""); } if field_name.eq("help_text") { - return Cell::new( - &crate::api::edge_app::setting::extract_display_help_text(field_value), - ); + let help_text = + crate::api::edge_app::setting::extract_display_help_text(field_value); + return Cell::new(&help_text); } debug!("field_name: {field_name}, field_value: {field_value:?}"); Cell::new(field_value.as_str().unwrap_or_default()) From 208f80697b378825e86983aefbc2a2a7638b8ca4 Mon Sep 17 00:00:00 2001 From: nicomiguelino Date: Mon, 7 Sep 2026 08:56:41 -0700 Subject: [PATCH 5/5] Preserve raw JSON fallback for malformed help_text properties Only extract properties.help_text when the schema is well-formed, matching the existing malformed-schema warning in assign_setting_display_orders. --- src/api/edge_app/setting.rs | 41 ++++++++++++++++++++++++------------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/src/api/edge_app/setting.rs b/src/api/edge_app/setting.rs index fde73f2b..f60c73ca 100644 --- a/src/api/edge_app/setting.rs +++ b/src/api/edge_app/setting.rs @@ -222,25 +222,31 @@ fn is_structured_help_text(help_text: &str) -> bool { serde_json::from_str::(help_text).is_ok_and(|value| value.is_object()) } -pub(crate) fn extract_display_help_text(help_text: &Value) -> String { - let object = match help_text { - Value::Object(_) => Some(help_text.clone()), - Value::String(raw) if raw.trim_start().starts_with('{') => { - serde_json::from_str::(raw) - .ok() - .filter(|value| value.is_object()) - } - _ => None, - }; +fn properties_are_malformed(object: &serde_json::Map) -> bool { + matches!(object.get("properties"), Some(value) if !value.is_object()) +} - match object { - Some(object) => object +pub(crate) fn extract_display_help_text(help_text: &Value) -> String { + match help_text { + Value::Object(object) if !properties_are_malformed(object) => object .get("properties") .and_then(|properties| properties.get("help_text")) .and_then(|value| value.as_str()) .unwrap_or_default() .to_string(), - None => help_text.as_str().unwrap_or_default().to_string(), + Value::String(raw) if raw.trim_start().starts_with('{') => { + match serde_json::from_str::(raw) { + Ok(Value::Object(object)) if !properties_are_malformed(&object) => object + .get("properties") + .and_then(|properties| properties.get("help_text")) + .and_then(|value| value.as_str()) + .unwrap_or_default() + .to_string(), + _ => raw.clone(), + } + } + Value::String(raw) => raw.clone(), + _ => String::new(), } } @@ -248,7 +254,7 @@ fn has_malformed_properties(help_text: &str) -> bool { let Ok(Value::Object(object)) = serde_json::from_str::(help_text) else { return false; }; - matches!(object.get("properties"), Some(value) if !value.is_object()) + properties_are_malformed(&object) } pub fn help_text_with_display_order(name: &str, help_text: &str, display_order: usize) -> String { @@ -638,6 +644,13 @@ mod display_order_tests { assert_eq!(extract_display_help_text(&json!(structured)), ""); } + #[test] + fn extract_display_help_text_returns_raw_json_for_malformed_properties() { + let malformed = json!({ "schema_version": 1, "properties": "nope" }).to_string(); + + assert_eq!(extract_display_help_text(&json!(malformed)), malformed); + } + #[test] fn extract_display_help_text_accepts_a_nested_object_value() { let structured = json!({