Skip to content

Commit 621f00b

Browse files
authored
Replace manual ParseDocument implementations with derive macro (#70)
1 parent 7faf28d commit 621f00b

File tree

15 files changed

+255
-222
lines changed

15 files changed

+255
-222
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/eure-codegen/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,4 @@ keywords = ["codegen", "eure", "schema"]
1212
[dependencies]
1313
bon = "3"
1414
eure-document = { path = "../eure-document" }
15+
eure-macros = { workspace = true }

crates/eure-codegen/src/parse.rs

Lines changed: 23 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
//! - [`RecordCodegen`] - Codegen settings for record types
1212
//! - [`FieldCodegen`] - Codegen settings for individual record fields
1313
14-
use eure_document::parse::{ParseContext, ParseDocument, ParseError};
14+
use eure_macros::ParseDocument;
1515

1616
// ============================================================================
1717
// Root-Level Codegen Types
@@ -29,25 +29,14 @@ use eure_document::parse::{ParseContext, ParseDocument, ParseError};
2929
/// type = "MyRootType"
3030
/// }
3131
/// ```
32-
#[derive(Debug, Clone, Default)]
32+
#[derive(Debug, Clone, Default, ParseDocument)]
33+
#[eure(crate = eure_document)]
3334
pub struct RootCodegen {
3435
/// The root type name for the generated code.
36+
#[eure(rename = "type", default)]
3537
pub type_name: Option<String>,
3638
}
3739

38-
impl ParseDocument<'_> for RootCodegen {
39-
type Error = ParseError;
40-
fn parse(ctx: &ParseContext<'_>) -> Result<Self, Self::Error> {
41-
let rec = ctx.parse_record()?;
42-
43-
let type_name = rec.parse_field_optional::<String>("type")?;
44-
45-
rec.deny_unknown_fields()?;
46-
47-
Ok(RootCodegen { type_name })
48-
}
49-
}
50-
5140
/// Default codegen settings applied to all types.
5241
///
5342
/// Corresponds to `$types.codegen-defaults` in the schema.
@@ -63,41 +52,23 @@ impl ParseDocument<'_> for RootCodegen {
6352
/// document-node-id-field = "doc_node"
6453
/// }
6554
/// ```
66-
#[derive(Debug, Clone, Default)]
55+
#[derive(Debug, Clone, Default, ParseDocument)]
56+
#[eure(crate = eure_document, rename_all = "kebab-case")]
6757
pub struct CodegenDefaults {
6858
/// Default derive macros for all generated types.
59+
#[eure(default)]
6960
pub derive: Option<Vec<String>>,
7061
/// Prefix for extension type field names (e.g., "ext_" -> ext_types).
62+
#[eure(default)]
7163
pub ext_types_field_prefix: Option<String>,
7264
/// Prefix for extension type names (e.g., "Ext" -> ExtTypes).
65+
#[eure(default)]
7366
pub ext_types_type_prefix: Option<String>,
7467
/// Field name for storing document node ID in generated types.
68+
#[eure(default)]
7569
pub document_node_id_field: Option<String>,
7670
}
7771

78-
impl ParseDocument<'_> for CodegenDefaults {
79-
type Error = ParseError;
80-
fn parse(ctx: &ParseContext<'_>) -> Result<Self, Self::Error> {
81-
let rec = ctx.parse_record()?;
82-
83-
let derive = rec.parse_field_optional::<Vec<String>>("derive")?;
84-
let ext_types_field_prefix =
85-
rec.parse_field_optional::<String>("ext-types-field-prefix")?;
86-
let ext_types_type_prefix = rec.parse_field_optional::<String>("ext-types-type-prefix")?;
87-
let document_node_id_field =
88-
rec.parse_field_optional::<String>("document-node-id-field")?;
89-
90-
rec.deny_unknown_fields()?;
91-
92-
Ok(CodegenDefaults {
93-
derive,
94-
ext_types_field_prefix,
95-
ext_types_type_prefix,
96-
document_node_id_field,
97-
})
98-
}
99-
}
100-
10172
// ============================================================================
10273
// Type-Level Codegen Types
10374
// ============================================================================
@@ -122,42 +93,23 @@ impl ParseDocument<'_> for CodegenDefaults {
12293
/// variants.b = `integer`
12394
/// }
12495
/// ```
125-
#[derive(Debug, Clone, Default)]
96+
#[derive(Debug, Clone, Default, ParseDocument)]
97+
#[eure(crate = eure_document, rename_all = "kebab-case")]
12698
pub struct UnionCodegen {
12799
/// Override the generated type name (from base-codegen).
100+
#[eure(rename = "type", default)]
128101
pub type_name: Option<String>,
129102
/// Override the list of derive macros (from base-codegen).
103+
#[eure(default)]
130104
pub derive: Option<Vec<String>>,
131105
/// Generate separate types for each variant instead of struct-like variants.
106+
#[eure(default)]
132107
pub variant_types: Option<bool>,
133108
/// Suffix for variant type names (e.g., "Type" -> TextType, IntegerType).
109+
#[eure(default)]
134110
pub variant_types_suffix: Option<String>,
135111
}
136112

137-
impl ParseDocument<'_> for UnionCodegen {
138-
type Error = ParseError;
139-
fn parse(ctx: &ParseContext<'_>) -> Result<Self, Self::Error> {
140-
let rec = ctx.parse_record()?;
141-
142-
// Parse base-codegen fields (flattened)
143-
let type_name = rec.parse_field_optional::<String>("type")?;
144-
let derive = rec.parse_field_optional::<Vec<String>>("derive")?;
145-
146-
// Parse union-specific fields
147-
let variant_types = rec.parse_field_optional::<bool>("variant-types")?;
148-
let variant_types_suffix = rec.parse_field_optional::<String>("variant-types-suffix")?;
149-
150-
rec.deny_unknown_fields()?;
151-
152-
Ok(UnionCodegen {
153-
type_name,
154-
derive,
155-
variant_types,
156-
variant_types_suffix,
157-
})
158-
}
159-
}
160-
161113
/// Codegen settings for record types.
162114
///
163115
/// Corresponds to `$types.record-codegen` in the schema.
@@ -179,29 +131,17 @@ impl ParseDocument<'_> for UnionCodegen {
179131
/// age = `integer`
180132
/// }
181133
/// ```
182-
#[derive(Debug, Clone, Default)]
134+
#[derive(Debug, Clone, Default, ParseDocument)]
135+
#[eure(crate = eure_document)]
183136
pub struct RecordCodegen {
184137
/// Override the generated type name (from base-codegen).
138+
#[eure(rename = "type", default)]
185139
pub type_name: Option<String>,
186140
/// Override the list of derive macros (from base-codegen).
141+
#[eure(default)]
187142
pub derive: Option<Vec<String>>,
188143
}
189144

190-
impl ParseDocument<'_> for RecordCodegen {
191-
type Error = ParseError;
192-
fn parse(ctx: &ParseContext<'_>) -> Result<Self, Self::Error> {
193-
let rec = ctx.parse_record()?;
194-
195-
// Parse base-codegen fields (flattened)
196-
let type_name = rec.parse_field_optional::<String>("type")?;
197-
let derive = rec.parse_field_optional::<Vec<String>>("derive")?;
198-
199-
rec.deny_unknown_fields()?;
200-
201-
Ok(RecordCodegen { type_name, derive })
202-
}
203-
}
204-
205145
/// Codegen settings for individual record fields.
206146
///
207147
/// Corresponds to `$types.field-codegen` in the schema.
@@ -215,25 +155,14 @@ impl ParseDocument<'_> for RecordCodegen {
215155
/// user-name.$codegen.name = "username" // Rename to `username` in Rust
216156
/// }
217157
/// ```
218-
#[derive(Debug, Clone, Default)]
158+
#[derive(Debug, Clone, Default, ParseDocument)]
159+
#[eure(crate = eure_document)]
219160
pub struct FieldCodegen {
220161
/// Override the field name in generated Rust code.
162+
#[eure(default)]
221163
pub name: Option<String>,
222164
}
223165

224-
impl ParseDocument<'_> for FieldCodegen {
225-
type Error = ParseError;
226-
fn parse(ctx: &ParseContext<'_>) -> Result<Self, Self::Error> {
227-
let rec = ctx.parse_record()?;
228-
229-
let name = rec.parse_field_optional::<String>("name")?;
230-
231-
rec.deny_unknown_fields()?;
232-
233-
Ok(FieldCodegen { name })
234-
}
235-
}
236-
237166
#[cfg(test)]
238167
mod tests {
239168
use super::*;

crates/eure-config/src/lib.rs

Lines changed: 12 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,9 @@
1313
use std::collections::HashMap;
1414
use std::path::{Path, PathBuf};
1515

16+
use eure::ParseDocument;
1617
use eure::document::parse_to_document;
17-
use eure_document::parse::{ParseContext, ParseDocument, ParseError};
18+
use eure_document::parse::{ParseContext, ParseDocument as ParseDocumentTrait, ParseError};
1819

1920
// Re-export for convenience
2021
pub use eure_document::parse::ParseError as ConfigParseError;
@@ -46,79 +47,36 @@ impl From<ParseError> for ConfigError {
4647
}
4748

4849
/// A check target definition.
49-
#[derive(Debug, Clone)]
50+
#[derive(Debug, Clone, ParseDocument)]
51+
#[eure(crate = eure_document, allow_unknown_fields)]
5052
pub struct Target {
5153
/// Glob patterns for files to include in this target.
5254
pub globs: Vec<String>,
5355
/// Optional schema file path (relative to config file).
56+
#[eure(default)]
5457
pub schema: Option<String>,
5558
}
5659

57-
impl ParseDocument<'_> for Target {
58-
type Error = ParseError;
59-
60-
fn parse(ctx: &ParseContext<'_>) -> Result<Self, Self::Error> {
61-
let rec = ctx.parse_record()?;
62-
63-
let globs = rec.parse_field::<Vec<String>>("globs")?;
64-
let schema = rec.parse_field_optional::<String>("schema")?;
65-
66-
rec.allow_unknown_fields()?;
67-
68-
Ok(Target { globs, schema })
69-
}
70-
}
71-
7260
/// CLI-specific configuration.
7361
#[cfg(feature = "cli")]
74-
#[derive(Debug, Clone, Default)]
62+
#[derive(Debug, Clone, Default, ParseDocument)]
63+
#[eure(crate = eure_document, rename_all = "kebab-case", allow_unknown_fields)]
7564
pub struct CliConfig {
7665
/// Default targets to check when running `eure check` without arguments.
66+
#[eure(default)]
7767
pub default_targets: Vec<String>,
7868
}
7969

80-
#[cfg(feature = "cli")]
81-
impl ParseDocument<'_> for CliConfig {
82-
type Error = ParseError;
83-
84-
fn parse(ctx: &ParseContext<'_>) -> Result<Self, Self::Error> {
85-
let rec = ctx.parse_record()?;
86-
87-
let default_targets = rec
88-
.parse_field_optional::<Vec<String>>("default-targets")?
89-
.unwrap_or_default();
90-
91-
rec.allow_unknown_fields()?;
92-
93-
Ok(CliConfig { default_targets })
94-
}
95-
}
96-
9770
/// Language server configuration.
9871
#[cfg(feature = "ls")]
99-
#[derive(Debug, Clone, Default)]
72+
#[derive(Debug, Clone, Default, ParseDocument)]
73+
#[eure(crate = eure_document, rename_all = "kebab-case", allow_unknown_fields)]
10074
pub struct LsConfig {
10175
/// Whether to format on save.
76+
#[eure(default)]
10277
pub format_on_save: bool,
10378
}
10479

105-
#[cfg(feature = "ls")]
106-
impl ParseDocument<'_> for LsConfig {
107-
type Error = ParseError;
108-
109-
fn parse(ctx: &ParseContext<'_>) -> Result<Self, Self::Error> {
110-
let rec = ctx.parse_record()?;
111-
112-
let format_on_save = rec
113-
.parse_field_optional::<bool>("format-on-save")?
114-
.unwrap_or(false);
115-
116-
rec.allow_unknown_fields()?;
117-
118-
Ok(LsConfig { format_on_save })
119-
}
120-
}
121-
12280
/// The main Eure configuration.
12381
#[derive(Debug, Clone, Default)]
12482
pub struct EureConfig {
@@ -134,7 +92,7 @@ pub struct EureConfig {
13492
pub ls: Option<LsConfig>,
13593
}
13694

137-
impl ParseDocument<'_> for EureConfig {
95+
impl ParseDocumentTrait<'_> for EureConfig {
13896
type Error = ParseError;
13997

14098
fn parse(ctx: &ParseContext<'_>) -> Result<Self, Self::Error> {

crates/eure-document/src/parse.rs

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -830,9 +830,13 @@ pub enum ParseErrorKind {
830830
#[error("value out of range: {0}")]
831831
OutOfRange(String),
832832

833-
/// Invalid string pattern.
834-
#[error("invalid pattern: expected {pattern}, got {value}")]
835-
InvalidPattern { pattern: String, value: String },
833+
/// Invalid value pattern or format.
834+
///
835+
/// Used for validation errors in types like regex, URL, UUID, etc.
836+
/// - `kind`: Type of validation (e.g., "regex", "url", "uuid", "pattern: <expected>")
837+
/// - `reason`: Human-readable error message explaining the failure
838+
#[error("invalid {kind}: {reason}")]
839+
InvalidPattern { kind: String, reason: String },
836840

837841
/// Nested parse error with path context.
838842
#[error("at {path}: {source}")]
@@ -1338,6 +1342,21 @@ where
13381342
}
13391343
}
13401344

1345+
impl ParseDocument<'_> for regex::Regex {
1346+
type Error = ParseError;
1347+
1348+
fn parse(ctx: &ParseContext<'_>) -> Result<Self, Self::Error> {
1349+
let pattern: &str = ctx.parse()?;
1350+
regex::Regex::new(pattern).map_err(|e| ParseError {
1351+
node_id: ctx.node_id(),
1352+
kind: ParseErrorKind::InvalidPattern {
1353+
kind: format!("regex '{}'", pattern),
1354+
reason: e.to_string(),
1355+
},
1356+
})
1357+
}
1358+
}
1359+
13411360
/// `Option<T>` is a union with variants `some` and `none`.
13421361
///
13431362
/// - `$variant: some` -> parse T

crates/eure-macros/src/attrs/container.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,12 @@ pub struct ContainerAttrs {
1616
/// When true, uses `ctx.parse_extension()` and `ext.parse_ext()` instead of
1717
/// `ctx.parse_record()` and `rec.parse_field()`.
1818
pub parse_ext: bool,
19+
/// Allow unknown fields instead of denying them.
20+
/// By default (false), unknown fields cause a parse error.
21+
/// When true, uses `allow_unknown_fields()` instead of `deny_unknown_fields()`.
22+
pub allow_unknown_fields: bool,
23+
/// Allow unknown extensions instead of denying them.
24+
/// By default (false), unknown extensions cause a parse error.
25+
/// When true, skips the `deny_unknown_extensions()` check.
26+
pub allow_unknown_extensions: bool,
1927
}

crates/eure-macros/src/attrs/variant.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,8 @@ use darling::FromVariant;
55
pub struct VariantAttrs {
66
/// Explicit rename for this variant (overrides rename_all)
77
pub rename: Option<String>,
8+
/// Allow unknown fields for this variant instead of denying them.
9+
/// By default (false), unknown fields cause a parse error.
10+
/// When true, uses `allow_unknown_fields()` instead of `deny_unknown_fields()`.
11+
pub allow_unknown_fields: bool,
812
}

0 commit comments

Comments
 (0)