diff --git a/crates/iceberg/src/lib.rs b/crates/iceberg/src/lib.rs index 43473cc35e..b3a409fa8c 100644 --- a/crates/iceberg/src/lib.rs +++ b/crates/iceberg/src/lib.rs @@ -65,6 +65,7 @@ #[macro_use] extern crate derive_builder; extern crate core; +// Required so `#[derive(Properties)]` output can name `::iceberg::Error`. extern crate self as iceberg; mod error; diff --git a/crates/property-macro/README.md b/crates/property-macro/README.md index e85e9194b0..8ebf7054c7 100644 --- a/crates/property-macro/README.md +++ b/crates/property-macro/README.md @@ -50,6 +50,10 @@ Documentation attributes on the field are copied to the generated getter. The macro generates no setters, backing fields, or conversion back to a property map. +`Option` key fields and `HashMap` prefix fields must use the +corresponding types from `std`. The macro recognizes those field shapes +syntactically and generates code using the standard-library variants. + ## Complete example This example covers exact keys and defaults, optional values, case-insensitive @@ -141,14 +145,16 @@ struct TableLikeProperties { #[property(prefix = COLUMN_FPP_PREFIX, getter)] column_fpp: HashMap, - /// A single-key parser can validate and normalize a property value. + /// An explicitly configured data path override. + /// `None` means callers should use `/data`. + /// A single-key parser validates and normalizes configured values. #[property( key = LOCATION, - default = "warehouse", + default = None, parse_with = parse_location, getter )] - location: String, + location: Option, /// A full-map parser can model one field with multiple property keys. #[property( @@ -167,7 +173,7 @@ fn main() -> iceberg::Result<()> { assert_eq!(defaults.owner(), &None); assert!(defaults.fanout_enabled()); assert!(defaults.column_fpp().is_empty()); - assert_eq!(defaults.location(), "warehouse"); + assert_eq!(defaults.location(), &None); assert_eq!(defaults.dimensions(), (640, 480, 320)); let raw = HashMap::from([ @@ -187,7 +193,10 @@ fn main() -> iceberg::Result<()> { assert_eq!(properties.owner().as_deref(), Some("iceberg")); assert!(!properties.fanout_enabled()); assert_eq!(properties.column_fpp()["id"], 0.01); - assert_eq!(properties.location(), "s3://bucket/table"); + assert_eq!( + properties.location().as_deref(), + Some("s3://bucket/table") + ); assert_eq!(properties.dimensions(), (1920, 1080, 720)); let error = TableLikeProperties::from_properties(&HashMap::from([( diff --git a/crates/property-macro/tests/properties.rs b/crates/property-macro/tests/properties.rs index 2ced3b7f73..1b8edff146 100644 --- a/crates/property-macro/tests/properties.rs +++ b/crates/property-macro/tests/properties.rs @@ -249,6 +249,15 @@ fn custom_single_value_parser_wraps_present_optional_values() { )])) .unwrap(); assert_eq!(parsed.location().as_deref(), Some("path")); + + let error = OptionalValidatedProperties::from_properties(&HashMap::from([( + "optional-location".to_string(), + " ".to_string(), + )])) + .unwrap_err(); + assert_eq!(error.kind(), ErrorKind::DataInvalid); + assert_eq!(error.message(), "value must not be empty"); + assert!(format!("{error}").contains("property: optional-location")); } #[derive(Debug, Clone, Copy, PartialEq, Eq)]