Liberal Boolean Parsing#
OPNsense and pfSense config.xml both emit a shared "liberal" boolean vocabulary for toggle-style fields. The truthy set is 1 | on | yes | true | enable | enabled (case-insensitive, whitespace-trimmed); the falsy set is 0 | off | no | false | disable | disabled | "". Before PR #577, the codebase had three divergent parsers: pfsense.isPfSenseValueTrue accepted only 1|on|yes; internal/converter/formatters.IsTruthy covered a broader set; and int-typed schema fields crashed with strconv.ParseInt: parsing "on" when OPNsense 26.1 started emitting non-numeric values (issue #558, ).
Canonical Source of Truth (PR #577)#
PR #577 introduces pkg/schema/shared/ as the canonical boolean library, consolidating all truthy/falsy parsing. These files exist in PR #577 but are not yet on main.
| Symbol | File | Role |
|---|---|---|
IsValueTrue(s) / IsValueFalse(s) | pkg/schema/shared/bool.go | Full-vocabulary parser; both return false for unknown input, letting callers distinguish unknown from explicitly falsy |
FlexBool | pkg/schema/shared/flex_bool.go | Value-level liberal bool: always-emitted XML element, body carries the boolean signal. Marshals as "1"/"0". |
FlexInt | pkg/schema/shared/flex_int.go | Liberal int: truthy strings → 1, falsy strings → 0, clean numerics pass through, unknown strings → wrapped error |
Four Coexisting Types and Decision Rubric#
Current main has two types; after PR #577 merges, four types will coexist. The decision rubric lives in GOTCHAS §15:
| Type | Semantics | On-wire marshal | Use when |
|---|---|---|---|
BoolFlag | Presence + liberal body | <tag/> if true; element omitted if false | PHP uses isset($x['field']); absent = false |
FlexBool (PR #577) | Value-only, liberal | "1" / "0" | Element always emitted; content carries truth value |
FlexInt (PR #577) | Liberal int with error | numeric | Field is semantically int but may receive truthy/falsy strings |
Strict bool / int | Standard Go | true/false, integer | Wire format guarantees numeric/standard values |
Critical: BoolFlag is not a drop-in for value-based booleans. Its MarshalXML emits nothing when false — a field whose wire form must always be <tag>0</tag>/<tag>1</tag> cannot use BoolFlag .
BoolFlag: Current Implementation#
BoolFlag (pkg/schema/opnsense/common.go) is a type BoolFlag bool with pointer-receiver XML marshaling:
UnmarshalXML: Any element presence →true; element body is consumed but ignored. Onmaintoday, it does not delegate body content toIsValueTrue— that delegation is part of PR #577'sBoolFlagupgrade.MarshalXML: Emits<tag/>whentrue; emits nothing whenfalse.
After PR #577 merges, UnmarshalXML will additionally apply shared.IsValueTrue(body) for non-empty element bodies, fixing <tag>0</tag> → false (previously a silent bug where any element presence returned true).
PR #577 field migrations : 10 OPNsense System fields (e.g., UseVirtualTerminal, DisableVLANHWFilter, DisableChecksumOffloading, PfShareForward, LbUseSticky) and 3 pfSense System fields migrate from int to BoolFlag. On current main, those fields are still typed as int .
GOTCHAS §15: MarshalXML Addressability#
GOTCHAS §15.1 documents the most common trap:
BoolFlagimplementsMarshalXMLon a pointer receiver (*BoolFlag). When a struct containing aBoolFlagfield is marshaled by value,encoding/xmlcannot find the pointer-receiver method and falls back to defaultboolserialization — producing<enable>true</enable>instead of<enable/>.
Fix pattern: Add a private type alias (e.g., type interfaceAlias Interface) and a pointer-receiver MarshalXML on the parent struct that casts via e.EncodeElement((*alias)(ptr), start). Precedent: pkg/schema/pfsense/interfaces.go — interfaceAlias and (*Interface).MarshalXML. Also pass &value (not value) when encoding items inside map-based containers. This pattern applies equally to FlexBool/FlexInt after PR #577 merges.
Rule: Any pfSense struct forked from opnsense that adds a
BoolFlagfield must implement this alias pattern.
Legacy Parsers (Pre-PR #577)#
On current main, value-based truthy parsing is split:
isPfSenseValueTrue(pkg/parser/pfsense/constants.go): Private pfSense-only helper; accepts1|on|yes(3 values).formatters.IsTruthy(internal/converter/formatters/formatters.go): Formatter-layer helper; accepts a wider set includingenabled|active|inactive|-1; also falls back to float parsing. Used only byFormatBooleanCheckbox/FormatBooleanWithUnset.
PR #577 retires isPfSenseValueTrue in favor of shared.IsValueTrue and does not change formatters.IsTruthy (different concern).
Key Reference Files#
| File | What's there |
|---|---|
pkg/schema/opnsense/common.go | BoolFlag definition, XML marshal/unmarshal |
pkg/schema/pfsense/interfaces.go | interfaceAlias + MarshalXML addressability fix (precedent) |
pkg/schema/opnsense/system.go | Fields currently typed int that PR #577 migrates to BoolFlag |
pkg/parser/pfsense/constants.go | isPfSenseValueTrue (legacy, to be retired) |
internal/converter/formatters/formatters.go | IsTruthy (formatter layer, not schema layer) |
GOTCHAS.md §15 | Addressability decision rubric |
| PR #577 | pkg/schema/shared/ source, BoolFlag upgrade, field migrations, post-mortem doc |