From d3d00b0e48dca542ff627dc007b293c9e03170d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Dallidet?= Date: Tue, 28 Nov 2023 13:21:00 +0100 Subject: [PATCH 1/3] Fixes #23799: Make rudderc able to compile techniques using a per method policy mode override --- policies/rudder-commons/src/lib.rs | 17 +++ .../src/backends/unix/ncf/method_call.rs | 55 +++++++-- policies/rudderc/src/backends/windows.rs | 16 ++- policies/rudderc/src/ir/technique.rs | 8 +- policies/rudderc/src/technique.schema.json | 11 ++ .../rudderc/templates/technique.ps1.askama | 6 +- .../cases/general/policy_mode/metadata.xml | 56 +++++++++ .../cases/general/policy_mode/technique.cf | 53 ++++++++ .../cases/general/policy_mode/technique.ps1 | 115 ++++++++++++++++++ .../cases/general/policy_mode/technique.yml | 36 ++++++ 10 files changed, 354 insertions(+), 19 deletions(-) create mode 100644 policies/rudderc/tests/cases/general/policy_mode/metadata.xml create mode 100644 policies/rudderc/tests/cases/general/policy_mode/technique.cf create mode 100644 policies/rudderc/tests/cases/general/policy_mode/technique.ps1 create mode 100644 policies/rudderc/tests/cases/general/policy_mode/technique.yml diff --git a/policies/rudder-commons/src/lib.rs b/policies/rudder-commons/src/lib.rs index 7bc0b41d749..1a678b8b15c 100644 --- a/policies/rudder-commons/src/lib.rs +++ b/policies/rudder-commons/src/lib.rs @@ -269,6 +269,23 @@ pub enum PolicyMode { Audit, } +impl PolicyMode { + pub fn from_string <'de, D>(deserializer: D) -> Result, D::Error> + where + D: Deserializer<'de>, + { + use serde::de::Error; + String::deserialize(deserializer).and_then(|string| { + match string.as_ref() { + "enforce" => Ok(Some(PolicyMode::Enforce)), + "audit" => Ok(Some(PolicyMode::Audit)), + "default" => Ok(None), + _ => Err(Error::custom(format!("Could not parse policy mode '{}'", string))) + } + }) + } +} + impl fmt::Display for PolicyMode { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( diff --git a/policies/rudderc/src/backends/unix/ncf/method_call.rs b/policies/rudderc/src/backends/unix/ncf/method_call.rs index 1d55963886b..bf1188c427a 100644 --- a/policies/rudderc/src/backends/unix/ncf/method_call.rs +++ b/policies/rudderc/src/backends/unix/ncf/method_call.rs @@ -8,7 +8,7 @@ //! signature, type, and constraints). use anyhow::{bail, Result}; -use rudder_commons::{canonify, methods::method::Agent}; +use rudder_commons::{canonify, methods::method::Agent, PolicyMode}; use crate::{ backends::unix::cfengine::{ @@ -102,19 +102,43 @@ pub fn method_call( info.bundle_name ); + let push_policy_mode = m.policy_mode.map(|p| { + Promise::usebundle( + "push_dry_run_mode", + Some(&report_component), + Some(unique), + vec![match p { + PolicyMode::Enforce => "\"false\"".to_string(), + PolicyMode::Audit => "\"true\"".to_string(), + }], + ) + }); + let pop_policy_mode = if m.policy_mode.is_some() { + Some(Promise::usebundle( + "pop_dry_run_mode", + Some(&report_component), + Some(unique), + vec![], + )) + } else { + None + }; + let mut promises = match (&condition, is_supported) { (Condition::Expression(_), true) => vec![ - reporting_context, - method.if_condition(condition.clone()), - Promise::usebundle("_classes_noop", Some(&report_component), Some(unique), vec![na_condition.clone()]).unless_condition(&condition), - Promise::usebundle("log_rudder", Some(&report_component), Some(unique), vec![ - quoted(&format!("Skipping method '{}' with key parameter '{}' since condition '{}' is not reached", &method_name, &report_parameter, condition)), - quoted(&report_parameter), - na_condition.clone(), - na_condition, - "@{args}".to_string() - ]).unless_condition(&condition) - ], + Some(reporting_context), + push_policy_mode, + Some(method.if_condition(condition.clone())), + pop_policy_mode, + Some(Promise::usebundle("_classes_noop", Some(&report_component), Some(unique), vec![na_condition.clone()]).unless_condition(&condition)), + Some(Promise::usebundle("log_rudder", Some(&report_component), Some(unique), vec![ + quoted(&format!("Skipping method '{}' with key parameter '{}' since condition '{}' is not reached", &method_name, &report_parameter, condition)), + quoted(&report_parameter), + na_condition.clone(), + na_condition, + "@{args}".to_string() + ]).unless_condition(&condition)) + ].into_iter().flatten().collect(), (Condition::NotDefined, true) => vec![ reporting_context, Promise::usebundle("_classes_noop", Some(&report_component), Some(unique), vec![na_condition.clone()]), @@ -126,7 +150,12 @@ pub fn method_call( "@{args}".to_string() ]) ], - (Condition::Defined, true) => vec![reporting_context, method], + (Condition::Defined, true) => vec![ + Some(reporting_context), + push_policy_mode, + Some(method), + pop_policy_mode, + ].into_iter().flatten().collect(), (_, false) => vec![ reporting_context, Promise::usebundle( diff --git a/policies/rudderc/src/backends/windows.rs b/policies/rudderc/src/backends/windows.rs index 7681ff2fbe8..6046fc77d3f 100644 --- a/policies/rudderc/src/backends/windows.rs +++ b/policies/rudderc/src/backends/windows.rs @@ -7,7 +7,7 @@ use std::path::Path; use anyhow::{bail, Result}; use askama::Template; -use rudder_commons::{methods::method::Agent, Escaping}; +use rudder_commons::{methods::method::Agent, Escaping, PolicyMode}; use super::Backend; use crate::ir::{ @@ -62,7 +62,7 @@ pub mod filters { use std::fmt::Display; use anyhow::Error; - use rudder_commons::{regex_comp, Escaping, Target}; + use rudder_commons::{regex_comp, Escaping, PolicyMode, Target}; use crate::ir::value::Expression; @@ -135,6 +135,16 @@ pub mod filters { Escaping::Raw => value, }) } + + pub fn policy_mode_fmt(op: &Option) -> askama::Result { + match op { + None => Ok("$policyMode".to_string()), + Some(p) => match p { + PolicyMode::Audit => Ok("([Rudder.PolicyMode]::Audit)".to_string()), + PolicyMode::Enforce => Ok("([Rudder.PolicyMode]::Enforce)".to_string()), + }, + } + } } struct WindowsMethod { @@ -147,6 +157,7 @@ struct WindowsMethod { args: Vec<(String, String, Escaping)>, name: String, is_supported: bool, + policy_mode: Option, } fn method_call(m: Method, condition: Condition) -> Result { @@ -196,6 +207,7 @@ fn method_call(m: Method, condition: Condition) -> Result { args, name: filters::dsc_case(&m.info.as_ref().unwrap().bundle_name).unwrap(), is_supported, + policy_mode: m.policy_mode, }) } diff --git a/policies/rudderc/src/ir/technique.rs b/policies/rudderc/src/ir/technique.rs index 2a983593760..5ebe5002d37 100644 --- a/policies/rudderc/src/ir/technique.rs +++ b/policies/rudderc/src/ir/technique.rs @@ -9,7 +9,7 @@ use std::{ }; use anyhow::{bail, Error, Result}; -use rudder_commons::{methods::method::MethodInfo, RegexConstraint, Select}; +use rudder_commons::{methods::method::MethodInfo, PolicyMode, RegexConstraint, Select}; use serde::{de, Deserialize, Deserializer, Serialize}; use serde_yaml::Value; use uuid::Uuid; @@ -351,6 +351,9 @@ pub struct Block { pub id: Id, #[serde(default)] pub reporting: BlockReporting, + #[serde(deserialize_with = "PolicyMode::from_string")] + #[serde(default)] + pub policy_mode: Option, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] @@ -388,6 +391,9 @@ pub struct Method { pub reporting: LeafReporting, #[serde(skip)] pub info: Option<&'static MethodInfo>, + #[serde(deserialize_with = "PolicyMode::from_string")] + #[serde(default)] + pub policy_mode: Option, } #[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)] diff --git a/policies/rudderc/src/technique.schema.json b/policies/rudderc/src/technique.schema.json index a7f155db684..87328deef64 100644 --- a/policies/rudderc/src/technique.schema.json +++ b/policies/rudderc/src/technique.schema.json @@ -185,6 +185,14 @@ "tags": { "type": "object" }, + "policyMode": { + "type": "string", + "enum": [ + "audit", + "enforce", + "default" + ] + }, "reportingMode": { "oneOf": [ { @@ -234,6 +242,9 @@ "tags": { "$ref": "#/$defs/tags" }, + "policy_mode": { + "$ref": "#/$defs/policyMode" + }, "condition": { "type": ["string", "boolean"] }, diff --git a/policies/rudderc/templates/technique.ps1.askama b/policies/rudderc/templates/technique.ps1.askama index ebbc47edbd2..23ed7a0e656 100644 --- a/policies/rudderc/templates/technique.ps1.askama +++ b/policies/rudderc/templates/technique.ps1.askama @@ -32,7 +32,7 @@ function {{ id|dsc_case }} { ClassPrefix = ([Rudder.Condition]::canonify(("{{ m.class_prefix }}_" + $componentKey))) ComponentKey = $componentKey ComponentName = "{{ m.component_name|escape_double_quotes }}" - PolicyMode = $policyMode + PolicyMode = {{ m.policy_mode|policy_mode_fmt }} ReportId = $reportId DisableReporting = ${{ m.disable_reporting }} TechniqueName = $techniqueName @@ -47,7 +47,7 @@ function {{ id|dsc_case }} { {{- arg.0 }} = {{ arg|parameter_fmt }} {% endfor %} } - $call = {{ m.name|dsc_case }} @methodParams -PolicyMode $policyMode + $call = {{ m.name|dsc_case }} @methodParams -PolicyMode {{ m.policy_mode|policy_mode_fmt }} $methodContext = Compute-Method-Call @reportParams -MethodCall $call $localContext.merge($methodContext) } else { @@ -59,7 +59,7 @@ function {{ id|dsc_case }} { {{- arg.0 }} = {{ arg|parameter_fmt }} {% endfor %} } - $call = {{ m.name|dsc_case }} @methodParams -PolicyMode $policyMode + $call = {{ m.name|dsc_case }} @methodParams -PolicyMode {{ m.policy_mode|policy_mode_fmt }} $methodContext = Compute-Method-Call @reportParams -MethodCall $call $localContext.merge($methodContext) {% endmatch %} diff --git a/policies/rudderc/tests/cases/general/policy_mode/metadata.xml b/policies/rudderc/tests/cases/general/policy_mode/metadata.xml new file mode 100644 index 00000000000..357415a6723 --- /dev/null +++ b/policies/rudderc/tests/cases/general/policy_mode/metadata.xml @@ -0,0 +1,56 @@ + + test audit + true + false + separated + + + test_audit + + + + true + + + + + + Test-Audit + + + + true + + + + +
+ + + /tmp/1 + + +
+
+ + + /tmp/1 + + +
+
+ + + /tmp/1 + + +
+
+ + + /tmp/1 + + +
+
+
\ No newline at end of file diff --git a/policies/rudderc/tests/cases/general/policy_mode/technique.cf b/policies/rudderc/tests/cases/general/policy_mode/technique.cf new file mode 100644 index 00000000000..0c0c022ced7 --- /dev/null +++ b/policies/rudderc/tests/cases/general/policy_mode/technique.cf @@ -0,0 +1,53 @@ +# @name test audit +# @version 1.0 + +bundle agent test_audit { + + vars: + "args" slist => {}; + "report_param" string => join("_", args); + "full_class_prefix" string => canonify("test_audit_${report_param}"); + "class_prefix" string => string_head("${full_class_prefix}", "1000"); + + methods: + "46b8025a-0b06-485c-9127-50e4258ee7e6_${report_data.directive_id}" usebundle => call_test_audit_46b8025a_0b06_485c_9127_50e4258ee7e6("In audit mode", "/tmp/1", "46b8025a-0b06-485c-9127-50e4258ee7e6", @{args}, "${class_prefix}", "/tmp/1", "foobar", "true"); + + "1eedce7b-3441-4251-bdd6-706fda3ec7a8_${report_data.directive_id}" usebundle => call_test_audit_1eedce7b_3441_4251_bdd6_706fda3ec7a8("In omit mode", "/tmp/1", "1eedce7b-3441-4251-bdd6-706fda3ec7a8", @{args}, "${class_prefix}", "/tmp/1", "foobar", "true"); + + "dbd5ba50-8dfc-11ee-a57e-84a938c470d4_${report_data.directive_id}" usebundle => call_test_audit_dbd5ba50_8dfc_11ee_a57e_84a938c470d4("In enforce mode", "/tmp/1", "dbd5ba50-8dfc-11ee-a57e-84a938c470d4", @{args}, "${class_prefix}", "/tmp/1", "foobar", "true"); + + "1d809592-808e-4177-8351-8b7b7769af69_${report_data.directive_id}" usebundle => call_test_audit_1d809592_808e_4177_8351_8b7b7769af69("In default mode", "/tmp/1", "1d809592-808e-4177-8351-8b7b7769af69", @{args}, "${class_prefix}", "/tmp/1", "foobar", "true"); + +} +bundle agent call_test_audit_46b8025a_0b06_485c_9127_50e4258ee7e6(c_name, c_key, report_id, args, class_prefix, path, lines, enforce) { + + methods: + "46b8025a-0b06-485c-9127-50e4258ee7e6_${report_data.directive_id}" usebundle => _method_reporting_context_v4("${c_name}", "${c_key}", "${report_id}"); + "46b8025a-0b06-485c-9127-50e4258ee7e6_${report_data.directive_id}" usebundle => push_dry_run_mode("true"); + "46b8025a-0b06-485c-9127-50e4258ee7e6_${report_data.directive_id}" usebundle => file_content("${path}", "${lines}", "${enforce}"); + "46b8025a-0b06-485c-9127-50e4258ee7e6_${report_data.directive_id}" usebundle => pop_dry_run_mode(); + +} +bundle agent call_test_audit_1eedce7b_3441_4251_bdd6_706fda3ec7a8(c_name, c_key, report_id, args, class_prefix, path, lines, enforce) { + + methods: + "1eedce7b-3441-4251-bdd6-706fda3ec7a8_${report_data.directive_id}" usebundle => _method_reporting_context_v4("${c_name}", "${c_key}", "${report_id}"); + "1eedce7b-3441-4251-bdd6-706fda3ec7a8_${report_data.directive_id}" usebundle => file_content("${path}", "${lines}", "${enforce}"); + +} +bundle agent call_test_audit_dbd5ba50_8dfc_11ee_a57e_84a938c470d4(c_name, c_key, report_id, args, class_prefix, path, lines, enforce) { + + methods: + "dbd5ba50-8dfc-11ee-a57e-84a938c470d4_${report_data.directive_id}" usebundle => _method_reporting_context_v4("${c_name}", "${c_key}", "${report_id}"); + "dbd5ba50-8dfc-11ee-a57e-84a938c470d4_${report_data.directive_id}" usebundle => push_dry_run_mode("false"); + "dbd5ba50-8dfc-11ee-a57e-84a938c470d4_${report_data.directive_id}" usebundle => file_content("${path}", "${lines}", "${enforce}"); + "dbd5ba50-8dfc-11ee-a57e-84a938c470d4_${report_data.directive_id}" usebundle => pop_dry_run_mode(); + +} +bundle agent call_test_audit_1d809592_808e_4177_8351_8b7b7769af69(c_name, c_key, report_id, args, class_prefix, path, lines, enforce) { + + methods: + "1d809592-808e-4177-8351-8b7b7769af69_${report_data.directive_id}" usebundle => _method_reporting_context_v4("${c_name}", "${c_key}", "${report_id}"); + "1d809592-808e-4177-8351-8b7b7769af69_${report_data.directive_id}" usebundle => file_content("${path}", "${lines}", "${enforce}"); + +} diff --git a/policies/rudderc/tests/cases/general/policy_mode/technique.ps1 b/policies/rudderc/tests/cases/general/policy_mode/technique.ps1 new file mode 100644 index 00000000000..de0fe5d5435 --- /dev/null +++ b/policies/rudderc/tests/cases/general/policy_mode/technique.ps1 @@ -0,0 +1,115 @@ +function Test-Audit { + [CmdletBinding()] + param ( + [parameter(Mandatory = $true)] + [string]$reportId, + [parameter(Mandatory = $true)] + [string]$techniqueName, + + [Rudder.PolicyMode]$policyMode + ) + $techniqueParams = @{ + + } + BeginTechniqueCall -Name $techniqueName -Parameters $techniqueParams + $reportIdBase = $reportId.Substring(0, $reportId.Length - 1) + $localContext = New-Object -TypeName "Rudder.Context" -ArgumentList @($techniqueName) + $localContext.Merge($system_classes) + + + + $reportId=$reportIdBase + "46b8025a-0b06-485c-9127-50e4258ee7e6" + $componentKey = "/tmp/1" + $reportParams = @{ + ClassPrefix = ([Rudder.Condition]::canonify(("file_lines_present_" + $componentKey))) + ComponentKey = $componentKey + ComponentName = "In audit mode" + PolicyMode = ([Rudder.PolicyMode]::Audit) + ReportId = $reportId + DisableReporting = $false + TechniqueName = $techniqueName + } + + $methodParams = @{ + Enforce = "true" + Lines = "foobar" + Path = "/tmp/1" + + } + $call = File-Content @methodParams -PolicyMode ([Rudder.PolicyMode]::Audit) + $methodContext = Compute-Method-Call @reportParams -MethodCall $call + $localContext.merge($methodContext) + + + $reportId=$reportIdBase + "1eedce7b-3441-4251-bdd6-706fda3ec7a8" + $componentKey = "/tmp/1" + $reportParams = @{ + ClassPrefix = ([Rudder.Condition]::canonify(("file_lines_present_" + $componentKey))) + ComponentKey = $componentKey + ComponentName = "In omit mode" + PolicyMode = $policyMode + ReportId = $reportId + DisableReporting = $false + TechniqueName = $techniqueName + } + + $methodParams = @{ + Enforce = "true" + Lines = "foobar" + Path = "/tmp/1" + + } + $call = File-Content @methodParams -PolicyMode $policyMode + $methodContext = Compute-Method-Call @reportParams -MethodCall $call + $localContext.merge($methodContext) + + + $reportId=$reportIdBase + "dbd5ba50-8dfc-11ee-a57e-84a938c470d4" + $componentKey = "/tmp/1" + $reportParams = @{ + ClassPrefix = ([Rudder.Condition]::canonify(("file_lines_present_" + $componentKey))) + ComponentKey = $componentKey + ComponentName = "In enforce mode" + PolicyMode = ([Rudder.PolicyMode]::Enforce) + ReportId = $reportId + DisableReporting = $false + TechniqueName = $techniqueName + } + + $methodParams = @{ + Enforce = "true" + Lines = "foobar" + Path = "/tmp/1" + + } + $call = File-Content @methodParams -PolicyMode ([Rudder.PolicyMode]::Enforce) + $methodContext = Compute-Method-Call @reportParams -MethodCall $call + $localContext.merge($methodContext) + + + $reportId=$reportIdBase + "1d809592-808e-4177-8351-8b7b7769af69" + $componentKey = "/tmp/1" + $reportParams = @{ + ClassPrefix = ([Rudder.Condition]::canonify(("file_lines_present_" + $componentKey))) + ComponentKey = $componentKey + ComponentName = "In default mode" + PolicyMode = $policyMode + ReportId = $reportId + DisableReporting = $false + TechniqueName = $techniqueName + } + + $methodParams = @{ + Enforce = "true" + Lines = "foobar" + Path = "/tmp/1" + + } + $call = File-Content @methodParams -PolicyMode $policyMode + $methodContext = Compute-Method-Call @reportParams -MethodCall $call + $localContext.merge($methodContext) + + + + EndTechniqueCall -Name $techniqueName +} \ No newline at end of file diff --git a/policies/rudderc/tests/cases/general/policy_mode/technique.yml b/policies/rudderc/tests/cases/general/policy_mode/technique.yml new file mode 100644 index 00000000000..97501f0bf30 --- /dev/null +++ b/policies/rudderc/tests/cases/general/policy_mode/technique.yml @@ -0,0 +1,36 @@ +id: test_audit +name: test audit +version: '1.0' +category: ncf_techniques +items: + - id: 46b8025a-0b06-485c-9127-50e4258ee7e6 + name: 'In audit mode' + method: file_content + params: + path: /tmp/1 + lines: "foobar" + enforce: "true" + policy_mode: audit + - id: 1eedce7b-3441-4251-bdd6-706fda3ec7a8 + name: 'In omit mode' + method: file_content + params: + path: /tmp/1 + lines: "foobar" + enforce: "true" + - id: dbd5ba50-8dfc-11ee-a57e-84a938c470d4 + name: 'In enforce mode' + method: file_content + params: + path: /tmp/1 + lines: "foobar" + enforce: "true" + policy_mode: enforce + - id: 1d809592-808e-4177-8351-8b7b7769af69 + name: 'In default mode' + method: file_content + params: + path: /tmp/1 + lines: "foobar" + enforce: "true" + policy_mode: default From 43a6c11566799c3fed57a5ddf777987f4531301e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Dallidet?= Date: Wed, 29 Nov 2023 18:14:08 +0100 Subject: [PATCH 2/3] fixup! Fixes #23799: Make rudderc able to compile techniques using a per method policy mode override Fixes #23799: Make rudderc able to compile techniques using a per method policy mode override --- policies/_typos.toml | 6 ++++++ policies/rudderc/src/backends/unix/ncf/method_call.rs | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/policies/_typos.toml b/policies/_typos.toml index 7d8bc541cab..c169f12eece 100644 --- a/policies/_typos.toml +++ b/policies/_typos.toml @@ -1,5 +1,11 @@ +[default] +extend-ignore-re = [ + "[0-9a-fA-F]{8}[-_][0-9a-fA-F]{4}[-_][0-9a-fA-F]{4}[-_][0-9a-fA-F]{4}[-_][0-9a-fA-F]{12}" +] + [default.extend-words] # from fusion SOFTWARES = "SOFTWARES" # crate name flate = "flate" + diff --git a/policies/rudderc/src/backends/unix/ncf/method_call.rs b/policies/rudderc/src/backends/unix/ncf/method_call.rs index bf1188c427a..dd8c263bd2b 100644 --- a/policies/rudderc/src/backends/unix/ncf/method_call.rs +++ b/policies/rudderc/src/backends/unix/ncf/method_call.rs @@ -108,8 +108,8 @@ pub fn method_call( Some(&report_component), Some(unique), vec![match p { - PolicyMode::Enforce => "\"false\"".to_string(), - PolicyMode::Audit => "\"true\"".to_string(), + PolicyMode::Enforce => quoted("false").to_string(), + PolicyMode::Audit => quoted("true").to_string(), }], ) }); From a680399b070878e16af379deaa3ed8150762124f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Dallidet?= Date: Thu, 30 Nov 2023 08:12:28 +0100 Subject: [PATCH 3/3] fixup! fixup! Fixes #23799: Make rudderc able to compile techniques using a per method policy mode override Fixes #23799: Make rudderc able to compile techniques using a per method policy mode override --- policies/rudder-commons/src/lib.rs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/policies/rudder-commons/src/lib.rs b/policies/rudder-commons/src/lib.rs index 1a678b8b15c..57944724c7c 100644 --- a/policies/rudder-commons/src/lib.rs +++ b/policies/rudder-commons/src/lib.rs @@ -270,18 +270,19 @@ pub enum PolicyMode { } impl PolicyMode { - pub fn from_string <'de, D>(deserializer: D) -> Result, D::Error> + pub fn from_string<'de, D>(deserializer: D) -> Result, D::Error> where D: Deserializer<'de>, { use serde::de::Error; - String::deserialize(deserializer).and_then(|string| { - match string.as_ref() { - "enforce" => Ok(Some(PolicyMode::Enforce)), - "audit" => Ok(Some(PolicyMode::Audit)), - "default" => Ok(None), - _ => Err(Error::custom(format!("Could not parse policy mode '{}'", string))) - } + String::deserialize(deserializer).and_then(|string| match string.as_ref() { + "enforce" => Ok(Some(PolicyMode::Enforce)), + "audit" => Ok(Some(PolicyMode::Audit)), + "default" => Ok(None), + _ => Err(Error::custom(format!( + "Could not parse policy mode '{}'", + string + ))), }) } }