Fixing pkg/ importing internal/ packages
Problem¶
After moving packages from internal/ to pkg/ to create a public API (issue #301), four production files in pkg/ still imported internal/ packages. Go enforces the internal/ access boundary at the module level -- any external consumer running go get would get a build error:
Affected files:
pkg/parser/factory.go-- importedinternal/cfgparserforDefaultMaxInputSizepkg/parser/opnsense/parser.go-- importedinternal/cfgparserforNewXMLParser()pkg/schema/opnsense/common.go-- importedinternal/constantsforNetworkAnypkg/schema/opnsense/security.go-- importedinternal/constantsforNetworkAny
Root Cause¶
The move from internal/ to pkg/ was mechanical (import path updates) but did not address structural dependencies. cfgparser depends on internal/validator which depends on internal/constants, so moving the whole chain would cascade across the codebase.
Solution¶
Two separate fixes for the two dependency chains:
1. Trivial constant extraction (constants.NetworkAny)¶
Defined const NetworkAny = "any" locally in pkg/schema/opnsense/constants.go and removed the internal/constants import. The internal package keeps its own copy -- both are independent definitions of the same string literal.
2. Interface injection for XML parser (cfgparser.XMLParser)¶
Instead of moving cfgparser to pkg/ (which would cascade into validator and constants), defined an XMLDecoder interface in pkg/parser/:
// pkg/parser/factory.go
type XMLDecoder interface {
Parse(ctx context.Context, r io.Reader) (*schema.OpnSenseDocument, error)
ParseAndValidate(ctx context.Context, r io.Reader) (*schema.OpnSenseDocument, error)
}
func NewFactory(decoder XMLDecoder) *Factory {
return &Factory{xmlDecoder: decoder}
}
The Parser in pkg/parser/opnsense/ uses a local unexported interface (Go structural typing satisfies it):
// pkg/parser/opnsense/parser.go
type xmlDecoder interface {
Parse(ctx context.Context, r io.Reader) (*schema.OpnSenseDocument, error)
ParseAndValidate(ctx context.Context, r io.Reader) (*schema.OpnSenseDocument, error)
}
func NewParser(decoder xmlDecoder) *Parser {
return &Parser{decoder: decoder}
}
Application code wires the concrete implementation:
3. Unexport Converter (bonus API surface reduction)¶
Renamed Converter to converter (unexported) since Parser is the intended entry point. Added ConvertDocument() as a convenience function for consumers who have a pre-parsed OpnSenseDocument.
Prevention¶
- Before exposing
internal/packages aspkg/, run:grep -rn 'internal/' --include='*.go' pkg/ | grep -v _test.goto catch boundary violations. - Consider adding a CI check or linter rule that flags
internal/imports frompkg/production code. - When a
pkg/package needs functionality frominternal/, prefer interface injection over moving entire dependency chains. - Test files in
pkg/can importinternal/(Go allows this) -- only production code is restricted for external consumers.