Skip to content

Commit b1a6745

Browse files
ryo33claude
andcommitted
Add VariantLiteralParser for proper UnknownVariant errors
Replace LiteralParser with VariantLiteralParser for unit enum variant parsing. VariantLiteralParser returns UnknownVariant error on mismatch instead of LiteralMismatch, providing better error messages for string-based enums. Also migrate BindingStyle from manual ParseDocument impl to derive macro. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent f3639d2 commit b1a6745

File tree

7 files changed

+41
-40
lines changed

7 files changed

+41
-40
lines changed

crates/eure-document/src/parse.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1190,6 +1190,29 @@ where
11901190
}
11911191
}
11921192

1193+
/// A parser that matches a specific string literal as an enum variant name.
1194+
///
1195+
/// Similar to [`LiteralParser`], but returns [`ParseErrorKind::UnknownVariant`]
1196+
/// on mismatch instead of [`ParseErrorKind::LiteralMismatch`]. This provides
1197+
/// better error messages when parsing unit enum variants as string literals.
1198+
pub struct VariantLiteralParser(pub &'static str);
1199+
1200+
impl<'doc> DocumentParser<'doc> for VariantLiteralParser {
1201+
type Output = &'static str;
1202+
type Error = ParseError;
1203+
fn parse(&mut self, ctx: &ParseContext<'doc>) -> Result<Self::Output, Self::Error> {
1204+
let value: &str = ctx.parse()?;
1205+
if value == self.0 {
1206+
Ok(self.0)
1207+
} else {
1208+
Err(ParseError {
1209+
node_id: ctx.node_id(),
1210+
kind: ParseErrorKind::UnknownVariant(value.to_string()),
1211+
})
1212+
}
1213+
}
1214+
}
1215+
11931216
pub struct MapParser<T, F> {
11941217
parser: T,
11951218
mapper: F,

crates/eure-macros/src/context.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,9 +103,9 @@ impl MacroContext {
103103
}
104104

105105
#[allow(non_snake_case)]
106-
pub fn LiteralParser(&self, value: TokenStream, mapper: TokenStream) -> TokenStream {
106+
pub fn VariantLiteralParser(&self, value: TokenStream, mapper: TokenStream) -> TokenStream {
107107
let document_crate = &self.config.document_crate;
108-
quote!(#document_crate::parse::DocumentParserExt::map(#document_crate::parse::LiteralParser(#value), #mapper))
108+
quote!(#document_crate::parse::DocumentParserExt::map(#document_crate::parse::VariantLiteralParser(#value), #mapper))
109109
}
110110

111111
/// Applies container-level `rename_all` to a name.

crates/eure-macros/src/parse_document/parse_union.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,12 +57,12 @@ fn generate_unit_variant(
5757
variant_name: &str,
5858
variant_ident: &syn::Ident,
5959
) -> TokenStream {
60-
let literal_parser = context.LiteralParser(
60+
let variant_parser = context.VariantLiteralParser(
6161
quote!(#variant_name),
6262
quote!(|_| #enum_ident::#variant_ident),
6363
);
6464
quote! {
65-
.variant(#variant_name, #literal_parser)
65+
.variant(#variant_name, #variant_parser)
6666
}
6767
}
6868

crates/eure-macros/src/parse_document/parse_union/tests.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ fn test_unit_variant() {
2121

2222
fn parse(ctx: &::eure::document::parse::ParseContext<'doc>) -> Result<Self, Self::Error> {
2323
ctx.parse_union(::eure::document::data_model::VariantRepr::default())?
24-
.variant("Unit", ::eure::document::parse::DocumentParserExt::map(::eure::document::parse::LiteralParser("Unit"), |_| TestEnum::Unit))
24+
.variant("Unit", ::eure::document::parse::DocumentParserExt::map(::eure::document::parse::VariantLiteralParser("Unit"), |_| TestEnum::Unit))
2525
.parse()
2626
}
2727
}
@@ -128,7 +128,7 @@ fn test_mixed_variants() {
128128

129129
fn parse(ctx: &::eure::document::parse::ParseContext<'doc>) -> Result<Self, Self::Error> {
130130
ctx.parse_union(::eure::document::data_model::VariantRepr::default())?
131-
.variant("Unit", ::eure::document::parse::DocumentParserExt::map(::eure::document::parse::LiteralParser("Unit"), |_| TestEnum::Unit))
131+
.variant("Unit", ::eure::document::parse::DocumentParserExt::map(::eure::document::parse::VariantLiteralParser("Unit"), |_| TestEnum::Unit))
132132
.parse_variant::<(i32, bool,)>("Tuple", |(field_0, field_1,)| Ok(TestEnum::Tuple(field_0, field_1)))
133133
.variant("Struct", |ctx: &::eure::document::parse::ParseContext<'_>| {
134134
let mut rec = ctx.parse_record()?;
@@ -167,7 +167,7 @@ fn test_mixed_variants_with_custom_crate() {
167167

168168
fn parse(ctx: &::eure_document::parse::ParseContext<'doc>) -> Result<Self, Self::Error> {
169169
ctx.parse_union(::eure_document::data_model::VariantRepr::default())?
170-
.variant("Unit", ::eure_document::parse::DocumentParserExt::map(::eure_document::parse::LiteralParser("Unit"), |_| TestEnum::Unit))
170+
.variant("Unit", ::eure_document::parse::DocumentParserExt::map(::eure_document::parse::VariantLiteralParser("Unit"), |_| TestEnum::Unit))
171171
.parse_variant::<(i32, bool,)>("Tuple", |(field_0, field_1,)| Ok(TestEnum::Tuple(field_0, field_1)))
172172
.variant("Struct", |ctx: &::eure_document::parse::ParseContext<'_>| {
173173
let mut rec = ctx.parse_record()?;
@@ -204,8 +204,8 @@ fn test_unit_variant_with_rename_all_snake_case() {
204204

205205
fn parse(ctx: &::eure::document::parse::ParseContext<'doc>) -> Result<Self, Self::Error> {
206206
ctx.parse_union(::eure::document::data_model::VariantRepr::default())?
207-
.variant("user_created", ::eure::document::parse::DocumentParserExt::map(::eure::document::parse::LiteralParser("user_created"), |_| Event::UserCreated))
208-
.variant("order_placed", ::eure::document::parse::DocumentParserExt::map(::eure::document::parse::LiteralParser("order_placed"), |_| Event::OrderPlaced))
207+
.variant("user_created", ::eure::document::parse::DocumentParserExt::map(::eure::document::parse::VariantLiteralParser("user_created"), |_| Event::UserCreated))
208+
.variant("order_placed", ::eure::document::parse::DocumentParserExt::map(::eure::document::parse::VariantLiteralParser("order_placed"), |_| Event::OrderPlaced))
209209
.parse()
210210
}
211211
}

crates/eure-schema/Cargo.toml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ chrono = { version = "0.4", optional = true }
1616
convert_case = "0.9"
1717
eure-document = { workspace = true }
1818
# Optional derive macro support
19-
eure-macros = { workspace = true, optional = true }
19+
eure-macros = { workspace = true }
2020
eure-parol = { workspace = true }
2121
eure-tree = { workspace = true }
2222
indexmap = { workspace = true, features = ["serde"] }
@@ -33,7 +33,6 @@ uuid = { version = "1.0", optional = true }
3333
[features]
3434
default = []
3535
chrono = ["dep:chrono"]
36-
derive = ["dep:eure-macros"]
3736
semver = ["dep:semver"]
3837
url = ["dep:url"]
3938
uuid = ["dep:uuid"]

crates/eure-schema/src/lib.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ pub mod validate;
3737
use eure_document::data_model::VariantRepr;
3838
use eure_document::document::EureDocument;
3939
use eure_document::identifier::Identifier;
40+
use eure_macros::ParseDocument;
4041
use num_bigint::BigInt;
4142
use regex::Regex;
4243
use std::collections::{BTreeMap, HashMap, HashSet};
@@ -430,7 +431,8 @@ pub struct UnionSchema {
430431
/// $variant: union
431432
/// variants { auto, passthrough, section, nested, binding, section-binding, section-root-binding }
432433
/// ```
433-
#[derive(Debug, Clone, PartialEq, Eq, Default)]
434+
#[derive(Debug, Clone, PartialEq, Eq, Default, ParseDocument)]
435+
#[eure(crate = ::eure_document, rename_all = "kebab-case")]
434436
pub enum BindingStyle {
435437
/// Automatically determine the best representation
436438
#[default]

crates/eure-schema/src/parse.rs

Lines changed: 5 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -31,30 +31,6 @@ use regex::Regex;
3131

3232
use crate::{BindingStyle, Description, TextSchema, TypeReference};
3333

34-
// ============================================================================
35-
// ParseDocument for existing types (no NodeId)
36-
// ============================================================================
37-
38-
impl ParseDocument<'_> for BindingStyle {
39-
type Error = ParseError;
40-
fn parse(ctx: &ParseContext<'_>) -> Result<Self, Self::Error> {
41-
let value: &str = ctx.parse()?;
42-
match value {
43-
"auto" => Ok(BindingStyle::Auto),
44-
"passthrough" => Ok(BindingStyle::Passthrough),
45-
"section" => Ok(BindingStyle::Section),
46-
"nested" => Ok(BindingStyle::Nested),
47-
"binding" => Ok(BindingStyle::Binding),
48-
"section-binding" => Ok(BindingStyle::SectionBinding),
49-
"section-root-binding" => Ok(BindingStyle::SectionRootBinding),
50-
_ => Err(ParseError {
51-
node_id: ctx.node_id(),
52-
kind: ParseErrorKind::UnknownVariant(value.to_string()),
53-
}),
54-
}
55-
}
56-
}
57-
5834
impl ParseDocument<'_> for Description {
5935
type Error = ParseError;
6036
fn parse(ctx: &ParseContext<'_>) -> Result<Self, Self::Error> {
@@ -948,10 +924,11 @@ mod tests {
948924
let node_id = create_text_node(&mut doc, "unknown");
949925

950926
let result: Result<BindingStyle, _> = doc.parse(node_id);
951-
assert!(matches!(
952-
result.unwrap_err().kind,
953-
ParseErrorKind::UnknownVariant(_)
954-
));
927+
let err = result.unwrap_err();
928+
assert_eq!(
929+
err.kind,
930+
ParseErrorKind::UnknownVariant("unknown".to_string())
931+
);
955932
}
956933

957934
#[test]

0 commit comments

Comments
 (0)