-
Notifications
You must be signed in to change notification settings - Fork 533
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
Converting the API to return Results #1049
Comments
Error typesAs a start I'll propose an pub enum ChronoError {
InvalidParameter, // example: hour == 25
Overflow, // example: year = 500_000
DoesNotExist, // example: 31 February
InconsistentDate(NaiveDate), // example: parsing Sunday 2023-04-21 (should be Friday)
ParsingError(ParsingError), // collect all other reasons for failing to parse something in a nested enum.
// Maybe:
TzLoadingError(/* todo */), // Catchall for all errors caused by the OS in `offset::local`
} And a specialized type for errors caused by timezone conversion: pub enum LocalResult<T> {
Single(T),
Ambiguous(T, T),
InGap(T, T),
Error(ChronoError)
} |
Goals
Pleasant to useThe deprecations of many potentially panicking methods in 0.4.23 have made chrono very unpleasant to use, with Many edge conditions may simply never arise in real-world use. Making every method that might error in some obscure situation return Example: See #815 (comment) Allow use without panicsFor every API that may panic, there should be an equivalent that is guaranteed to never panic (and otherwise that is a bug). Example: Errors should be actionable.The prime example is Similarly I would want to know the best guess of a date considered inconsistend while parsing (the And maybe |
Failure conditions in current/proposed APIOkay, this doesn't match the current API completely. And it certainly doesn't guarantee all error conditions I would expect are actually handled correctly. NaiveDateTime
NaiveDate
NaiveTime
DateTime
Datelike
Timelike
LocalResult
|
Based on the API above, I would say that methods that can only fail because of Example 1
It should return a
Example 2
Example 3
This is better than returning Example 4
For It should probably document there is a workaround to handle the error for datetime.timezone().from_local(datetime.naive_local().with_minute(val).unwrap()).latest_or_nearest(); Maybe splitting the |
I think that point strongly depends on how exactly you use(d) chrono in your own package. Maybe Case 1) Always unwrap and risk panics.
Case 2) Manually map the error or handle it.
Case 3: Implement
I'm in favor of the third approach as it is very pleasant to use (once you strictly stick to it) and does not panic even in "unlikely cases". Functions that logically cannot panic, should be safe to unwrap within the function itself and shall not return |
@Zomtir We then agree on |
Parsing errorsCurrent error enum for parsing errors: pub enum ParseErrorKind {
/// Given field is out of permitted range.
OutOfRange,
/// There is no possible date and time value with given set of fields.
///
/// This does not include the out-of-range conditions, which are trivially invalid.
/// It includes the case that there are one or more fields that are inconsistent to each other.
Impossible,
/// Given set of fields is not enough to make a requested date and time value.
///
/// Note that there *may* be a case that given fields constrain the possible values so much
/// that there is a unique possible value. Chrono only tries to be correct for
/// most useful sets of fields however, as such constraint solving can be expensive.
NotEnough,
/// The input string has some invalid character sequence for given formatting items.
Invalid,
/// The input string has been prematurely ended.
TooShort,
/// All formatting items have been read but there is a remaining input.
TooLong,
/// There was an error on the formatting string, or there were non-supported formating items.
BadFormat,
// TODO: Change this to `#[non_exhaustive]` (on the enum) with the next breaking release.
#[doc(hidden)]
__Nonexhaustive,
} ParsingError(usize)In my opinion it would be okay, or even better, if the variants
It may be helpful to indicate the index of the parsing error in the string, although I realize that is not easily possible with the current implementation. Is this losing important information compared to the current errors? Honestly I can image are rare case were the current Intermediate result instead of
|
@pitdicker I have no strong preference either way. I don't mind the additional effort to unwrap any I really like your thoughts about the ParsingError. I took a tour around that issue and kept the existing logic without much afterthought. Unfortunately I think enums cannot take additional parameters like |
My knowledge of error handling in Rust is a couple of years out of date. But as I understand it error chaining remains unavailable without the standard library or Timezone-related errorsThere is currently an error type with 16 variants in pub(crate) enum Error {
/// Date time error
// DateTime(&'static str),
/// Local time type search error
FindLocalTimeType(&'static str),
/// Local time type error
LocalTimeType(&'static str),
/// Invalid slice for integer conversion
InvalidSlice(&'static str),
/// Invalid Tzif file
InvalidTzFile(&'static str),
/// Invalid TZ string
InvalidTzString(&'static str),
/// I/O error
Io(io::Error),
/// Out of range error
OutOfRange(&'static str),
/// Integer parsing error
ParseInt(ParseIntError),
/// Date time projection error
ProjectDateTime(&'static str),
/// System time error
//SystemTime(SystemTimeError),
/// Time zone error
TimeZone(&'static str),
/// Transition rule error
TransitionRule(&'static str),
/// Unsupported Tzif file
UnsupportedTzFile(&'static str),
/// Unsupported TZ string
UnsupportedTzString(&'static str),
/// UTF-8 error
Utf8(Utf8Error),
} The first thing to note is that it wraps whatever error may arise. This is appropriate for application code, but not usual for a library. And it is currently never exposed 😄. Maybe a method like How much should be reported? On Unix it is possible to override the local timezone with the In theory the OS-provided timezone information can be wrong. It may have offsets that are non-sensical. An API call may fail. On Unix the timezone database may be missing. Or a file in that database may be corrupt. If a tzinfo file is corrupt, I would say you don't need to know what is corrupt about it. And the current errors For OS error codes we may want to take a page out of the book of New iteration on ChronoErrorpub enum ChronoError {
InvalidParameter, // example: hour == 25
OutOfRange, // example: year = 500_000
DoesNotExist, // example: 31 February
Inconsistent, // example: parsing Sunday 2023-04-21 (should be Friday)
ParsingError(usize), // with byte index of parsing failure
InvalidTzOverride, // example: invalid TZ environment variable
InvalidTzFile,
OsError(i32),
} |
That was very reasonable to explore were and how the API should change. And now we can look from the other side: what errors should we expect? But I am only looking at the documentation, and haven't tried to touch the code yet. You have, so you opinion is useful.
If the result is not ignored or passed on, pattern matching would be easiest: |
Making the error implementation private?One option is to wrap the error enum inside a newtype In my opinion, with an error enum a simple as the one above, this would only add unnecessary boilerplate. Optimize error type for sizeParts of chrono have been written with performance in mind. It would be unfortunate if the addition of a complex error type would regress this. Benchmarks would have to tell, but I don't think this is really a risk if the error type stays small; similar or smaller in size than types like We could shrink the size of the error enum a little: This would put the size of the error enum at just 8 bytes. New iteration on ChronoError#[non_exhaustive]
pub enum ChronoError {
InvalidParameter, // example: hour == 25
OutOfRange, // example: year = 500_000
DoesNotExist, // example: 31 February
Inconsistent, // example: parsing Sunday 2023-04-21 (should be Friday)
ParsingError(u32), // with byte index of parsing failure
InvalidTzOverride, // example: invalid TZ environment variable
InvalidTzFile,
OsError(i32),
} |
If this is internal then yes, I recommend changing it. Specifically, |
What traits will this |
Note that the conversions to and from These are especially insidious. Consider this: fn example(dt: impl Into<DateTime<Utc>>) {
let dt = dt.into();
// …do stuff with `dt`…
} It's not immediately obvious that this function can panic, but it can, because The reverse is also true: the earliest date-time representable by |
The initial PR to add an error type is up #1049! Parsing errorsI've started working on converting the parsing code to the new Overview of parsing
Errors causesConsider the string During parsing we can have four causes for errors:
After parsing we have another set of error causes:
The error causes after parsing can map to Internal error typeI propose the internal methods used during parsing should use their own error type. Then the errors can contain the remaining slice and have a lifetime that depends on the input. At the conversion to our public error type it can be turned into a byte index of the source slice. enum ParseError<'a> {
/// Character does not match with the expected format.
///
/// Contains a reference to the remaining slice, starting with the mismatched character.
///
/// Maps to `Error::InvalidCharacter(index)`.
InvalidCharacter(&'a str),
/// The input is shorter than expected by the format.
///
/// Maps to `Error::InvalidCharacter(input.len())`.
TooShort,
/// Value is not allowed by the format.
///
/// Examples are a number that is larger or smaller than the defined range, or the name of a
/// weekday, month or timezone that doesn't match.
///
/// Contains a reference to the remaining slice, starting with invalid value.
///
/// Maps to `Error::InvalidValue(index)`.
InvalidValue(&'a str),
/// Some of the date or time components are not consistent with each other.
///
/// During parsing this only arises if a field is set for a second time in the `Parsed` type,
/// while not matching the first value.
///
/// Maps to `Error::Inconsistent`.
Inconsistent,
} New iteration on
|
@ahcodedthat We have no documentation yet for |
@pitdicker Ok, I have opened #1421. |
It seems good to write down some of the steps I am planning.
|
This issue seems to have mostly served its purpose. There is still some iteration on the details, so the end result is going to be different from what is discussed here. #1469 tracks the progress of individual methods, and #1448 is split out for errors originating from methods on the |
There are now multiple issues or comments to make chrono 0.5 return
Result
s instead ofOption
s, and panic less. There is some design work that needs to be done first. I don't see any concrete, high-level proposals. Maybe this issue can serve for exploration.See #815
cc @Zomtir, @udoprog
The text was updated successfully, but these errors were encountered: