-
Notifications
You must be signed in to change notification settings - Fork 0
477: feat: Allow types to provide default values #55
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
13b4e53
40a4506
c5ade47
3915546
28ebd24
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -81,6 +81,10 @@ use std::collections::{HashMap, HashSet}; | |||||||||||||||||
| /// | ||||||||||||||||||
| /// Set the `doc` attribute of the schema. Defaults to the documentation of the type. | ||||||||||||||||||
| /// | ||||||||||||||||||
| /// - `#[avro(default = r#"{"field": 42, "other": "Spam"}"#)]` | ||||||||||||||||||
| /// | ||||||||||||||||||
| /// Provide the default value for this type when it is used in a field. | ||||||||||||||||||
| /// | ||||||||||||||||||
| /// - `#[avro(alias = "name")]` | ||||||||||||||||||
| /// | ||||||||||||||||||
| /// Set the `alias` attribute of the schema. Can be specified multiple times. | ||||||||||||||||||
|
|
@@ -113,11 +117,22 @@ use std::collections::{HashMap, HashSet}; | |||||||||||||||||
| /// | ||||||||||||||||||
| /// Set the `doc` attribute of the field. Defaults to the documentation of the field. | ||||||||||||||||||
| /// | ||||||||||||||||||
| /// - `#[avro(default = "null")]` | ||||||||||||||||||
| /// - `#[avro(default = ..)]` | ||||||||||||||||||
| /// | ||||||||||||||||||
| /// Control the `default` attribute of the field. When not used, it will use [`AvroSchemaComponent::field_default`] | ||||||||||||||||||
| /// to get the default value for a type. To remove the `default` attribute for a field, set `default` to `false`: `#[avro(default = false)]`. | ||||||||||||||||||
| /// | ||||||||||||||||||
| /// Set the `default` attribute of the field. | ||||||||||||||||||
| /// To override or set a default value, provide a JSON string: | ||||||||||||||||||
| /// | ||||||||||||||||||
| /// _Note:_ This is a JSON value not a Rust value, as this is put in the schema itself. | ||||||||||||||||||
| /// - Null: `#[avro(default = "null")]` | ||||||||||||||||||
| /// - Boolean: `#[avro(default = "true")]`. | ||||||||||||||||||
| /// - Number: `#[avro(default = "42")]` or `#[avro(default = "42.5")]` | ||||||||||||||||||
| /// - String: `#[avro(default = r#""String needs extra quotes""#)]`. | ||||||||||||||||||
| /// - Array: `#[avro(default = r#"["One", "Two", "Three"]"#)]`. | ||||||||||||||||||
| /// - Object: `#[avro(default = r#"{"One": 1}"#)]`. | ||||||||||||||||||
| /// | ||||||||||||||||||
| /// See [the specification](https://avro.apache.org/docs/++version++/specification/#schema-record) | ||||||||||||||||||
| /// for details on how to map a type to a JSON value. | ||||||||||||||||||
| /// | ||||||||||||||||||
| /// - `#[serde(alias = "name")]` | ||||||||||||||||||
| /// | ||||||||||||||||||
|
|
@@ -220,6 +235,11 @@ pub trait AvroSchema { | |||||||||||||||||
| /// fn get_record_fields_in_ctxt(_: usize, _: &mut HashSet<Name>, _: &Namespace) -> Option<Vec<RecordField>> { | ||||||||||||||||||
| /// None // A Schema::Int is not a Schema::Record so there are no fields to return | ||||||||||||||||||
| /// } | ||||||||||||||||||
| /// | ||||||||||||||||||
| /// fn field_default() -> Option<serde_json::Value> { | ||||||||||||||||||
| /// // Zero as default value. Can also be None if you don't want to provide a default value | ||||||||||||||||||
| /// Some(0u8.into()) | ||||||||||||||||||
| /// } | ||||||||||||||||||
| ///} | ||||||||||||||||||
| /// ``` | ||||||||||||||||||
| /// | ||||||||||||||||||
|
|
@@ -242,6 +262,10 @@ pub trait AvroSchema { | |||||||||||||||||
| /// fn get_record_fields_in_ctxt(first_field_position: usize, named_schemas: &mut HashSet<Name>, enclosing_namespace: &Namespace) -> Option<Vec<RecordField>> { | ||||||||||||||||||
| /// T::get_record_fields_in_ctxt(first_field_position, named_schemas, enclosing_namespace) | ||||||||||||||||||
| /// } | ||||||||||||||||||
| /// | ||||||||||||||||||
| /// fn field_default() -> Option<serde_json::Value> { | ||||||||||||||||||
| /// T::field_default() | ||||||||||||||||||
| /// } | ||||||||||||||||||
| ///} | ||||||||||||||||||
| /// ``` | ||||||||||||||||||
| /// | ||||||||||||||||||
|
|
@@ -256,6 +280,7 @@ pub trait AvroSchema { | |||||||||||||||||
| /// - Implement `get_record_fields_in_ctxt` as the default implementation has to be implemented | ||||||||||||||||||
| /// with backtracking and a lot of cloning. | ||||||||||||||||||
| /// - Even if your schema is not a record, still implement the function and just return `None` | ||||||||||||||||||
| /// - Implement `field_default()` if you want to use `#[serde(skip_serializing{,_if})]`. | ||||||||||||||||||
| /// | ||||||||||||||||||
| /// ``` | ||||||||||||||||||
| /// # use apache_avro::{Schema, serde::{AvroSchemaComponent}, schema::{Name, Namespace, RecordField, RecordSchema}}; | ||||||||||||||||||
|
|
@@ -305,6 +330,11 @@ pub trait AvroSchema { | |||||||||||||||||
| /// .build(), | ||||||||||||||||||
| /// ]) | ||||||||||||||||||
| /// } | ||||||||||||||||||
| /// | ||||||||||||||||||
| /// fn field_default() -> Option<serde_json::Value> { | ||||||||||||||||||
| /// // This type does not provide a default value | ||||||||||||||||||
| /// None | ||||||||||||||||||
| /// } | ||||||||||||||||||
| ///} | ||||||||||||||||||
| /// ``` | ||||||||||||||||||
| pub trait AvroSchemaComponent { | ||||||||||||||||||
|
|
@@ -332,6 +362,16 @@ pub trait AvroSchemaComponent { | |||||||||||||||||
| Self::get_schema_in_ctxt, | ||||||||||||||||||
| ) | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| /// The default value of this type when used for a record field. | ||||||||||||||||||
| /// | ||||||||||||||||||
| /// `None` means no default value, which is also the default implementation. | ||||||||||||||||||
| /// | ||||||||||||||||||
| /// Implementations of this trait provided by this crate return `None` except for `Option<T>` | ||||||||||||||||||
| /// which returns `Some(serde_json::Value::Null)`. | ||||||||||||||||||
| fn field_default() -> Option<serde_json::Value> { | ||||||||||||||||||
| None | ||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| /// Get the record fields from `schema_fn` without polluting `named_schemas` or causing duplicate names | ||||||||||||||||||
|
|
@@ -487,6 +527,10 @@ macro_rules! impl_schema ( | |||||||||||||||||
| fn get_record_fields_in_ctxt(_: usize, _: &mut HashSet<Name>, _: &Namespace) -> Option<Vec<RecordField>> { | ||||||||||||||||||
| None | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| fn field_default() -> Option<serde_json::Value> { | ||||||||||||||||||
| None | ||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
| ); | ||||||||||||||||||
| ); | ||||||||||||||||||
|
|
@@ -515,6 +559,10 @@ macro_rules! impl_passthrough_schema ( | |||||||||||||||||
| fn get_record_fields_in_ctxt(first_field_position: usize, named_schemas: &mut HashSet<Name>, enclosing_namespace: &Namespace) -> Option<Vec<RecordField>> { | ||||||||||||||||||
| T::get_record_fields_in_ctxt(first_field_position, named_schemas, enclosing_namespace) | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| fn field_default() -> Option<serde_json::Value> { | ||||||||||||||||||
| T::field_default() | ||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
| ); | ||||||||||||||||||
| ); | ||||||||||||||||||
|
|
@@ -535,6 +583,10 @@ macro_rules! impl_array_schema ( | |||||||||||||||||
| fn get_record_fields_in_ctxt(_: usize, _: &mut HashSet<Name>, _: &Namespace) -> Option<Vec<RecordField>> { | ||||||||||||||||||
| None | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| fn field_default() -> Option<serde_json::Value> { | ||||||||||||||||||
| None | ||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
| ); | ||||||||||||||||||
| ); | ||||||||||||||||||
|
|
@@ -562,6 +614,11 @@ where | |||||||||||||||||
| ) -> Option<Vec<RecordField>> { | ||||||||||||||||||
| None | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| /// If `T` has a field default, this will return an array of elements with that default. Otherwise there is no default. | ||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The documentation for
Suggested change
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. value:good-to-have; category:documentation; feedback: The Gemini AI reviewer is correct! The docstring is obsolete since now there is no default value by default for all types but std::option::Option. The method could be removed because it does the same as the default implementation in the AvroSchemaComponent trait. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The doc comment says the array implementation will derive a default from Severity: low 🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. value:good-to-have; category:documentation; feedback: The Augment AI reviewer is correct! The docstring is obsolete since now there is no default value by default for all types but std::option::Option. The method could be removed because it does the same as the default implementation in the AvroSchemaComponent trait. |
||||||||||||||||||
| fn field_default() -> Option<serde_json::Value> { | ||||||||||||||||||
| None | ||||||||||||||||||
| } | ||||||||||||||||||
|
Comment on lines
+617
to
+621
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Stale doc comment contradicts the implementation. The doc comment says "If Proposed fix (update doc to match implementation)- /// If `T` has a field default, this will return an array of elements with that default. Otherwise there is no default.
- fn field_default() -> Option<serde_json::Value> {
- None
- }
+ fn field_default() -> Option<serde_json::Value> {
+ None
+ }📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. value:good-to-have; category:documentation; feedback: The CodeRabbit AI reviewer is correct! The docstring is obsolete since now there is no default value by default for all types but std::option::Option. The method could be removed because it does the same as the default implementation in the AvroSchemaComponent trait. |
||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| impl<T> AvroSchemaComponent for HashMap<String, T> | ||||||||||||||||||
|
|
@@ -582,6 +639,10 @@ where | |||||||||||||||||
| ) -> Option<Vec<RecordField>> { | ||||||||||||||||||
| None | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| fn field_default() -> Option<serde_json::Value> { | ||||||||||||||||||
| None | ||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| impl<T> AvroSchemaComponent for Option<T> | ||||||||||||||||||
|
|
@@ -609,6 +670,10 @@ where | |||||||||||||||||
| ) -> Option<Vec<RecordField>> { | ||||||||||||||||||
| None | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| fn field_default() -> Option<serde_json::Value> { | ||||||||||||||||||
| Some(serde_json::Value::Null) | ||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| impl AvroSchemaComponent for core::time::Duration { | ||||||||||||||||||
|
|
@@ -644,6 +709,10 @@ impl AvroSchemaComponent for core::time::Duration { | |||||||||||||||||
| ) -> Option<Vec<RecordField>> { | ||||||||||||||||||
| None | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| fn field_default() -> Option<serde_json::Value> { | ||||||||||||||||||
| None | ||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| impl AvroSchemaComponent for uuid::Uuid { | ||||||||||||||||||
|
|
@@ -679,6 +748,10 @@ impl AvroSchemaComponent for uuid::Uuid { | |||||||||||||||||
| ) -> Option<Vec<RecordField>> { | ||||||||||||||||||
| None | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| fn field_default() -> Option<serde_json::Value> { | ||||||||||||||||||
| None | ||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| impl AvroSchemaComponent for u64 { | ||||||||||||||||||
|
|
@@ -712,6 +785,10 @@ impl AvroSchemaComponent for u64 { | |||||||||||||||||
| ) -> Option<Vec<RecordField>> { | ||||||||||||||||||
| None | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| fn field_default() -> Option<serde_json::Value> { | ||||||||||||||||||
| None | ||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| impl AvroSchemaComponent for u128 { | ||||||||||||||||||
|
|
@@ -745,6 +822,10 @@ impl AvroSchemaComponent for u128 { | |||||||||||||||||
| ) -> Option<Vec<RecordField>> { | ||||||||||||||||||
| None | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| fn field_default() -> Option<serde_json::Value> { | ||||||||||||||||||
| None | ||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| impl AvroSchemaComponent for i128 { | ||||||||||||||||||
|
|
@@ -778,12 +859,18 @@ impl AvroSchemaComponent for i128 { | |||||||||||||||||
| ) -> Option<Vec<RecordField>> { | ||||||||||||||||||
| None | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| fn field_default() -> Option<serde_json::Value> { | ||||||||||||||||||
| None | ||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| #[cfg(test)] | ||||||||||||||||||
| mod tests { | ||||||||||||||||||
| use crate::schema::{FixedSchema, Name}; | ||||||||||||||||||
| use crate::{AvroSchema, Schema}; | ||||||||||||||||||
| use crate::{ | ||||||||||||||||||
| AvroSchema, Schema, | ||||||||||||||||||
| schema::{FixedSchema, Name}, | ||||||||||||||||||
| }; | ||||||||||||||||||
| use apache_avro_test_helper::TestResult; | ||||||||||||||||||
|
|
||||||||||||||||||
| #[test] | ||||||||||||||||||
|
|
||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -766,6 +766,13 @@ impl Value { | |
| } | ||
| Value::Uuid(Uuid::from_slice(bytes).map_err(Details::ConvertSliceToUuid)?) | ||
| } | ||
| (Value::String(ref string), UuidSchema::Fixed(_)) => { | ||
| let bytes = string.as_bytes(); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For Severity: high 🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. value:annoying; category:bug; feedback: The Augment AI reviewer is not correct! By specification UUID v4 could contain only alphanumeric characters and the '-'. So, there are no multi-byte characters in it. If the String length is not 16 then the value is invalid. |
||
| if bytes.len() != 16 { | ||
| return Err(Details::ConvertFixedToUuid(bytes.len()).into()); | ||
| } | ||
| Value::Uuid(Uuid::from_slice(bytes).map_err(Details::ConvertSliceToUuid)?) | ||
| } | ||
| (other, _) => return Err(Details::GetUuid(other).into()), | ||
| }; | ||
| Ok(value) | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doc link contains
++version++, which looks like a placeholder and likely won’t resolve as-is; consider linking to a concrete Avro spec version or a stable URL.Severity: low
🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
value:incorrect-but-reasonable; category:documentation; feedback: The Augment AI reviewer is not correct!
++version++is a special placeholder for the next version that is not yet released. It is always valid.