Documents
Schema Field Miscategorisation
Schema Field Miscategorisation
Type
Topic
Status
Published
Created
Apr 18, 2026
Updated
Apr 18, 2026
Created by
Dosu Bot
Updated by
Dosu Bot

Schema Field Miscategorisation#

Schema Field Miscategorisation is a recurring bug class in opnDossier where OPNsense/pfSense XML config fields are declared as Go int or string in the schema structs, but the upstream PHP source treats them as booleans — either via isset() (presence-based) or == "1" / on / yes (value-based). The mismatch surfaces in two ways:

  • Parse crash: strconv.ParseInt: parsing "on": invalid syntax when OPNsense 26.1 emits a liberal truthy string into a field typed as Go int
  • Silent semantic drop: everything appears disabled because a self-closing <enable/> or an empty string both deserialise to "" / 0, which then compare as false

The canonical resolution is documented in issue #558 (parse crash trigger) and PR #577 (fix), with prior groundwork laid in PR #461 / issue #461 (silent-disable trigger).

Fix Pattern#

Always fix at the type level; never escape-hatch at the converter. The two correct target types are:

PHP patternGo typeWhen to use
isset($config['field']) — presence-basedBoolFlagSelf-closing <tag/> = true, absent = false; content ignored
$config['field'] == "1" — value-basedshared.FlexBoolAlways-emitted element whose body carries the boolean signal

BoolFlag is defined in pkg/schema/opnsense/common.go. Its UnmarshalXML currently sets true on any element presence and consumes-but-ignores content. PR #577 upgrades it to delegate non-empty bodies to shared.IsValueTrue(), so <tag>0</tag> correctly parses as false rather than true — a latent bug exposed by OPNsense 26.1 emitting on into previously-int fields .

shared.FlexBool and the canonical truthy-vocab helpers (IsValueTrue / IsValueFalse) live in pkg/schema/shared/ and recognise 1 | on | yes | true | enable | enabled (and their falsy counterparts) case-insensitively .

Anti-pattern to avoid: calling shared.IsValueTrue(field) inside the converter for a field that is still typed int or string in the schema struct. This only papers over the crash without fixing the root type, and the next consumer of the raw struct gets the wrong Go value anyway.

GOTCHAS §15.1: BoolFlag.MarshalXML is a pointer-receiver method. Any forked pfSense struct that changes a field to BoolFlag needs a private type-alias + pointer-receiver MarshalXML on the parent struct to prevent encoding/xml falling back to <enable>true</enable> instead of <enable/>. See the interfaceAlias pattern in pkg/schema/pfsense/interfaces.go .

Migrated Fields Catalog#

OPNsense System — 10 fields (int → BoolFlag), pending in PR #577#

All ten are currently typed int in pkg/schema/opnsense/system.go and will be retyped to BoolFlag when PR #577 merges:

FieldLine
DNSAllowOverride
UseVirtualTerminal
DisableVLANHWFilter
DisableChecksumOffloading
DisableSegmentationOffloading
DisableLargeReceiveOffloading
PfShareForward
LbUseSticky
RrdBackup
NetflowBackup

pfSense System — 3 fields (int → BoolFlag), pending in PR #577#

Declared in pkg/schema/pfsense/system.go:

FieldLine
DNSAllowOverride
DisableSegmentationOffloading
DisableLargeReceiveOffloading

pfSense Interface.Enable and DhcpdInterface.Enable — PR #461 / PR #473#

pfSense's <enable/> is presence-based; OPNsense's is value-based (<enable>1</enable>). PR #461 (tracked as PR #473) forks these into pkg/schema/pfsense/ with Enable opnsense.BoolFlag, using an intermediate decodeDocument type that converts to backward-compatible opnsense.Interfaces/opnsense.Dhcpd (Enable = "1" / "") after decoding .

Known Deferred Migrations#

The following fields in pkg/schema/pfsense/ still use shared.IsValueTrue at the converter site rather than the correct type in the schema struct — the acknowledged follow-up from PR #461's review :

FieldStructCurrent approach
BlockPrivpfSense Interfaceshared.IsValueTrue(field) in converter_network.go
BlockBogonspfSense Interfaceshared.IsValueTrue(field) in converter_network.go
FarGWpfSense Gatewayshared.IsValueTrue(field) in converter_network.go

These are not crash risks (they are typed string, not int), but they remain converter-level workarounds rather than type-level fixes. Each should eventually be retyped to BoolFlag or FlexBool and the converter call removed.

Decision Trigger: Auditing on strconv Errors#

Any strconv.ParseInt, strconv.ParseBool, or strconv.Atoi in the parser stack is a signal to audit the field's upstream PHP usage before patching.

Decision flow:

  1. Identify which Go field the decoder was processing when the error was thrown.
  2. Find the corresponding PHP source (OPNsense src/etc/inc/, pfSense etc/inc/).
  3. If PHP does == "1" or value-comparison → retype to shared.FlexBool (or keep string if other values are valid).
  4. If PHP does isset() or !empty() → retype to BoolFlag.
  5. Never convert at the converter or validator layer alone — the schema struct must change.

Issue #558 is the reference case: OPNsense 26.1 started emitting on for fields like <dnsallowoverride>, which were typed int, causing strconv.ParseInt: parsing "on": invalid syntax . The maintainer's immediate diagnosis — "I suspect the field … is being used as a boolean value, but I thought it was a number" — is exactly the pattern this workflow is designed to short-circuit .

Schema Field Miscategorisation | Dosu