-
Notifications
You must be signed in to change notification settings - Fork 0
ts/address quality audit #5
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
base: main
Are you sure you want to change the base?
Changes from all commits
61acc18
890fb4e
941d8ef
64edde1
f057678
4029c1f
73dae07
54a4468
6a0a428
dd1bac3
fbb9f19
6f6342f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -21,3 +21,12 @@ jobs: | |
| components: rustfmt, clippy | ||
| - uses: Swatinem/rust-cache@v2 | ||
| - run: make ci | ||
| - name: Install cargo-llvm-cov | ||
| uses: taiki-e/install-action@cargo-llvm-cov | ||
| - name: Generate coverage report | ||
| run: cargo llvm-cov --all-features --lcov --output-path lcov.info | ||
| - name: Upload coverage reports to Codecov | ||
| uses: codecov/codecov-action@v5 | ||
| with: | ||
| token: ${{ secrets.CODECOV_TOKEN }} | ||
| slug: tcrypt25519/fuzzy_date | ||
|
Comment on lines
+28
to
+32
|
||
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,30 @@ | ||||||||||
| # Contributing | ||||||||||
|
|
||||||||||
| Thanks for your interest in contributing to `fuzzy_date`. | ||||||||||
|
|
||||||||||
| ## Style | ||||||||||
|
|
||||||||||
| - Format with `cargo fmt` before committing (`make fmt`). | ||||||||||
| - Fix all clippy warnings — the project runs clippy at `deny` level (`make clippy`). | ||||||||||
| - Add or update rustdoc comments for any public items you add or change. | ||||||||||
| - Keep changes focused; one logical change per commit/PR. | ||||||||||
|
|
||||||||||
| ## Tests | ||||||||||
|
|
||||||||||
| - Every bug fix should include a regression test. | ||||||||||
| - Every new parsing rule or API surface should have unit tests. | ||||||||||
| - Run the full suite before opening a PR: `make ci` (format-check + clippy + tests + docs). | ||||||||||
|
|
||||||||||
| ## Opening a PR | ||||||||||
|
|
||||||||||
| 1. Fork the repository and create a feature branch from `main`. | ||||||||||
| 2. Run `make ci` locally and confirm it passes. | ||||||||||
| 3. Open a PR against `main` with a short description of what and why. | ||||||||||
|
|
||||||||||
| ## Expectations | ||||||||||
|
|
||||||||||
| - Keep it small: PRs that are easier to review get merged faster. | ||||||||||
| - The crate has no runtime dependencies beyond `serde` and `thiserror`; please | ||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The list of runtime dependencies is incomplete. The project also depends on
Suggested change
|
||||||||||
| avoid introducing new ones without discussion. | ||||||||||
|
Comment on lines
+27
to
+28
|
||||||||||
| - The crate has no runtime dependencies beyond `serde` and `thiserror`; please | |
| avoid introducing new ones without discussion. | |
| - Runtime dependencies are currently limited to `serde`, `thiserror`, and `derive_more`; | |
| please avoid introducing new ones without discussion. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -15,10 +15,13 @@ doctest: ## Run doc tests | |
| .PHONY: test-all | ||
| test-all: test doctest ## Run all tests including doctests | ||
|
|
||
| .PHONY: check | ||
| check: ## Run Cargo check | ||
| .PHONY: cargo-check | ||
| cargo-check: ## Run cargo check (type-check without linking) | ||
| cargo check --all-targets --all-features | ||
|
|
||
| .PHONY: check | ||
| check: fmt-check clippy test-all doc ## Run fmt-check, clippy, all tests, and docs in one command | ||
|
|
||
|
Comment on lines
+22
to
+24
|
||
| .PHONY: fmt | ||
| fmt: ## Format code | ||
| cargo fmt --all | ||
|
|
@@ -35,8 +38,12 @@ clippy: ## Run clippy | |
| doc: ## Build documentation | ||
| RUSTDOCFLAGS="-D warnings" cargo doc --no-deps --all-features | ||
|
|
||
| .PHONY: coverage | ||
| coverage: ## Generate HTML coverage report with cargo-llvm-cov (install: cargo install cargo-llvm-cov) | ||
| cargo llvm-cov --all-features --open | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Using the |
||
|
|
||
| .PHONY: ci | ||
| ci: fmt-check clippy test-cargo ## Run all CI checks | ||
| ci: fmt-check clippy test-cargo doc ## Run all CI checks | ||
|
|
||
| .PHONY: clippy-fix | ||
| clippy-fix: ## Run clippy with automatic fixes | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -2,6 +2,14 @@ | |||||
|
|
||||||
| A Rust crate for dates with varying precision: year-only, year+month, or full date. | ||||||
|
|
||||||
| ## Quick Start | ||||||
|
|
||||||
| 1. Add the dependency: `fuzzy_date = "0.1"` in `[dependencies]`. | ||||||
| 2. Parse a string: `"2026-02".parse::<FuzzyDate>()?` | ||||||
| 3. Query bounds: `.lower_bound()` and `.upper_bound_inclusive()` return `(year, month, day)` tuples. | ||||||
| 4. Check containment: `month.contains(&day_date)`. | ||||||
|
||||||
| 4. Check containment: `month.contains(&day_date)`. | |
| 4. Check containment: `month.contains(&day)`. |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -19,3 +19,34 @@ helpers for lower/upper bounds. | |||||||||||||||||
|
|
||||||||||||||||||
| - `cargo test` to run the included tests. | ||||||||||||||||||
| - Use `FuzzyDate::from_str("1991-08")?` or `"08/1991"`, etc. | ||||||||||||||||||
|
|
||||||||||||||||||
| ## Error Behavior | ||||||||||||||||||
|
|
||||||||||||||||||
| `FromStr` returns `ParseError`, an enum with these variants: | ||||||||||||||||||
|
|
||||||||||||||||||
| | Variant | When raised | | ||||||||||||||||||
| |----------------------------------|----------------------------------------------------------| | ||||||||||||||||||
| | `EmptyInput` | Input is empty or all whitespace | | ||||||||||||||||||
| | `InvalidFormat(String)` | Unrecognized structure, mixed delimiters, non-digit tokens | | ||||||||||||||||||
| | `InvalidYear(u16)` | Year is 0 or exceeds 9999 | | ||||||||||||||||||
|
Comment on lines
+30
to
+31
|
||||||||||||||||||
| | `InvalidFormat(String)` | Unrecognized structure, mixed delimiters, non-digit tokens | | |
| | `InvalidYear(u16)` | Year is 0 or exceeds 9999 | | |
| | `InvalidFormat(String)` | Unrecognized structure, mixed delimiters, non-digit tokens, or numeric overflow during parsing | | |
| | `InvalidYear(u16)` | Parsed year is 0 or exceeds 9999 (within `u16`; overflow is reported as `InvalidFormat`) | |
Copilot
AI
Feb 22, 2026
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.
This section says parsing performs "no allocation beyond the error message", but FromStr currently allocates on successful ISO parses by collecting trimmed.split('-') into a Vec<&str> in src/lib.rs (around the parts: Vec<&str> = ...collect() line). Either update the spec to reflect this, or change the implementation to avoid the allocation (e.g., parse via the split iterator without collecting).
| value. Parsing is pure computation: no I/O, no allocation beyond the error message. | |
| value. Parsing is pure computation: no I/O, and allocations are limited and primarily for diagnostics. |
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.
The claim that parsing involves "no allocation beyond the error message" is not entirely accurate for all formats. While the slash-delimited parser is optimized to avoid allocations, the ISO format parser (e.g., for YYYY-MM-DD) currently uses split().collect::<Vec<_>>(), which performs a heap allocation on successful parses.
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.
@copilot Update the ISO parsing routine to work the same way as the slash format routine now works. Or as close as you can get to it. With the goal being to minimize allocations.
While the slash-delimited parser is optimized to avoid allocations, the ISO format parser (e.g., for YYYY-MM-DD) currently uses split().collect::<Vec<_>>(), which performs a heap allocation on successful parses.
Copilot
AI
Feb 22, 2026
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.
The dependency-direction statement here doesn't reflect the actual module relationships: both types and range import items from the crate root (ParseError in types.rs, FuzzyDate/ParseError in range.rs). Consider revising this section to describe the real layering (e.g., types/consts are low-level, while range depends on FuzzyDate and thus the crate root).
| Dependency direction is strictly one-way: `lib` and `range` import from `types` and | |
| `consts`; no leaf module imports from `lib` or `range`. This keeps compilation | |
| incremental and prevents circular dependencies. | |
| In practice, `types` and `consts` form the low-level layer with no dependencies on other | |
| crate modules. The crate root (`lib`) defines `FuzzyDate`, `ParseError`, and other traits | |
| on top of them, and `range` builds `FuzzyDateRange` on `FuzzyDate`/`ParseError`, depending | |
| on both the crate root and the low-level modules. This keeps the layering clear and helps | |
| avoid circular dependencies in the implementation. |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -315,6 +315,20 @@ impl FuzzyDate { | |||||||||||||||||||||||
| /// Uses a strict byte-by-byte walk: month and day fields are at most 2 ASCII | ||||||||||||||||||||||||
| /// digits; any other byte in those positions is an immediate error. The year | ||||||||||||||||||||||||
| /// field consumes all remaining bytes, which must all be ASCII digits. | ||||||||||||||||||||||||
| /// | ||||||||||||||||||||||||
| /// # Why byte-by-byte instead of split('/')? | ||||||||||||||||||||||||
| /// | ||||||||||||||||||||||||
| /// The two slash formats are structurally ambiguous at the second field: `MM/DD/YYYY` | ||||||||||||||||||||||||
| /// has a 1-2 digit day followed by another `/`, while `MM/YYYY` has the year there | ||||||||||||||||||||||||
| /// with no second `/`. A simple `split('/')` gives 2 or 3 parts, but that forces | ||||||||||||||||||||||||
| /// a heap allocation and still requires per-field length checks. | ||||||||||||||||||||||||
| /// | ||||||||||||||||||||||||
| /// The byte walker resolves the ambiguity without allocating by peeking ahead: | ||||||||||||||||||||||||
|
Comment on lines
+323
to
+326
|
||||||||||||||||||||||||
| /// with no second `/`. A simple `split('/')` gives 2 or 3 parts, but that forces | |
| /// a heap allocation and still requires per-field length checks. | |
| /// | |
| /// The byte walker resolves the ambiguity without allocating by peeking ahead: | |
| /// with no second `/`. A simple approach like `split('/')` and collecting into a | |
| /// `Vec` gives 2 or 3 owned parts, which requires a heap allocation for the | |
| /// collection and still requires per-field length checks (even though `str::split` | |
| /// itself is allocation-free). | |
| /// | |
| /// The byte walker resolves the ambiguity while avoiding these allocations by | |
| /// peeking ahead: |
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.
cargo llvm-covtypically requires the Rustllvm-tools-previewcomponent to be installed via rustup. The workflow installscargo-llvm-covbut doesn't explicitly installllvm-tools-preview; if the install action doesn't handle it, the coverage step may fail. Consider adding arustup component add llvm-tools-previewstep (or documenting why it's unnecessary here).