Skip to content

Commit 6937268

Browse files
authored
refactor!: properly handle chained fit file and improve control flow (#9)
1 parent 1dac9aa commit 6937268

File tree

10 files changed

+230
-156
lines changed

10 files changed

+230
-156
lines changed

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

Lines changed: 79 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -19,53 +19,74 @@ Missing features, test completeness, and more robust documentation may be added
1919

2020
#### Decode
2121

22-
Decoder's `decode` allows us to interact with FIT files directly through their original protocol messages' structure.
22+
Decoder's `decode` allows us to interact with FIT files directly through their original protocol messages' structure.
23+
First call will return either Ok(Some(fit)) or Err(err), never Ok(None).
24+
On next call, it may return Ok(None) to indicate that no more FIT sequence in the file.
2325

2426
```rust
25-
use rustyfit::{
26-
Decoder,
27-
profile::{mesgdef, typedef},
28-
};
29-
use std::{fs::File, io::BufReader};
27+
use rustyfit::{Decoder, profile::{mesgdef, typedef}};
28+
use std::{error::Error, fs::File, io::BufReader};
3029

31-
fn main() {
30+
fn main() -> Result<(), Box<dyn Error>> {
3231
let name = "Activity.fit";
33-
let f = File::open(name).unwrap();
32+
let f = File::open(name)?;
3433
let br = BufReader::new(f);
3534
let mut dec = Decoder::new(br);
3635

37-
let fit = dec.decode().unwrap();
36+
let fit = dec.decode()?.unwrap(); // First decode call is either Ok(Some(fit)) or Err(err), never Ok(None).
3837

3938
println!("file_header's data_size: {}", fit.file_header.data_size);
4039
println!("messages count: {}", fit.messages.len());
41-
for field in &fit.messages[0].fields { // first message: file_id
40+
for field in &fit.messages[0].fields {
41+
// first message: file_id
4242
if field.num == mesgdef::FileId::TYPE {
4343
println!("file type: {}", typedef::File(field.value.as_u8()));
4444
}
4545
}
46+
47+
Ok(())
48+
4649
// # Output:
4750
// file_header's data_size: 94080
4851
// messages count: 3611
4952
// file type: activity
5053
}
54+
```
55+
56+
The `decode` method can be invoked multiple times to decode chained FIT file until it return Ok(None) or Err(err).
57+
We can decode chained FIT file using `while let`, e.g:
5158

59+
```rust
60+
while let Some(fit) = dec.decode()? {
61+
println!("file_header's data_size: {}", fit.file_header.data_size);
62+
println!("messages count: {}", fit.messages.len());
63+
for field in &fit.messages[0].fields {
64+
// first message: file_id
65+
if field.num == mesgdef::FileId::TYPE {
66+
println!("file type: {}", typedef::File(field.value.as_u8()));
67+
}
68+
}
69+
}
5270
```
5371

5472
#### Decode with Closure
5573

56-
Decoder's `decode_fn` allow us to retrieve message definition or message data event as soon as it is being decoded. This way, users can have fine-grained control on how to interact with the data.
74+
Decoder's `decode_with` allow us to retrieve event data (FileHeader, MessageDefinition, Message, CRC) as soon as it is being decoded.
75+
This way, users can have fine-grained control on how to interact with the data.
5776

5877
```rust
5978
use rustyfit::{Decoder, DecoderEvent,profile::{mesgdef, typedef}};
60-
use std::{fs::File, io::BufReader};
79+
use std::{error::Error, fs::File, io::BufReader};
6180

62-
fn main() {
81+
fn main() -> Result<(), Box<dyn Error>> {
6382
let name = "Activity.fit";
64-
let f = File::open(name).unwrap();
83+
let f = File::open(name)?;
6584
let br = BufReader::new(f);
6685
let mut dec = Decoder::new(br);
6786

68-
dec.decode_fn(|event| match event {
87+
dec.decode_with(|event| match event {
88+
DecoderEvent::FileHeader(_) => {},
89+
DecoderEvent::MessageDefinition(_) => {},
6990
DecoderEvent::Message(mesg) => {
7091
if mesg.num == typedef::MesgNum::SESSION {
7192
// Convert mesg into Session struct
@@ -76,17 +97,34 @@ fn main() {
7697
);
7798
}
7899
}
79-
DecoderEvent::MessageDefinition(_) => {}
80-
})
81-
.unwrap();
100+
DecoderEvent::Crc(_) => {}
101+
})?;
102+
103+
Ok(())
82104

83105
// # Output
84106
// session:
85107
// start_time: 995749880
86108
// sport: stand_up_paddleboarding
87109
// num_laps: 1
88110
}
111+
```
112+
113+
The `decode_with` method can be invoked multiple times to decode chained FIT file until it return Ok(false) or Err(err).
89114

115+
```rust
116+
while dec.decode_with(|event| {
117+
if let DecoderEvent::Message(mesg) = event {
118+
if mesg.num == typedef::MesgNum::SESSION {
119+
// Convert mesg into Session struct
120+
let ses = mesgdef::Session::from(mesg);
121+
println!(
122+
"session:\n start_time: {}\n sport: {}\n num_laps: {}",
123+
ses.start_time.0, ses.sport, ses.num_laps
124+
);
125+
}
126+
}
127+
})? {}
90128
```
91129

92130
#### DecoderBuilder
@@ -107,25 +145,14 @@ let mut dec: Decoder = DecoderBuilder::new(br)
107145
Here is the example of manually encode FIT protocol using this library to give the idea how it works.
108146

109147
```rust
110-
use std::{
111-
fs::File,
112-
io::{BufWriter, Write},
113-
};
114-
115-
use rustyfit::{
116-
Encoder,
117-
profile::{
118-
ProfileType, mesgdef,
119-
typedef::{self},
120-
},
121-
proto::{FIT, Field, Message, Value},
122-
};
123-
124-
fn main() {
148+
use std::{error::Error, fs::File, io::{BufWriter, Write}};
149+
use rustyfit::{Encoder, profile::{ProfileType, mesgdef, typedef::{self}}, proto::{FIT, Field, Message, Value}};
150+
151+
fn main() -> Result<(), Box<dyn Error>> {
125152
let fout_name = "output.fit";
126-
let fout = File::create(fout_name).unwrap();
127-
let bw = BufWriter::new(fout);
128-
let mut enc = Encoder::new(bw);
153+
let fout = File::create(fout_name)?;
154+
let mut bw = BufWriter::new(fout);
155+
let mut enc = Encoder::new(&mut bw);
129156

130157
let mut fit = FIT {
131158
messages: vec![
@@ -159,7 +186,7 @@ fn main() {
159186
Field {
160187
num: mesgdef::Record::DISTANCE,
161188
profile_type: ProfileType::UINT32,
162-
value: Value::Uint16(100 * 100), // 100 m
189+
value: Value::Uint32(100 * 100), // 100 m
163190
is_expanded: false,
164191
},
165192
Field {
@@ -181,36 +208,26 @@ fn main() {
181208
..Default::default()
182209
};
183210

184-
enc.encode(&mut fit).unwrap();
185-
bw.flush().unwrap();
186-
}
211+
enc.encode(&mut fit)?;
212+
bw.flush()?;
187213

214+
Ok(())
215+
}
188216
```
189217

190218
#### Encode using mesgdef module
191219

192220
Alternatively, users can create messages using the mesgdef module for convenience.
193221

194222
```rust
195-
use std::{
196-
fs::File,
197-
io::{BufWriter, Write},
198-
};
199-
200-
use rustyfit::{
201-
Encoder,
202-
profile::{
203-
mesgdef,
204-
typedef::{self},
205-
},
206-
proto::{FIT, Message},
207-
};
208-
209-
fn main() {
223+
use std::{error::Error, fs::File, io::{BufWriter, Write}};
224+
use rustyfit::{Encoder, profile::{mesgdef, typedef::{self}}, proto::{FIT, Message}};
225+
226+
fn main() -> Result<(), Box<dyn Error>> {
210227
let fout_name = "output.fit";
211-
let fout = File::create(fout_name).unwrap();
212-
let bw = BufWriter::new(fout);
213-
let mut enc = Encoder::new(bw);
228+
let fout = File::create(fout_name)?;
229+
let mut bw = BufWriter::new(fout);
230+
let mut enc = Encoder::new(&mut bw);
214231

215232
let mut fit = FIT {
216233
messages: vec![
@@ -232,10 +249,11 @@ fn main() {
232249
..Default::default()
233250
};
234251

235-
enc.encode(&mut fit).unwrap();
236-
bw.flush().unwrap();
237-
}
252+
enc.encode(&mut fit)?;
253+
bw.flush()?;
238254

255+
Ok(())
256+
}
239257
```
240258

241259
#### EncoderBuilder

rustyfit/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "rustyfit"
3-
version = "0.3.0"
3+
version = "0.4.0"
44
edition = "2024"
55
description = "This project hosts the Rust implementation for The Flexible and Interoperable Data Transfer (FIT) Protocol"
66
authors = ["Hikmatulloh Hari Mukti <muktihaz@gmail.com>"]

rustyfit/benches/decoder.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,20 +25,20 @@ pub fn bench_decode(c: &mut Criterion) {
2525
})
2626
});
2727

28-
c.bench_function("decode fn", |b| {
28+
c.bench_function("decode_with", |b| {
2929
b.iter(|| {
3030
let mut dec = Decoder::new(black_box(Cursor::new(&file_bytes)));
31-
dec.decode_fn(|_| {}).unwrap();
31+
dec.decode_with(|_| {}).unwrap();
3232
})
3333
});
3434

35-
c.bench_function("decode fn no checksum no expand", |b| {
35+
c.bench_function("decode_with no checksum no expand", |b| {
3636
b.iter(|| {
3737
let mut dec = DecoderBuilder::new(black_box(Cursor::new(&file_bytes)))
3838
.checksum(false)
3939
.expand_components(false)
4040
.build();
41-
dec.decode_fn(|_| {}).unwrap();
41+
dec.decode_with(|_| {}).unwrap();
4242
})
4343
});
4444
}

rustyfit/benches/encoder.rs

Lines changed: 32 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -8,37 +8,40 @@ const TEST_FILE: &str = "tests/data/large.fit";
88
pub fn bench_encode(c: &mut Criterion) {
99
let file_bytes = std::fs::read(TEST_FILE).unwrap();
1010
let mut dec = Decoder::new(Cursor::new(&file_bytes));
11-
let mut fit = dec.decode().unwrap();
12-
1311
let mut buf = Vec::<u8>::with_capacity(10_000 >> 10); // 10 MB, large enough to avoid realloc.
1412

15-
c.bench_function("encode default", |b| {
16-
b.iter(|| {
17-
let cur = Cursor::new(&mut buf);
18-
let mut enc = Encoder::new(black_box(cur));
19-
enc.encode(&mut fit).unwrap();
20-
})
21-
});
22-
23-
c.bench_function("encode normal interleave 15", |b| {
24-
b.iter(|| {
25-
let cur = Cursor::new(&mut buf);
26-
let mut enc = EncoderBuilder::new(black_box(cur))
27-
.header_option(HeaderOption::Normal(15))
28-
.build();
29-
enc.encode(&mut fit).unwrap();
30-
})
31-
});
32-
33-
c.bench_function("encode compress interleave 3", |b| {
34-
b.iter(|| {
35-
let cur = Cursor::new(&mut buf);
36-
let mut enc = EncoderBuilder::new(black_box(cur))
37-
.header_option(HeaderOption::Compressed(3))
38-
.build();
39-
enc.encode(&mut fit).unwrap();
40-
})
41-
});
13+
while let Some(fit) = &mut dec.decode().unwrap() {
14+
c.bench_function("encode default", |b| {
15+
b.iter(|| {
16+
let cur = Cursor::new(&mut buf);
17+
let mut enc = Encoder::new(black_box(cur));
18+
enc.encode(fit).unwrap();
19+
buf.clear();
20+
})
21+
});
22+
23+
c.bench_function("encode normal interleave 15", |b| {
24+
b.iter(|| {
25+
let cur = Cursor::new(&mut buf);
26+
let mut enc = EncoderBuilder::new(black_box(cur))
27+
.header_option(HeaderOption::Normal(15))
28+
.build();
29+
enc.encode(fit).unwrap();
30+
buf.clear();
31+
})
32+
});
33+
34+
c.bench_function("encode compress interleave 3", |b| {
35+
b.iter(|| {
36+
let cur = Cursor::new(&mut buf);
37+
let mut enc = EncoderBuilder::new(black_box(cur))
38+
.header_option(HeaderOption::Compressed(3))
39+
.build();
40+
enc.encode(fit).unwrap();
41+
buf.clear();
42+
})
43+
});
44+
}
4245
}
4346

4447
criterion_group!(benches, bench_encode);

rustyfit/src/crc16.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ impl Crc16 {
2424
self.0 = crc;
2525
}
2626

27-
pub fn sum16(&mut self) -> u16 {
27+
pub fn sum16(&self) -> u16 {
2828
self.0
2929
}
3030

0 commit comments

Comments
 (0)