-
Notifications
You must be signed in to change notification settings - Fork 108
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
Deserialization derive macros #1024
Conversation
accce36
to
2edbbfa
Compare
See the following report for details: cargo semver-checks output
|
9579ead
to
b090a75
Compare
Cassandra failure in CI is unrelated to the PR. @Lorak-mmk @muzarski will you take a look? |
b090a75
to
db49d5d
Compare
v1.1: more attribute verification & tests for that. |
db49d5d
to
6ce6ca3
Compare
Rebased on main. |
6ce6ca3
to
b2d5e1b
Compare
v1.2: addressed most of the review comments by @muzarski. Thank you very much for spotting bugs! |
Remember that previously, the doc tests were not executed in CI (the CI passed, even though there was a buggy test). Please, make sure that we include doc tests in CI. I briefly checked, and see that there is no |
The doc tests have always been executed in CI. The CI passed even though the test was buggy, because the tests failed to compile (as expected by the tests) because of different reasons than expected.
|
Oh, makes sense. I forgot that all tests failed because of |
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.
Very nice, but big PR, took some time to review.
syn provides strong typing on syntax items, which is better for us. Co-authored-by: Piotr Dulikowski <[email protected]>
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.
Sorry, I totally forgot about my comment regarding panicking in case of a name mismatch. I think that we should do the check in DeserializeRow::deserialize
, and panic just like in DeserializeValue::deserialize
for udts. Otherwise, LGTM. Good job!
DeserializeRow enforce_order flavour is added, analogous to SerializeRow enforce_order flavour. The flavour requires that fields in Rust struct are ordered the same way as columns in CQL row definition. Co-authored-by: Piotr Dulikowski <[email protected]>
DeserializeRow enforce_order flavour is tested in the following aspects: - the macro executes properly on a struct, - the generated type_check() and deserialize() implementations are correct both in valid and invalid cases (i.e. return error in invalid cases and expected value in valid cases). Co-authored-by: Piotr Dulikowski <[email protected]>
DeserializeRow enforce_order flavour is tested in the following aspects: - the generated type_check() and deserialize() implementations produce meaningful, appropriate errors in invalid cases.
`skip_name_checks` is allowed only in `enforce_order` flavour. If enabled, it turns off name match verification (e.g., for performance purposes) between Rust struct and CQL UDT definition. Co-authored-by: Piotr Dulikowski <[email protected]>
New tests are added for `enforce_order` flavour with name match verification turned off. Co-authored-by: Piotr Dulikowski <[email protected]>
44d261a
to
9fe650d
Compare
v1.3.1: added a field-column name check in |
`skip_name_checks` is allowed only in `enforce_order` flavour. If enabled, it turns off name match verification (e.g., for performance purposes) between Rust struct fields and CQL row columns. Co-authored-by: Piotr Dulikowski <[email protected]>
New tests are added for `enforce_order` flavour with name match verification turned off. Co-authored-by: Piotr Dulikowski <[email protected]>
`skip_name_checks` requires `enforce_order`, so let's ensure this at compile time by panicking the macro with an insightful message. The same with `skip_name_checks` excluding `rename`. And we check that `rename`s do not introduce any name clashes.
By default, if a UDT definition contains more fields than the Rust struct (in unordered flavour: anywhere, in enforce_order: in suffix), those excess fields are ignored. The `forbid_excess_udt_fields` attribute is added to fail with an error in case such fields are present. For comparison, DeserializeRow always requires the same number of Rust fields and CQL columns, effectively rejecting rows with excess columns.
New tests are added for both flavours that show how the attribute fails type check phase in case that excess fields are present.
By default, if a UDT definition does not contain a field that the Rust struct requires, type checking fails. Instead, if `allow_missing` is specified for a field, type check lets it pass and deserialization yields `Default::default()`. This is important in production: if adding a field to a UDT and clients are updated before the cluster, then using this attribute clients can operate correctly until the cluster gets updated (by extending the UDT definition with the field expected by clients). Co-authored-by: Piotr Dulikowski <[email protected]>
`enforce_order` flavour with `skip_name_checks` can't support `allow_missing` attribute in certain cases. Namely: * Fields with `allow_missing` are only permitted at the end of the struct, i.e. no field without `allow_missing` and `skip` is allowed to be after any field with `allow_missing`. If the condition is not upheld, the problem of matching fields becomes unsolvable. Thus, we don't support such cases, so let's ensure this condition at compile time by panicking the macro with an insightful message.
New tests are added for both flavours that show how the attribute allows for successful type check and deserialization when the corresponding field is missing from the CQL UDT definition. Co-authored-by: Piotr Dulikowski <[email protected]>
By default, if DB provides null value in serialized data and the corresponding Rust type expects non-null value, deserialization fails. Instead, if `default_when_null` is specified for a field, deserialization yields `Default::default()`. This is important in production: when added a field to a UDT, the existing UDT instances have the new field filled with null, even if the data represented makes no sense to allow nulls. By using `default_when_null`, clients can handle such situation without using `Option` in their Rust struct.
New tests are added for both flavours that show how the attribute allows for successful deserialization when the field contains null and the corresponding Rust type expects non-null value.
value.rs already got huge, better to put tests aside. Also, if only tests are modified when put in the same file as library code, Cargo will rebuild the lib crate anyway. Conversely, when tests are in a separate file, the lib crate won't be rebuilt and this saves precious time.
If only tests are modified when put in the same file as library code, Cargo will rebuild the lib crate anyway. Conversely, when tests are in a separate file, the lib crate won't be rebuilt and this saves precious time.
9fe650d
to
f470d1d
Compare
Fixes #962
Derive macros for
DeserializeValue
andDeserializeRow
are introduced.An example feature-complete use
Features
Flavours
Both macros support both unordered (the default) and enforce_order flavours. These are analogous to
Serialize{Value,Row}
macros flavours, i.e.skip_name_checks
attribute is given). This is more strict, but also more performant (because the code, by expecting a specific order, can be more compact and less dynamic).Struct attributes specific to
DeserializeValue
:forbid_excess_udt_fields
- by default, if a UDT definition contains more fields than the Rust struct (in unordered flavour: anywhere, in enforce_order: in suffix), those excess fields are ignored. Withforbid_excess_udt_fields
attribute set, type check fails with an error in case such fields are present. For comparison,DeserializeRow
always requires the same number of Rust fields and CQL columns, effectively rejecting rows with excess columns.Field attributes common to
DeserializeValue
andDeserializeRow
skip
- a Rust struct field is skipped during type check and deserialization, and initialised withDefault::default()
;rename = "X"
- a Rust struct field is matched to CQL UDT field / row column with the name specified instead of its own name.Field attributes specific to
DeserializeValue
The following attributes are very important in production when adding new fields to a UDT, if one first updates clients, then the cluster. They allow for seamless transition in such case.
allow_missing
- if a Rust struct field's name is missing from CQL UDT definition, the field is initialised withDefault::default()
;default_when_null
- if a CQL UDT field contains null, but the Rust field's type expects non-null, deserialization would normally fail. With this attribute, the field is initialised withDefault::default()
instead.Errors
The returned errors are rich and insightful, using the new deserialization error framework. They are well-tested.
Testing
All flavours and attributes should be tested in various combinations. Please verify that I didn't forget about any.
Pre-review checklist
./docs/source/
.Fixes:
annotations to PR description.