Documents
Liberal Boolean Parsing
Liberal Boolean Parsing
Type
Topic
Status
Published
Created
Apr 18, 2026
Updated
Apr 18, 2026
Created by
Dosu Bot
Updated by
Dosu Bot

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.

SymbolFileRole
IsValueTrue(s) / IsValueFalse(s)pkg/schema/shared/bool.goFull-vocabulary parser; both return false for unknown input, letting callers distinguish unknown from explicitly falsy
FlexBoolpkg/schema/shared/flex_bool.goValue-level liberal bool: always-emitted XML element, body carries the boolean signal. Marshals as "1"/"0".
FlexIntpkg/schema/shared/flex_int.goLiberal 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:

TypeSemanticsOn-wire marshalUse when
BoolFlagPresence + liberal body<tag/> if true; element omitted if falsePHP 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 errornumericField is semantically int but may receive truthy/falsy strings
Strict bool / intStandard Gotrue/false, integerWire 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. On main today, it does not delegate body content to IsValueTrue — that delegation is part of PR #577's BoolFlag upgrade.
  • MarshalXML : Emits <tag/> when true; emits nothing when false.

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:

BoolFlag implements MarshalXML on a pointer receiver (*BoolFlag). When a struct containing a BoolFlag field is marshaled by value, encoding/xml cannot find the pointer-receiver method and falls back to default bool serialization — 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.gointerfaceAlias 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 BoolFlag field 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; accepts 1|on|yes (3 values).
  • formatters.IsTruthy (internal/converter/formatters/formatters.go): Formatter-layer helper; accepts a wider set including enabled|active|inactive|-1; also falls back to float parsing. Used only by FormatBooleanCheckbox / FormatBooleanWithUnset.

PR #577 retires isPfSenseValueTrue in favor of shared.IsValueTrue and does not change formatters.IsTruthy (different concern).


Key Reference Files#

FileWhat's there
pkg/schema/opnsense/common.goBoolFlag definition, XML marshal/unmarshal
pkg/schema/pfsense/interfaces.gointerfaceAlias + MarshalXML addressability fix (precedent)
pkg/schema/opnsense/system.goFields currently typed int that PR #577 migrates to BoolFlag
pkg/parser/pfsense/constants.goisPfSenseValueTrue (legacy, to be retired)
internal/converter/formatters/formatters.goIsTruthy (formatter layer, not schema layer)
GOTCHAS.md §15Addressability decision rubric
PR #577pkg/schema/shared/ source, BoolFlag upgrade, field migrations, post-mortem doc
Liberal Boolean Parsing | Dosu