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

API improvements for usage of owned Elements #858

Merged
merged 13 commits into from
Nov 26, 2024
Merged

Conversation

jpschorr
Copy link
Contributor

@jpschorr jpschorr commented Nov 25, 2024

This PR includes a couple changes intended to make working with owned Elements easier are not require cloning for 'projecting' an element to be replaced by something it contains.

  • Exposes OwnedSequenceIterator and modifies its implementation to clone less
  • Adds try_into_<x> methods similar to as_<x> methods on Element
    • These allow 'partial' conversions of owned Elements with the 'failure' case returning the original Element

Example simplified usage:

#[derive(Debug)]
enum ElementIterator {
    Single(Element),
    Sequence(OwnedSequenceIterator),
}

...

match elt.try_into_sequence() {
    Err(elt) => ElementIterator::Single(elt),
    Ok(seq) => ElementIterator::Sequence(seq.into_iter()),
}

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.

Copy link

codecov bot commented Nov 25, 2024

Codecov Report

Attention: Patch coverage is 85.71429% with 37 lines in your changes missing coverage. Please review.

Project coverage is 77.81%. Comparing base (d6cb3b4) to head (fab335e).
Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
src/element/mod.rs 89.32% 21 Missing and 1 partial ⚠️
src/result/conversion.rs 42.85% 12 Missing ⚠️
src/result/mod.rs 0.00% 3 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #858      +/-   ##
==========================================
+ Coverage   77.55%   77.81%   +0.26%     
==========================================
  Files         135      136       +1     
  Lines       33796    33984     +188     
  Branches    33796    33984     +188     
==========================================
+ Hits        26210    26446     +236     
+ Misses       5645     5598      -47     
+ Partials     1941     1940       -1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@jpschorr jpschorr marked this pull request as ready for review November 25, 2024 20:46
Copy link
Contributor

@zslayton zslayton left a comment

Choose a reason for hiding this comment

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

This looks good, thanks! One question about our error types before we merge this.

src/element/mod.rs Outdated Show resolved Hide resolved
src/element/mod.rs Outdated Show resolved Hide resolved
use crate::{Bytes, Decimal, Int, IonType, Sequence, Str, Struct, Symbol, Timestamp};
use thiserror::Error;

pub type ConversionResult<T> = Result<T, ConversionError>;
Copy link
Contributor

Choose a reason for hiding this comment

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

I think I was expecting Conversion and ConversionResult to be the same type, something like:

pub type ConversionResult<FromType, ToType> = Result<ToType, ConversionError<FromType>>;

impl <FromType, ToType> From<ConversionResult<FromType, ToType>> for IonResult<ToType> {
  fn from(conversion: ConversionResult<FromType, ToType>) -> Self {
    match conversion {
      Ok(value) => Ok(value),
      Err(conversion_error) => {
        Err(IonError::Conversion(conversion_err.message())) // some text explanation
      }
    }
  }
}

This way the try_into_* methods returning a ConversionResult would benefit from all of the Result methods (including ok() for Option conversion). You could then also use ? with ConvesionResult in contexts that needed to return an IonResult, which would be nice.

Could you explain the motivation for distinct Conversion/ConversionResult types?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ConversionError<FromType> can't be put directly into IonError due to the generic.

pub enum IonError {
   ...
    #[error("{0}")]
    Conversion(#[from] ConversionError<FromType>),
   ...
}

So you end up needing two 'error' structs anyway.

I originally had something like:

/// Represents a mismatch during conversion between the expected type and the actual value's type.
#[derive(Clone, Debug, Error, PartialEq)]
#[error("Type conversion error; expected a(n) {output_type}, found a(n) {input_type}")]
pub struct ConversionError {
    input_value: String,
    output_type: String,
}

/// Represents a mismatch during conversion between the expected type and the actual value's type.
#[derive(Clone, Debug, Error, PartialEq)]
#[error("Type conversion error; expected a(n) {output_type}, found a(n) {input_type}")]
pub struct ConversionOperationError<FromType>
where
    FromType: ValueTypeExpectation,
{
    input_value: FromType,
    output_type: String,
}

So, a pair of Errors and a pair of Results. Except the ConversionOperationError/ConversionOperationResult basically never existed for long and wasn't really used as an error; It was either unwrapped for its success or fail element or converted immediately into a ConversionError/ConversionResult. On top of this, I kept confusing myself as to which was which between Conversion<x> and ConversionOperation<x>.

So, I condensed ConversionOperationError&ConversionOperationResult and into Conversion. However, if you find that more surprising and would prefer the pair of Errors/Results or have other structure or naming suggestions, I'm open to change it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've refactored to a pair of Errors & Results.

FromType: ValueTypeExpectation,
ToType: TypeExpectation,
{
input_value: Box<FromType>,
Copy link
Contributor

Choose a reason for hiding this comment

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

Why does this need to be boxed? Are you concerned about stack size?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Without boxing, clippy warns:

warning: the `Err`-variant returned from this function is very large
   --> src/element/mod.rs:492:34
    |
492 |     pub fn try_into_int(self) -> ConversionOperationResult<Element, Int> {
    |                                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the `Err`-variant is at least 128 bytes
    |
    = help: try reducing the size of `result::conversion::ConversionOperationError<element::Element, types::integer::Int>`, for example by boxing large elements or replacing it with `Box<result::conversion::ConversionOperationError<element::Element, types::integer::Int>>`
    = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#result_large_err
    = note: `#[warn(clippy::result_large_err)]` on by default

@jpschorr jpschorr requested a review from zslayton November 26, 2024 20:18
@jpschorr jpschorr merged commit 4e7431f into main Nov 26, 2024
35 checks passed
@jpschorr jpschorr deleted the feat-owned-element-qol branch November 26, 2024 21:25
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.

3 participants