Skip to content

Support serde_as on newtype variants of adjacently tagged enums - #1006

Open
hxperl wants to merge 1 commit into
jonasbb:masterfrom
hxperl:serde-as-newtype-variant
Open

hxperl wants to merge 1 commit into
jonasbb:masterfrom
hxperl:serde-as-newtype-variant

Conversation

@hxperl

@hxperl hxperl commented Sep 11, 2026

Copy link
Copy Markdown

For a newtype variant of an adjacently tagged enum, serde's derive only accepts
with/serialize_with/deserialize_with on the variant. Putting the same annotation on the
variant's single field makes the derive require Deserialize for the inner type — which is the
very thing the annotation was supposed to substitute. Since serde_as rejected the attribute on a
variant outright, there was no way to spell the substitution for that shape at all.

Reproduction against ea5dfdc (Inner only implements Display/FromStr, never Deserialize):

#[serde_as]
#[derive(Serialize, Deserialize)]
#[serde(tag = "type", content = "content")]
pub enum Adjacent {
    #[serde_as(as = "DisplayFromStr")]
    Bar(Inner),
}
error: serde_as attribute is not allowed on enum variants
 --> src/lib.rs:8:5
  |
8 |     #[serde_as(as = "DisplayFromStr")]
  |     ^

Moving it to the documented place, onto the field, does not compile either:

#[serde_as]
#[derive(Serialize, Deserialize)]
#[serde(tag = "type", content = "content")]
pub enum OnField {
    Bar(#[serde_as(as = "DisplayFromStr")] Inner),
}
error[E0277]: the trait bound `Inner: serde::Deserialize<'de>` is not satisfied
  --> src/lib.rs:16:21
   |
16 | #[derive(Serialize, Deserialize)]
   |                     ^^^^^^^^^^^ unsatisfied trait bound
note: required by a bound in `_::_serde::__private229::de::missing_field`

The only thing that works today is writing serde's own annotation by hand:
#[serde(with = "serde_with::As::<DisplayFromStr>")] on the variant.

The change

serde_as on a newtype variant is now accepted. The attribute is processed as if it had been
written on the variant's single field — so every existing check still runs, including the conflict
check against serde's own with/serialize_with/deserialize_with — and the generated serde
attribute is then emitted on the variant, which is where serde wants it. That is the code site you
pointed at in the issue.

Every other variant shape keeps rejecting the attribute, now with a message that says to put it on
the field. Writing serde_as on both a variant and its field is rejected as ambiguous. #[apply]
opts out of the new variant handling, so its behaviour is unchanged.

This also covers the original shape from #499, which is why that compile-fail case moves over to
tests/json.rs as a success case; the remaining error paths get a new compile-fail test.

Judgement calls, both worth disagreeing with

Newtype variants only. serde accepts with on struct and tuple variants too, but for those the
attribute already works on the field, and you said in the issue that you left serde_as off
variants on purpose. Narrowing to the shape that is actually broken keeps this a bug fix rather
than the feature you deliberately omitted. If you would rather have it on all variant shapes for
symmetry with serde(with = ...), that is a small follow-up.

schemars annotations are not emitted for a variant. I could not find a variant-level
schemars(with = ...) and did not want to emit something I had no way to test, so a newtype
variant annotated with serde_as gets a schema for the original inner type, not the substituted
one. That is a real gap, not an oversight — if schemars does support it, say so and I will add it.
serde(default) is suppressed for the same reason: serde has no variant-level default.

Verification

On macOS arm64, stable cargo 1.95:

  • cargo test --no-fail-fast, cargo test --all-features --no-fail-fast, and
    cargo test --no-default-features --no-fail-fast all pass.
  • cargo fmt --all -- --check clean.
  • All three clippy invocations from CI (--no-default-features, default, --all-features, each
    with --all-targets) are clean.

CI will need to confirm Linux, beta/nightly, MSRV, and the thumbv7em no_std checks — I have not
run those.

Fixes #975

serde's derive only accepts `with`/`serialize_with`/`deserialize_with` on
the variant itself for newtype variants; the same annotation on the
variant's single field makes the derive require `Deserialize` for the
inner type, which is exactly what the annotation was supposed to replace.
Since `serde_as` refused the attribute on a variant, there was no way to
spell the substitution for that shape at all.

Accept `serde_as` on a newtype variant, process it as if it were written
on the variant's single field, and emit the generated `serde` attribute on
the variant. All other variant shapes keep rejecting it, with a message
pointing at the field, since placing it on the field works there.

`default` and `schemars(with = ...)` are not emitted for a variant: serde
and schemars have no variant-level counterpart for either.

Fixes jonasbb#975

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Aw3Y3FfAVjRyaAhpCb1BVY
@codecov

codecov Bot commented Sep 11, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 75.30864% with 20 lines in your changes missing coverage. Please review.
✅ Project coverage is 68.89%. Comparing base (ea5dfdc) to head (b5487f2).

Files with missing lines Patch % Lines
serde_with_macros/src/lib.rs 74.35% 20 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #1006      +/-   ##
==========================================
+ Coverage   68.83%   68.89%   +0.06%     
==========================================
  Files          38       38              
  Lines        9705     9762      +57     
==========================================
+ Hits         6680     6726      +46     
- Misses       3025     3036      +11     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Inconsistency in adjacently tagged enums

1 participant