-
Notifications
You must be signed in to change notification settings - Fork 247
Enhance RecurrencePatternSerializer
#758
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
Conversation
Codecov ReportAll modified and coverable lines are covered by tests ✅ ❌ Your project status has failed because the head coverage (67%) is below the target coverage (80%). You can increase the head coverage or adjust the target coverage. @@ Coverage Diff @@
## main #758 +/- ##
===================================
+ Coverage 66% 67% +1%
===================================
Files 104 104
Lines 4592 4519 -73
Branches 1146 1106 -40
===================================
- Hits 3033 3014 -19
+ Misses 1124 1072 -52
+ Partials 435 433 -2
🚀 New features to boost your workflow:
|
|
@minichma TBD: BTW: Just noticed, we can ask Copilot for a review, too :) |
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.
Pull Request Overview
This pull request enhances the RecurrencePatternSerializer by supporting RRULE parts in any order, refactoring methods to reduce cognitive complexity, and separating the deserialization logic for RFC-compliant input and natural language input.
- Updated regex names and helper method extraction to improve parsing flexibility
- Revised processing of key-value pairs and numeric temporal units
- Added comprehensive test cases for validating RRULE parsing in any order
Reviewed Changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
| Ical.Net/Serialization/DataTypes/RecurrencePatternSerializer.cs | Refactored deserialization methods to support order-independent RRULE parts and natural language input |
| Ical.Net/DataTypes/RecurrencePattern.cs | Added documentation for the BySetPosition property |
| Ical.Net.Tests/SerializationTests.cs | Introduced new test cases ensuring RRULE properties are correctly deserialized regardless of order |
Comments suppressed due to low confidence (1)
Ical.Net/Serialization/DataTypes/RecurrencePatternSerializer.cs:421
- [nitpick] Returning false immediately in TryAddNumericTemporalUnit when parsing fails changes the original behavior (which skipped the invalid value). Consider continuing the loop instead to avoid prematurely terminating processing of comma-separated fields.
if (!int.TryParse(match.Groups["Num"].Value, out var num))
|
Didn't look into the actual implementation so far, but generally speaking I think the standard serializers/deserializers should stick to the RFC. So when parsing a calendar from any other format, this should happen fully intentional (not as a fallback from the standard parser) and independently. I therefore agree that it should rather not be an integral part of standard deserialization. If this included as part of Ical.Net, it should be implemented in separate classes that could be used independently. The functionality reminds me of #576. Could this be combined? |
5717fd6 to
191d839
Compare
- RRULE parts are now processed in any order, which is compliant to RFC5545 section 3.3.10. - Refactor methods with high cognitive complexity - Split deserialization of RFC-compliant and natural language input
191d839 to
1000abc
Compare
Yes, it has some similarities. I wonder whether natural langue for recurrences brings much advantage. End users would be better off with a good GUI, while devops might prefer a builder with a fluent API. Anyway, the natural language part is undocumented, untested, and can never be all fallback for RFC-compliance. I'll remove it. |
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.
Pull Request Overview
This PR enhances the RecurrencePatternSerializer to process RRULE parts in any order per RFC5545, reduces cognitive complexity by refactoring methods, and removes natural language input deserialization.
- Refactored deserialization logic using a dedicated FrequencyPattern regex and helper methods
- Removed legacy regex patterns and natural language parsing logic
- Updated tests to verify order-independent RRULE deserialization and recurrence range checks
Reviewed Changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| Ical.Net/Serialization/DataTypes/RecurrencePatternSerializer.cs | Refactored deserialization logic and removed natural language parsing regex definitions |
| Ical.Net/DataTypes/RecurrencePattern.cs | Added XML documentation for the BySetPosition property |
| Ical.Net.Tests/RecurrenceTests.cs | Added tests to validate order independence and recurrence range checks |
Comments suppressed due to low confidence (2)
Ical.Net/Serialization/DataTypes/RecurrencePatternSerializer.cs:179
- [nitpick] The constant 'CiCompiled' is now using PascalCase, which may be inconsistent with the previous naming style for constants. Consider adopting a consistent naming convention (such as all uppercase or a consistent prefix) for constants across the codebase.
private const RegexOptions CiCompiled = RegexOptions.IgnoreCase | RegexOptions.Compiled;
Ical.Net/Serialization/DataTypes/RecurrencePatternSerializer.cs:321
- [nitpick] Consider trimming whitespace from the 'day' string in the AddWeekDays method to prevent potential issues when parsing day names.
byDay.Add(new WeekDay(day));
|
|
||
| private void ProcessKeyValuePair(string key, string value, RecurrencePattern r, ISerializerFactory factory) | ||
| { | ||
| switch (key) |
Copilot
AI
Mar 30, 2025
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.
[nitpick] The switch statement in ProcessKeyValuePair does not include a default case to handle unexpected keys. Adding a default branch (even if only for logging or graceful handling) would improve robustness.
minichma
left a comment
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.
Great, much better! Just a few minor comment.
|
|
||
| private const RegexOptions CiCompiled = RegexOptions.IgnoreCase | RegexOptions.Compiled; | ||
|
|
||
| internal static readonly Regex FrequencyPattern = |
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.
make private?
|
|
||
| internal static readonly Regex FrequencyPattern = | ||
| new Regex(@"FREQ=(SECONDLY|MINUTELY|HOURLY|DAILY|WEEKLY|MONTHLY|YEARLY);?(.*)", CiCompiled, | ||
| RegexDefaults.Timeout); |
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.
I know, this is legacy, but I think, we shouldn't specify a timeout in Regex. Timeouts in Regex are good if the pattern is user-specified, but in our case there is no reason to expect any bad behavior. On the other hand, the timeout could easily cause unexpected behavior. E.g. on a server running close to its limits, a timeout of 200 ms could easily be hit also on well-formed input.
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.
Let's make the timeout 2000 ms, which is unlikely ever to be hit
|
|
||
| private const RegexOptions CiCompiled = RegexOptions.IgnoreCase | RegexOptions.Compiled; | ||
|
|
||
| internal static readonly Regex FrequencyPattern = |
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.
Do we actually need this regex at all (anymore)? The only thing it really does now is validating that a RRULE contains a FREQ=xxx string (e.g. thisIsNotFREQ=DAILY! would work), but that's really not too helpful as either all parts of the string would have to be validated or nothing. The real validation happens throughout the parsing process.
| { | ||
| // Parse the frequency type | ||
| r.Frequency = (FrequencyType) Enum.Parse(typeof(FrequencyType), match.Groups[1].Value, true); | ||
| return TryDeserializeFrequencyPattern(value, r, factory) |
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.
If the pattern can't be parsed, why would we return null? Shouldn't we raise an exception?
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.
Will change to always throw on failure
| } | ||
| else | ||
|
|
||
| CheckMutuallyExclusive("COUNT", "UNTIL", r.Count, r.Until); |
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.
Check frequency is present here, rather than doing the regex upfront.
| if (keywordPair.Length == 0) | ||
| { | ||
| r.Interval = 1; | ||
| // ignore subsequent semi-colons |
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.
.. as well as preceding and duplicate ones
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.
all kinds - will add a unit test
| break; | ||
|
|
||
| case "bysecond": | ||
| AddInt32Values(r.BySecond, value); |
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.
By adding the entry, we accept BYSECOND to appear more than once (same for all 'BY' parts). Legacy though.
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.
won't check
| // Couldn't parse the object, return null! | ||
| r = null; | ||
| case "freq": | ||
| r.Frequency = (FrequencyType) Enum.Parse(typeof(FrequencyType), value, true); |
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.
I guess we should fail if None is specified.
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.
Good point
| case "wkst": | ||
| r.FirstDayOfWeek = GetDayOfWeek(value); | ||
| break; | ||
| } |
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.
Shouldn't we add a default case for unknown/invalid rule parts and fail here?
|
Agree with your comments on existing code. Starting with a bug fix and ending up with a rewrite, while limited time is running by... |
|
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.
Pull Request Overview
This PR enhances the RecurrencePatternSerializer by refactoring its deserialization logic to process RRULE parts in any order in accordance with RFC5545 and by removing natural language deserialization.
- Refactored methods to reduce cognitive complexity and improve maintainability.
- Removed unused regex patterns related to natural language inputs.
- Updated regex timeout constant and added new tests for various RRULE orderings and error cases.
Reviewed Changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| Ical.Net/Serialization/DataTypes/RecurrencePatternSerializer.cs | Refactored deserialization logic, removed natural language regexes, and added helper methods. |
| Ical.Net/DataTypes/RecurrencePattern.cs | Added XML documentation for the BySetPosition property. |
| Ical.Net/Constants.cs | Increased the regex timeout from 200ms to 2000ms. |
| Ical.Net.Tests/RecurrenceTests.cs | Added tests validating RRULE property order, missing FREQ handling, and unsupported parts. |
Comments suppressed due to low confidence (1)
Ical.Net/Constants.cs:345
- Increasing the timeout from 200ms to 2000ms may have performance implications in parts of the system that rely on time-bound regex operations. Confirm that this change is acceptable for all affected components.
public const int TimeoutMilliseconds = 2000;
| System.Diagnostics.Debug.Assert(r != null); | ||
| System.Diagnostics.Debug.Assert(factory != null); | ||
|
|
Copilot
AI
Mar 31, 2025
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.
[nitpick] Using Debug.Assert for null checks may not enforce the condition in Release builds. Consider using an explicit exception or a proper null-check to ensure robust error handling in production.
| System.Diagnostics.Debug.Assert(r != null); | |
| System.Diagnostics.Debug.Assert(factory != null); | |
| if (r == null) | |
| { | |
| throw new InvalidOperationException("Failed to create RecurrencePattern."); | |
| } | |
| if (factory == null) | |
| { | |
| throw new InvalidOperationException("Serializer factory service is not available."); | |
| } |



RecurrencePatternSerializerexpects that theFREQ` is the first in Recurrence Rules #755ArgumentOutOfRangeExceptions