Skip to content
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

impl #[dynomite(default)] to gracefully handle sparse data #113

Merged
merged 13 commits into from
Jul 13, 2020

Conversation

softprops
Copy link
Owner

What did you implement:

Like the title says, when you are dealing with dynamodb data you sometimes need to account for sparse data. This is different than the ddb NULL type. This is the case where the field is just not present at all.

This change allows users to specify a new field attr for Items #[dynomite(default)] which is the mvp of dealing with an absent alternative std::default::Default::default()! Users will of course have to ensure their field type has a sensible default

Closes: #92

How did you verify your change:

  1. update example code

  2. cargo install cargo-expand # <-- best tool ever

  3. cargo expand -p dynomite --example demo

...
impl ::dynomite::FromAttributes for Author {
    fn from_attrs(
        mut attrs: ::dynomite::Attributes,
    ) -> ::std::result::Result<Self, ::dynomite::AttributeError> {
        ::std::result::Result::Ok(Self {
            id: ::dynomite::Attribute::from_attr(attrs.remove("id").ok_or(
                ::dynomite::AttributeError::MissingField {
                    name: "id".to_string(),
                },
            )?)?,
            name: match attrs.remove("name") {
                Some(field) => ::dynomite::Attribute::from_attr(field)?,
                _ => ::std::default::Default::default(), <-- note what happens here
            },
        })
    }
}
...

What (if anything) would need to be called out in the CHANGELOG for the next release:

let name = &ast.ident;
let vis = &ast.vis;
match ast.data {
Struct(DataStruct { fields, .. }) => match fields {
Fields::Named(named) => {
make_dynomite_item(vis, name, &named.named.into_iter().collect::<Vec<_>>())
}
_ => panic!("Dynomite Items require named fields"),
fields => Err(syn::Error::new(
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also took some liberty to improve error ux. this change gives the user a precise location of the source of the problem

@@ -25,7 +25,7 @@ use uuid::Uuid;
pub struct Book {
#[dynomite(partition_key)]
id: Uuid,
#[dynomite(rename = "bookTitle")]
#[dynomite(rename = "bookTitle", default)]
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

need to test this. not quite working yet

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doug, this will make the structures very verbose and will need a default implementation for any complex type. Would it better to assume that if a field can be absent it would be defined in the struct as Option and we can just return None?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, Option has None for default. So my only question here is if we can return None in all cases and avoid using #[dynomite(default)] altogether ...

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To answer your questions in order

  1. I'd err on the side of being explicit for two practical reasons
  • I don't want the default behavior to hide potential application bugs
  • Not all types that could impl Attribute are guaranteed to have Default impls. I could add that type constraint but I don't believe that's helpful in cases where you might want to impl attribute for a type that can't have a Default impl by design. These interfaces should be made to fit the types you have as a design goal.
  1. procmacros aren't type-aware, only syntax-aware. There's not a clear way in a procmacro to dicern types and Rust intentionally avoids runtime reflection by not providing a convenient runtime reflection api. I don't think it's possible to to do this only for option types and relating to question one: I'd strongly prefer an explicit approach to avoid hiding application bugs. Application developers should know which fields should be present and which might not.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Third thought I had. It might be useful in a future change to allow for a custom defaulting fn when a field is absent. Default::default() is just an mvp for a more common case.

@softprops
Copy link
Owner Author

Just a status update: I'm doing a bit of refactoring. It's actually not possible as far as I can see today for item fields to have multiple attributes in the way one might expect: comma delimited. I would expect a rename attr arg to play well with default. It doesn't in the example code.

I want to cover that case before I call this change good

@softprops
Copy link
Owner Author

This should be good to go. Highlight it's now much simpler to add attrs.

@softprops softprops merged commit 62be32a into master Jul 13, 2020
@softprops softprops deleted the default-attribute-fields branch July 13, 2020 15:49
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.

How to deal with optional attributes?
2 participants