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

feat(field_attributes): add helper functions #41

Open
wants to merge 2 commits into
base: master
Choose a base branch
from

Conversation

lucatrv
Copy link
Contributor

@lucatrv lucatrv commented Feb 24, 2024

Summary by CodeRabbit

  • Refactor
    • Enhanced serialization and deserialization by adjusting traits and helper functions.
    • Improved debugging and comparison capabilities with updated attributes.

Copy link

coderabbitai bot commented Feb 24, 2024

Walkthrough

The update focuses on optimizing deserialization by refining trait implementations and debugging processes in the codebase. It involves removing serde::Serialize, standardizing Deserialize, adjusting trait bounds, refining Debug and PartialEq derives, introducing deserialization helper functions, and updating serialization and debugging attributes across various structs.

Changes

Files Change Summary
src/field_attributes.rs - Removed serde::Serialize
- Replaced serde::Deserialize with Deserialize
- Updated trait bounds to Deserialize
- Adjusted Debug and PartialEq derives
- Introduced new deserialization helper functions
- Updated #[derive] attributes for deserialization and debug in various structs

🐰✨
Changes abound, the code does refine,
Serialize gone, Deserialize shines.
Traits and derives, neatly align,
Helpers emerge, as stars do entwine.
Debug and equal, more robust by design,
A rabbit's work, in code, does entwine. 🌟📜
🐰✨

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

Share

Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>.
    • Generate unit-tests for this file.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit tests for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai generate interesting stats about this repository from git and render them as a table.
    • @coderabbitai show all the console.log statements in this repository.
    • @coderabbitai read src/utils.ts and generate unit tests.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (invoked as PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger a review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai help to get help.

Additionally, you can add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.

CodeRabbit Configration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • The JSON schema for the configuration file is available here.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/coderabbit-overrides.v2.json

CodeRabbit Discord Community

Join our Discord Community to get help, request features, and share feedback.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Review Status

Actionable comments generated: 0

Configuration used: CodeRabbit UI

Commits Files that changed from the base of the PR and between 6d0e9f0 and df9b28f.
Files ignored due to path filters (1)
  • Cargo.toml is excluded by: !**/*.toml
Files selected for processing (1)
  • src/field_attributes.rs (29 hunks)
Additional comments: 12
src/field_attributes.rs (12)
  • 286-292: > 📝 NOTE

This review was outside the diff hunks and was mapped to the diff hunk with the greatest overlap. Original lines [264-317]

The function deserialize_number_from_string is well-implemented, leveraging Rust's type system and serde's capabilities to deserialize numbers from either string or numeric inputs. This function enhances the flexibility of input data handling, which is particularly useful in scenarios where the data source might not be consistent in its representation of numbers.

  • 337-343: > 📝 NOTE

This review was outside the diff hunks and was mapped to the diff hunk with the greatest overlap. Original lines [340-397]

The function deserialize_option_number_from_string correctly handles optional numeric fields, allowing for a graceful handling of null, empty strings, or valid numeric values (both as strings and as numbers). This function is a valuable addition for APIs or data formats where optional fields may be omitted or presented in different formats.

  • 543-549: > 📝 NOTE

This review was outside the diff hunks and was mapped to the diff hunk with the greatest overlap. Original lines [546-669]

The function deserialize_bool_from_anything introduces a robust way to deserialize boolean values from various input formats, including strings, integers, floats, and booleans. This function significantly enhances flexibility and error tolerance when dealing with diverse data sources. However, it's important to ensure that this flexibility aligns with the expected data integrity and validation requirements of the application.

  • 666-672: > 📝 NOTE

This review was outside the diff hunks and was mapped to the diff hunk with the greatest overlap. Original lines [669-714]

The function deserialize_string_from_number provides a straightforward and useful functionality to deserialize numeric values as strings, which can be particularly useful in contexts where numeric values need to be treated as text, such as identifiers or codes. This function complements the library's deserialization capabilities well.

  • 711-717: > 📝 NOTE

This review was outside the diff hunks and was mapped to the diff hunk with the greatest overlap. Original lines [714-747]

The function deserialize_default_from_null is a practical utility for handling null values by substituting them with default values of the specified type. This function can simplify handling optional fields in data structures, ensuring that they have sensible defaults when not explicitly provided.

  • 747-753: The function deserialize_default_from_empty_object introduces a nuanced approach to handling null or empty objects by defaulting to a specified type's default value. This function is particularly useful for complex nested structures where an empty or missing object should not lead to an error but rather a default state. It's a thoughtful addition to the library's deserialization utilities.
  • 802-808: > 📝 NOTE

This review was outside the diff hunks and was mapped to the diff hunk with the greatest overlap. Original lines [805-821]

The function deserialize_vec_from_string_or_vec is a versatile utility that allows for deserializing a list of elements either from a comma-separated string or a JSON array. This function enhances the library's flexibility in handling list inputs, accommodating various data representation formats seamlessly.

  • 828-884: The function deserialize_to_type_or_fallback introduces an innovative approach to deserialization by attempting to deserialize to a primary type and falling back to a secondary type if the first attempt fails. This function can be particularly useful in scenarios where data might be represented in multiple valid formats, and the application needs to handle all such formats gracefully.
  • 886-922: The function deserialize_to_type_or_none provides a fail-safe deserialization mechanism where, instead of failing on incompatible types, it returns None. This approach is beneficial for optional fields where data integrity is not strictly enforced, and the absence of a value is an acceptable outcome.
  • 924-987: The function deserialize_to_type_or_string_lossy offers a flexible deserialization strategy that attempts to deserialize to a specified type and, upon failure, captures the input as a string. This function is particularly useful for logging or error reporting purposes, where understanding the original input can provide valuable context for debugging.
  • 994-1000: > 📝 NOTE

This review was outside the diff hunks and was mapped to the diff hunk with the greatest overlap. Original lines [997-1014]

The macro StringOrVecToVecParser is a powerful tool for creating custom deserializers that can handle both string and vector inputs, converting them into a vector of a specified type. This macro significantly enhances the library's deserialization capabilities, allowing for custom parsing logic that can accommodate complex input scenarios.

  • 1116-1122: > 📝 NOTE

This review was outside the diff hunks and was mapped to the diff hunk with the greatest overlap. Original lines [1071-1169]

The StringOrVecToVec struct and its associated methods provide a comprehensive solution for parsing strings or vectors into vectors of a specified type. This functionality is crucial for handling data that may come in different formats but needs to be normalized into a consistent internal representation. The design of this utility is well-thought-out, offering flexibility through custom separators and parsing functions.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Review Status

Actionable comments generated: 0

Configuration used: CodeRabbit UI

Commits Files that changed from the base of the PR and between 6d0e9f0 and 08ebb38.
Files ignored due to path filters (1)
  • Cargo.toml is excluded by: !**/*.toml
Files selected for processing (1)
  • src/field_attributes.rs (29 hunks)
Files skipped from review as they are similar to previous changes (1)
  • src/field_attributes.rs

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Review Status

Actionable comments generated: 0

Configuration used: CodeRabbit UI

Commits Files that changed from the base of the PR and between 6d0e9f0 and e01ff98.
Files ignored due to path filters (1)
  • Cargo.toml is excluded by: !**/*.toml
Files selected for processing (1)
  • src/field_attributes.rs (29 hunks)
Files skipped from review as they are similar to previous changes (1)
  • src/field_attributes.rs

/// f64_or_string: Result<f64, String>,
/// }
///
/// let s = r#" { "i64_or_f64": 1, "f64_or_string": 1 } "#;
Copy link
Owner

Choose a reason for hiding this comment

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

I do not think 1 is a valid f64 number, as it has no valid representation in the floating point value specification. I think the parser should have failed. I understand this is not how serde works here, but this might be confusing. Can we do something about it? I don't think so. Can you think of any possible improvement here?

/// let s = r#" { "i64_or_f64": "foo", "f64_or_string": "foo" } "#;
/// assert!(serde_json::from_str::<MyStruct>(s).is_err());
/// ```
pub fn deserialize_to_type_or_fallback<'de, D, T, F>(
Copy link
Owner

Choose a reason for hiding this comment

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

I understand that this implementation was quite easy to implement this way, but I do not really like using the Result type for this matter. I wish we introduced another generic enum of our own to clearly designate the purpose (and the context), something like:

enum Either<FirstType, SecondType> {
    First(FirstType),
    Second(SecondType),
}

What do you think about that?

/// let a: MyStruct = serde_json::from_str(s).unwrap();
/// assert_eq!(a.f64_or_string_lossy, Err(String::from("foo")));
/// ```
pub fn deserialize_to_type_or_string_lossy<'de, D, T>(
Copy link
Owner

Choose a reason for hiding this comment

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

Should we have something like that but also returning just the byte array of the input instead? Perhaps, it could be more useful in some cases than a string? What do you think?

Comment on lines +961 to +986
let result = T::deserialize(value.clone()).map_err(|_| match value {
serde_value::Value::Bool(b) => b.to_string(),
serde_value::Value::U8(u) => u.to_string(),
serde_value::Value::U16(u) => u.to_string(),
serde_value::Value::U32(u) => u.to_string(),
serde_value::Value::U64(u) => u.to_string(),
serde_value::Value::I8(i) => i.to_string(),
serde_value::Value::I16(i) => i.to_string(),
serde_value::Value::I32(i) => i.to_string(),
serde_value::Value::I64(i) => i.to_string(),
serde_value::Value::F32(f) => f.to_string(),
serde_value::Value::F64(f) => f.to_string(),
serde_value::Value::Char(c) => c.to_string(),
serde_value::Value::String(s) => s,
serde_value::Value::Unit => String::new(),
serde_value::Value::Option(opt) => {
format!("{:?}", opt)
}
serde_value::Value::Newtype(nt) => {
format!("{:?}", nt)
}
serde_value::Value::Seq(seq) => format!("{:?}", seq),
serde_value::Value::Map(map) => format!("{:?}", map),
serde_value::Value::Bytes(v) => String::from_utf8_lossy(&v).into_owned(),
});
Ok(result)
Copy link
Owner

Choose a reason for hiding this comment

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

All the branches of this code lead to an immediate return, we can just

T::deserialize(value.clone()).map_err(|_| match value {
        serde_value::Value::Bool(b) => b.to_string(),
        serde_value::Value::U8(u) => u.to_string(),
        serde_value::Value::U16(u) => u.to_string(),
        serde_value::Value::U32(u) => u.to_string(),
        serde_value::Value::U64(u) => u.to_string(),
        serde_value::Value::I8(i) => i.to_string(),
        serde_value::Value::I16(i) => i.to_string(),
        serde_value::Value::I32(i) => i.to_string(),
        serde_value::Value::I64(i) => i.to_string(),
        serde_value::Value::F32(f) => f.to_string(),
        serde_value::Value::F64(f) => f.to_string(),
        serde_value::Value::Char(c) => c.to_string(),
        serde_value::Value::String(s) => s,
        serde_value::Value::Unit => String::new(),
        serde_value::Value::Option(opt) => {
            format!("{:?}", opt)
        }
        serde_value::Value::Newtype(nt) => {
            format!("{:?}", nt)
        }
        serde_value::Value::Seq(seq) => format!("{:?}", seq),
        serde_value::Value::Map(map) => format!("{:?}", map),
        serde_value::Value::Bytes(v) => String::from_utf8_lossy(&v).into_owned(),
    })

Comment on lines +1389 to +1390
D: Deserializer<'de>,
T: Deserialize<'de>,
Copy link
Owner

Choose a reason for hiding this comment

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

Why was this and other similar changes necessary? Did cargo fmt, or cargo clippy, or rustc complain about that?

Comment on lines +961 to +985
let result = T::deserialize(value.clone()).map_err(|_| match value {
serde_value::Value::Bool(b) => b.to_string(),
serde_value::Value::U8(u) => u.to_string(),
serde_value::Value::U16(u) => u.to_string(),
serde_value::Value::U32(u) => u.to_string(),
serde_value::Value::U64(u) => u.to_string(),
serde_value::Value::I8(i) => i.to_string(),
serde_value::Value::I16(i) => i.to_string(),
serde_value::Value::I32(i) => i.to_string(),
serde_value::Value::I64(i) => i.to_string(),
serde_value::Value::F32(f) => f.to_string(),
serde_value::Value::F64(f) => f.to_string(),
serde_value::Value::Char(c) => c.to_string(),
serde_value::Value::String(s) => s,
serde_value::Value::Unit => String::new(),
serde_value::Value::Option(opt) => {
format!("{:?}", opt)
}
serde_value::Value::Newtype(nt) => {
format!("{:?}", nt)
}
serde_value::Value::Seq(seq) => format!("{:?}", seq),
serde_value::Value::Map(map) => format!("{:?}", map),
serde_value::Value::Bytes(v) => String::from_utf8_lossy(&v).into_owned(),
});
Copy link
Owner

Choose a reason for hiding this comment

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

It looks like we can achieve the same result using either the deserialisation to serde_json::Value or implementing all these functions manually instead of using (and relying) on an old and, possibly, unmaintained crate such as serve-value. I also think that the less unreasonable dependencies there are in the dependency tree, the better it is for many various reasons. Not that I am strictly opposed to having it, I just don't (yet) see enough value in it.

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.

2 participants