Skip to content

StringEnumDeserializer crashes when scalar matches an implicit YAML type (Decimal/Integer) — asyncapi: 3.0.0, bindings.mqtt.qos: 2 #76

@thomas-stegemann

Description

@thomas-stegemann

I tried this:

What I'm seeing

Reading a minimal AsyncAPI 3 document through IAsyncApiDocumentReader.ReadAsync throws when:

  • the asyncapi: or info.version: value is written unquoted (asyncapi: 3.0.0)
  • any binding block contains a numeric scalar that maps to a typed property (bindings.mqtt.qos: 2)

In all cases the inner exception is System.FormatException from Decimal.Parse, called from
Neuroglia.Serialization.Yaml.StringEnumDeserializer after YamlDotNet's
ScalarNodeDeserializer was asked for an expectedType of Decimal.

Quoting the value (asyncapi: '3.0.0', qos: '2') makes the document parse fine — so the
data is well-formed AsyncAPI, the deserialiser just trips on YAML's implicit-type resolver.

Minimal repro

asyncapi: 3.0.0
info:
  title: Repro
  version: 1.0.0
channels:
  hello:
    address: 'hello'
using Neuroglia.AsyncApi.IO;
using Microsoft.Extensions.DependencyInjection;

var sp = new ServiceCollection().AddAsyncApiIO().BuildServiceProvider();
var reader = sp.GetRequiredService<IAsyncApiDocumentReader>();
using var stream = File.OpenRead("repro.yaml");
var doc = await reader.ReadAsync(stream, default);  // throws

This happened:

Stack trace

System.Number.ThrowFormatException
System.Decimal.Parse(String, IFormatProvider)
YamlDotNet ScalarNodeDeserializer.Deserialize
Neuroglia.Serialization.Yaml.StringEnumDeserializer.Deserialize
YamlDotNet NodeValueDeserializer.DeserializeValue
... (down to AsyncApiDocumentReader.ReadAsync line 52)

What I think is happening

StringEnumDeserializer looks like it's being installed for every scalar in the
document (not just ones whose expectedType is actually an enum), and inside it
delegates back to ScalarNodeDeserializer with the property's declared CLR type.
When the property is string or int but YAML resolved the scalar to a different
implicit type first, the conversion lands on Decimal.Parse and blows up.

I expected this:

Per the AsyncAPI 3 spec the version strings are unconstrained, and standard tooling
(Spectral, the official VS Code extension, the AsyncAPI playground) all accept the
unquoted form. The reader should treat them as strings without involving the
StringEnumDeserializer at all.

Is there a workaround?

Documentation note that authors should quote scalars that look numeric:

asyncapi: '3.0.0'
info:
  version: '1.2.3'

Happy to put together a PR if useful — needs guidance on whether the fix should be
in StringEnumDeserializer (skip when expectedType isn't an enum) or earlier in
the resolver chain.

Anything else?

  • Neuroglia.AsyncApi.Core 3.0.6
  • Neuroglia.AsyncApi.IO 3.0.6
  • .NET 10 (net8.0 target asset selected)
  • YamlDotNet (whatever Neuroglia.Serialization.YamlDotNet 4.20.0 pulls in)
  • Windows 11

Discovered while implementing AsyncAPI using .NET SDK for Bowire.

Platform(s)

Windows

Community Notes

  • Please vote by adding a 👍 reaction to the issue to help us prioritize.
  • If you are interested to work on this issue, please leave a comment.name: Bug Report 🐞

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions