Status: confirmed against main @ 21bc21d9 · Found incidentally while investigating something else.
lib/Dancer2/Serializer/Mutable.pm:14-27 builds its dispatch table from functions in Dancer2::Core::DSL:
my $serializer = {
'YAML' => {
to => sub { Dancer2::Core::DSL::to_yaml(@_) },
from => sub { Dancer2::Core::DSL::from_yaml(@_) },
},
'Dumper' => { ... Dancer2::Core::DSL::to_dumper / from_dumper ... },
'JSON' => { ... Dancer2::Core::DSL::to_json / from_json ... },
};
...but the module never requires Dancer2::Core::DSL. It works in a real application only because the DSL is always loaded by the time a request is served.
Reproduction
Using Dancer2::Serializer::Mutable standalone — without loading the DSL — fails at deserialization time, and the error is swallowed by Role::Serializer's around deserialize, surfacing only through log_cb:
LOG[core]: Failed to deserialize content: Undefined subroutine
&Dancer2::Core::DSL::from_yaml called at lib/Dancer2/Serializer/Mutable.pm line 17.
...and deserialize quietly returns undef.
Impact
Mutable cannot be unit-tested in isolation, which is presumably why this has gone unnoticed.
- It is a latent load-order dependency: any future refactor that stops loading the DSL eagerly turns this into a runtime failure that returns
undef rather than raising.
- The silent-
undef failure mode makes it hard to diagnose.
Suggested fix
Add an explicit use Dancer2::Core::DSL; (or require_module at the point of use — Module::Runtime is already imported at line 7, and the module already uses require_module for serializer classes in the mapping builder).
Also worth considering whether the dispatch table should call the serializer classes directly rather than routing through DSL keywords, which is a slightly odd layering — the mapping builder at lines 40-47 already constructs serializer objects directly for the non-default case.
Suggested test
A Dancer2::Serializer::Mutable round-trip in a test that does not load Dancer2 or the DSL.
Status: confirmed against
main@21bc21d9· Found incidentally while investigating something else.lib/Dancer2/Serializer/Mutable.pm:14-27builds its dispatch table from functions inDancer2::Core::DSL:...but the module never
requiresDancer2::Core::DSL. It works in a real application only because the DSL is always loaded by the time a request is served.Reproduction
Using
Dancer2::Serializer::Mutablestandalone — without loading the DSL — fails at deserialization time, and the error is swallowed byRole::Serializer'saround deserialize, surfacing only throughlog_cb:...and
deserializequietly returnsundef.Impact
Mutablecannot be unit-tested in isolation, which is presumably why this has gone unnoticed.undefrather than raising.undeffailure mode makes it hard to diagnose.Suggested fix
Add an explicit
use Dancer2::Core::DSL;(orrequire_moduleat the point of use —Module::Runtimeis already imported at line 7, and the module already usesrequire_modulefor serializer classes in themappingbuilder).Also worth considering whether the dispatch table should call the serializer classes directly rather than routing through DSL keywords, which is a slightly odd layering — the
mappingbuilder at lines 40-47 already constructs serializer objects directly for the non-default case.Suggested test
A
Dancer2::Serializer::Mutableround-trip in a test that does not loadDancer2or the DSL.