Skip to content

Commit 95c8b5a

Browse files
authored
Merge pull request #1 from bug-ops/phase-5-performance-optimizations
chore: fix clippy warnings and update MSRV to 1.88
2 parents 213caff + bc51695 commit 95c8b5a

File tree

20 files changed

+181
-146
lines changed

20 files changed

+181
-146
lines changed

.github/workflows/ci.yml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ jobs:
4747
run: cargo +nightly fmt --all -- --check
4848

4949
- name: Clippy
50-
run: cargo clippy --all-targets --all-features -- -D warnings
50+
run: cargo +stable clippy --all-targets --all-features -- -D warnings
5151

5252
- name: Check documentation
5353
run: cargo doc --no-deps --all-features
@@ -97,7 +97,7 @@ jobs:
9797
fail-fast: false
9898
matrix:
9999
os: [ubuntu-latest, macos-latest, windows-latest]
100-
node: [18, 20, 22]
100+
node: [20, 22]
101101

102102
steps:
103103
- uses: actions/checkout@v4
@@ -162,24 +162,24 @@ jobs:
162162

163163
# MSRV check
164164
msrv:
165-
name: Check MSRV (1.86.0)
165+
name: Check MSRV (1.88.0)
166166
runs-on: ubuntu-latest
167167
timeout-minutes: 15
168168
steps:
169169
- uses: actions/checkout@v4
170170

171-
- name: Install Rust 1.86.0
171+
- name: Install Rust 1.88.0
172172
uses: dtolnay/rust-toolchain@master
173173
with:
174-
toolchain: "1.86.0"
174+
toolchain: "1.88.0"
175175

176176
- name: Cache Cargo
177177
uses: Swatinem/rust-cache@v2
178178
with:
179179
shared-key: "msrv"
180180

181181
- name: Check with MSRV
182-
run: cargo +1.86.0 check --all-features
182+
run: cargo +1.88.0 check --all-features
183183

184184
# All checks passed gate
185185
ci-success:

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ resolver = "2"
55
[workspace.package]
66
version = "0.1.0"
77
edition = "2024"
8-
rust-version = "1.86.0"
8+
rust-version = "1.88.0"
99
authors = ["bug-ops"]
1010
license = "MIT OR Apache-2.0"
1111
repository = "https://github.com/bug-ops/feedparser-rs"

crates/feedparser-rs-core/benches/parsing.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
#![allow(missing_docs)]
2+
13
use criterion::{BenchmarkId, Criterion, criterion_group, criterion_main};
24
use feedparser_rs_core::parse;
35
use std::hint::black_box;
@@ -10,7 +12,7 @@ fn bench_parse_feeds(c: &mut Criterion) {
1012
let mut group = c.benchmark_group("parse");
1113

1214
group.bench_with_input(BenchmarkId::new("rss", "small"), &SMALL_FEED, |b, data| {
13-
b.iter(|| parse(black_box(data)))
15+
b.iter(|| parse(black_box(data)));
1416
});
1517

1618
group.bench_with_input(
@@ -20,7 +22,7 @@ fn bench_parse_feeds(c: &mut Criterion) {
2022
);
2123

2224
group.bench_with_input(BenchmarkId::new("rss", "large"), &LARGE_FEED, |b, data| {
23-
b.iter(|| parse(black_box(data)))
25+
b.iter(|| parse(black_box(data)));
2426
});
2527

2628
group.finish();
Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,6 @@
1-
/// Compatibility utilities for feedparser API
2-
///
3-
/// This module provides utilities to ensure API compatibility with
4-
/// Python's feedparser library.
1+
// Compatibility utilities for feedparser API
2+
//
3+
// This module provides utilities to ensure API compatibility with
4+
// Python's feedparser library.
55

66
// TODO: Implement in later phases as needed
7-
8-
#[cfg(test)]
9-
mod tests {
10-
#[test]
11-
fn test_placeholder() {
12-
// Placeholder test
13-
assert!(true);
14-
}
15-
}

crates/feedparser-rs-core/src/error.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,14 @@ mod tests {
6767
}
6868

6969
#[test]
70+
#[allow(clippy::unnecessary_wraps)]
7071
fn test_result_type() {
71-
let result: Result<i32> = Ok(42);
72-
assert_eq!(result.unwrap(), 42);
72+
fn get_result() -> Result<i32> {
73+
Ok(42)
74+
}
75+
let result = get_result();
76+
assert!(result.is_ok());
77+
assert_eq!(result.expect("should be ok"), 42);
7378

7479
let error: Result<i32> = Err(FeedError::Unknown("test".to_string()));
7580
assert!(error.is_err());

crates/feedparser-rs-core/src/parser/atom.rs

Lines changed: 18 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -101,16 +101,15 @@ fn parse_feed_element(
101101
match reader.read_event_into(&mut buf) {
102102
Ok(event @ (Event::Start(_) | Event::Empty(_))) => {
103103
let is_empty = matches!(event, Event::Empty(_));
104-
let e = match &event {
105-
Event::Start(e) | Event::Empty(e) => e,
106-
_ => unreachable!(),
104+
let (Event::Start(e) | Event::Empty(e)) = &event else {
105+
unreachable!()
107106
};
108107

109108
*depth += 1;
110109
if *depth > limits.max_nesting_depth {
111110
return Err(FeedError::InvalidFormat(format!(
112-
"XML nesting depth {} exceeds maximum {}",
113-
depth, limits.max_nesting_depth
111+
"XML nesting depth {depth} exceeds maximum {}",
112+
limits.max_nesting_depth
114113
)));
115114
}
116115

@@ -153,7 +152,7 @@ fn parse_feed_element(
153152
b"author" if !is_empty => {
154153
if let Ok(person) = parse_person(reader, &mut buf, limits, depth) {
155154
if feed.feed.author.is_none() {
156-
feed.feed.author = person.name.clone();
155+
feed.feed.author.clone_from(&person.name);
157156
feed.feed.author_detail = Some(person.clone());
158157
}
159158
feed.feed
@@ -200,7 +199,7 @@ fn parse_feed_element(
200199
feed.bozo = true;
201200
feed.bozo_exception =
202201
Some(format!("Entry limit exceeded: {}", limits.max_entries));
203-
skip_element(reader, &mut buf, limits, depth)?;
202+
skip_element(reader, &mut buf, limits, *depth)?;
204203
*depth = depth.saturating_sub(1);
205204
continue;
206205
}
@@ -215,7 +214,7 @@ fn parse_feed_element(
215214
}
216215
_ => {
217216
if !is_empty {
218-
skip_element(reader, &mut buf, limits, depth)?;
217+
skip_element(reader, &mut buf, limits, *depth)?;
219218
}
220219
}
221220
}
@@ -246,16 +245,15 @@ fn parse_entry(
246245
match reader.read_event_into(buf) {
247246
Ok(event @ (Event::Start(_) | Event::Empty(_))) => {
248247
let is_empty = matches!(event, Event::Empty(_));
249-
let e = match &event {
250-
Event::Start(e) | Event::Empty(e) => e,
251-
_ => unreachable!(),
248+
let (Event::Start(e) | Event::Empty(e)) = &event else {
249+
unreachable!()
252250
};
253251

254252
*depth += 1;
255253
if *depth > limits.max_nesting_depth {
256254
return Err(FeedError::InvalidFormat(format!(
257-
"XML nesting depth {} exceeds maximum {}",
258-
depth, limits.max_nesting_depth
255+
"XML nesting depth {depth} exceeds maximum {}",
256+
limits.max_nesting_depth
259257
)));
260258
}
261259

@@ -307,7 +305,7 @@ fn parse_entry(
307305
b"author" if !is_empty => {
308306
if let Ok(person) = parse_person(reader, buf, limits, depth) {
309307
if entry.author.is_none() {
310-
entry.author = person.name.clone();
308+
entry.author.clone_from(&person.name);
311309
entry.author_detail = Some(person.clone());
312310
}
313311
entry.authors.try_push_limited(person, limits.max_authors);
@@ -338,7 +336,7 @@ fn parse_entry(
338336
}
339337
_ => {
340338
if !is_empty {
341-
skip_element(reader, buf, limits, depth)?;
339+
skip_element(reader, buf, limits, *depth)?;
342340
}
343341
}
344342
}
@@ -414,7 +412,7 @@ fn parse_person(
414412
b"name" => name = Some(read_text(reader, buf, limits)?),
415413
b"email" => email = Some(read_text(reader, buf, limits)?),
416414
b"uri" => uri = Some(read_text(reader, buf, limits)?),
417-
_ => skip_element(reader, buf, limits, depth)?,
415+
_ => skip_element(reader, buf, limits, *depth)?,
418416
}
419417
*depth = depth.saturating_sub(1);
420418
}
@@ -517,15 +515,14 @@ fn parse_atom_source(
517515
if let Some(l) = Link::from_attributes(
518516
element.attributes().flatten(),
519517
limits.max_attribute_length,
520-
) {
521-
if link.is_none() {
522-
link = Some(l.href);
523-
}
518+
) && link.is_none()
519+
{
520+
link = Some(l.href);
524521
}
525522
skip_to_end(reader, buf, b"link")?;
526523
}
527524
b"id" => id = Some(read_text(reader, buf, limits)?),
528-
_ => skip_element(reader, buf, limits, depth)?,
525+
_ => skip_element(reader, buf, limits, *depth)?,
529526
}
530527
*depth = depth.saturating_sub(1);
531528
}

crates/feedparser-rs-core/src/parser/common.rs

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ pub struct ParseContext<'a> {
3636

3737
impl<'a> ParseContext<'a> {
3838
/// Create a new parse context from raw data
39+
#[allow(dead_code)]
3940
pub fn new(data: &'a [u8], limits: ParserLimits) -> Result<Self> {
4041
limits
4142
.check_feed_size(data.len())
@@ -54,6 +55,7 @@ impl<'a> ParseContext<'a> {
5455

5556
/// Check and increment depth, returning error if limit exceeded
5657
#[inline]
58+
#[allow(dead_code)]
5759
pub fn check_depth(&mut self) -> Result<()> {
5860
self.depth += 1;
5961
if self.depth > self.limits.max_nesting_depth {
@@ -67,18 +69,20 @@ impl<'a> ParseContext<'a> {
6769

6870
/// Decrement depth safely
6971
#[inline]
70-
pub fn decrement_depth(&mut self) {
72+
#[allow(dead_code)]
73+
pub const fn decrement_depth(&mut self) {
7174
self.depth = self.depth.saturating_sub(1);
7275
}
7376

7477
/// Clear the buffer
7578
#[inline]
79+
#[allow(dead_code)]
7680
pub fn clear_buf(&mut self) {
7781
self.buf.clear();
7882
}
7983
}
8084

81-
/// Initialize a ParsedFeed with common setup for any format
85+
/// Initialize a `ParsedFeed` with common setup for any format
8286
#[inline]
8387
pub fn init_feed(version: FeedVersion, max_entries: usize) -> ParsedFeed {
8488
let mut feed = ParsedFeed::with_capacity(max_entries);
@@ -89,15 +93,14 @@ pub fn init_feed(version: FeedVersion, max_entries: usize) -> ParsedFeed {
8993

9094
/// Check nesting depth and return error if exceeded
9195
///
92-
/// This is a standalone helper for parsers that don't use ParseContext.
93-
/// Future use: Will be used when ParseContext is adopted project-wide
96+
/// This is a standalone helper for parsers that don't use `ParseContext`.
97+
/// Future use: Will be used when `ParseContext` is adopted project-wide
9498
#[inline]
9599
#[allow(dead_code)]
96100
pub fn check_depth(depth: usize, max_depth: usize) -> Result<()> {
97101
if depth > max_depth {
98102
return Err(FeedError::InvalidFormat(format!(
99-
"XML nesting depth {} exceeds maximum {}",
100-
depth, max_depth
103+
"XML nesting depth {depth} exceeds maximum {max_depth}"
101104
)));
102105
}
103106
Ok(())
@@ -109,10 +112,10 @@ pub fn check_depth(depth: usize, max_depth: usize) -> Result<()> {
109112
/// is valid UTF-8, falling back to lossy conversion otherwise.
110113
#[inline]
111114
pub fn bytes_to_string(value: &[u8]) -> String {
112-
match std::str::from_utf8(value) {
113-
Ok(s) => s.to_string(),
114-
Err(_) => String::from_utf8_lossy(value).into_owned(),
115-
}
115+
std::str::from_utf8(value).map_or_else(
116+
|_| String::from_utf8_lossy(value).into_owned(),
117+
std::string::ToString::to_string,
118+
)
116119
}
117120

118121
/// Read text content from current XML element (handles text and CDATA)
@@ -160,15 +163,15 @@ pub fn skip_element(
160163
reader: &mut Reader<&[u8]>,
161164
buf: &mut Vec<u8>,
162165
limits: &ParserLimits,
163-
current_depth: &mut usize,
166+
current_depth: usize,
164167
) -> Result<()> {
165168
let mut local_depth: usize = 1;
166169

167170
loop {
168171
match reader.read_event_into(buf) {
169172
Ok(Event::Start(_)) => {
170173
local_depth += 1;
171-
if *current_depth + local_depth > limits.max_nesting_depth {
174+
if current_depth + local_depth > limits.max_nesting_depth {
172175
return Err(FeedError::InvalidFormat(format!(
173176
"XML nesting depth exceeds maximum of {}",
174177
limits.max_nesting_depth
@@ -278,7 +281,7 @@ mod tests {
278281
reader.config_mut().trim_text(true);
279282
let mut buf = Vec::new();
280283
let limits = ParserLimits::default();
281-
let mut depth = 1;
284+
let depth = 1;
282285

283286
// Skip to after the start tag
284287
loop {
@@ -291,7 +294,7 @@ mod tests {
291294
}
292295
buf.clear();
293296

294-
let result = skip_element(&mut reader, &mut buf, &limits, &mut depth);
297+
let result = skip_element(&mut reader, &mut buf, &limits, depth);
295298
assert!(result.is_ok());
296299
}
297300
}

crates/feedparser-rs-core/src/parser/detect.rs

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -60,14 +60,14 @@ fn detect_json_feed_version(data: &[u8]) -> FeedVersion {
6060
}
6161

6262
// Try to parse as JSON and check version field
63-
if let Ok(json) = serde_json::from_slice::<serde_json::Value>(data) {
64-
if let Some(version) = json.get("version").and_then(|v| v.as_str()) {
65-
return match version {
66-
"https://jsonfeed.org/version/1" => FeedVersion::JsonFeed10,
67-
"https://jsonfeed.org/version/1.1" => FeedVersion::JsonFeed11,
68-
_ => FeedVersion::Unknown,
69-
};
70-
}
63+
if let Ok(json) = serde_json::from_slice::<serde_json::Value>(data)
64+
&& let Some(version) = json.get("version").and_then(|v| v.as_str())
65+
{
66+
return match version {
67+
"https://jsonfeed.org/version/1" => FeedVersion::JsonFeed10,
68+
"https://jsonfeed.org/version/1.1" => FeedVersion::JsonFeed11,
69+
_ => FeedVersion::Unknown,
70+
};
7171
}
7272
FeedVersion::Unknown
7373
}
@@ -209,7 +209,7 @@ mod tests {
209209

210210
#[test]
211211
fn test_detect_atom10_no_xmlns() {
212-
let xml = br#"<feed></feed>"#;
212+
let xml = br"<feed></feed>";
213213
assert_eq!(detect_format(xml), FeedVersion::Atom10);
214214
}
215215

@@ -233,7 +233,7 @@ mod tests {
233233

234234
#[test]
235235
fn test_detect_unknown_xml() {
236-
let xml = br#"<unknown></unknown>"#;
236+
let xml = br"<unknown></unknown>";
237237
assert_eq!(detect_format(xml), FeedVersion::Unknown);
238238
}
239239

0 commit comments

Comments
 (0)