mirror of
https://github.com/google/comprehensive-rust.git
synced 2025-04-13 04:30:31 +02:00
Parse into data structures in protobuf example (#1591)
This is a suggestion for how we could make the protobuf exercise at the end of day 3 better. Generally speaking, the idea is to parse the protbuf bytes into data types instead of only printing the parsing outcome to the console. To make this a little more realistic, we also introduce a trait `ProtoMessage` for message types. I think this is more instructive than the current example. In particular, we get to mess around with lifetimes. This might be a little more complicated but can be a great opportunity for the students to tie together different things they've learnt in the course so far. What do you all think?
This commit is contained in:
parent
085cbf2c1e
commit
97a47cab78
@ -39,7 +39,8 @@ called VARINT. Luckily, `parse_varint` is defined for you below. The given code
|
||||
also defines callbacks to handle `Person` and `PhoneNumber` fields, and to parse
|
||||
a message into a series of calls to those callbacks.
|
||||
|
||||
What remains for you is to implement the `parse_field` function.
|
||||
What remains for you is to implement the `parse_field` function and the
|
||||
`ProtoMessage` trait for `Person` and `PhoneNumber`.
|
||||
|
||||
<!-- compile_fail because `mdbook test` does not allow use of `thiserror` -->
|
||||
```rust,editable,compile_fail
|
||||
@ -56,5 +57,9 @@ What remains for you is to implement the `parse_field` function.
|
||||
|
||||
{{#include exercise.rs:parse_message }}
|
||||
|
||||
{{#include exercise.rs:message_types}}
|
||||
|
||||
// TODO: Implement ProtoMessage for Person and PhoneNumber.
|
||||
|
||||
{{#include exercise.rs:main }}
|
||||
```
|
||||
|
@ -62,6 +62,10 @@ struct Field<'a> {
|
||||
value: FieldValue<'a>,
|
||||
}
|
||||
|
||||
trait ProtoMessage<'a>: Default + 'a {
|
||||
fn add_field(&mut self, field: Field<'a>) -> Result<(), Error>;
|
||||
}
|
||||
|
||||
impl TryFrom<u64> for WireType {
|
||||
type Error = Error;
|
||||
|
||||
@ -171,56 +175,66 @@ fn parse_field(data: &[u8]) -> Result<(Field, &[u8]), Error> {
|
||||
/// Parse a message in the given data, calling `field_callback` for each field in the message.
|
||||
///
|
||||
/// The entire input is consumed.
|
||||
fn parse_message(
|
||||
mut data: &[u8],
|
||||
field_callback: impl Fn(Field) -> Result<(), Error>,
|
||||
) -> Result<(), Error> {
|
||||
fn parse_message<'a, T: ProtoMessage<'a>>(mut data: &'a [u8]) -> Result<T, Error> {
|
||||
let mut result = T::default();
|
||||
while !data.is_empty() {
|
||||
let parsed = parse_field(data)?;
|
||||
field_callback(parsed.0)?;
|
||||
result.add_field(parsed.0)?;
|
||||
data = parsed.1;
|
||||
}
|
||||
Ok(())
|
||||
Ok(result)
|
||||
}
|
||||
// ANCHOR_END: parse_message
|
||||
|
||||
// ANCHOR: message_types
|
||||
#[derive(Debug, Default)]
|
||||
struct PhoneNumber<'a> {
|
||||
number: &'a str,
|
||||
type_: &'a str,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
struct Person<'a> {
|
||||
name: &'a str,
|
||||
id: u64,
|
||||
phone: Vec<PhoneNumber<'a>>,
|
||||
}
|
||||
// ANCHOR_END: message_types
|
||||
|
||||
impl<'a> ProtoMessage<'a> for Person<'a> {
|
||||
fn add_field(&mut self, field: Field<'a>) -> Result<(), Error> {
|
||||
match field.field_num {
|
||||
1 => self.name = field.value.as_string()?,
|
||||
2 => self.id = field.value.as_u64()?,
|
||||
3 => self.phone.push(parse_message(field.value.as_bytes()?)?),
|
||||
_ => {} // skip everything else
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> ProtoMessage<'a> for PhoneNumber<'a> {
|
||||
fn add_field(&mut self, field: Field<'a>) -> Result<(), Error> {
|
||||
match field.field_num {
|
||||
1 => self.number = field.value.as_string()?,
|
||||
2 => self.type_ = field.value.as_string()?,
|
||||
_ => {} // skip everything else
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// ANCHOR: main
|
||||
fn main() {
|
||||
/// Handle a field in a Person message.
|
||||
fn person_field(field: Field) -> Result<(), Error> {
|
||||
match field.field_num {
|
||||
1 => println!("name: {}", field.value.as_string()?),
|
||||
2 => println!("id: {}", field.value.as_u64()?),
|
||||
3 => {
|
||||
println!("phone:");
|
||||
parse_message(field.value.as_bytes()?, phone_number_field)?;
|
||||
}
|
||||
_ => {} // skip everything else
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Handle a field in a PhoneNumber message.
|
||||
fn phone_number_field(field: Field) -> Result<(), Error> {
|
||||
match field.field_num {
|
||||
1 => println!(" number: {}", field.value.as_string()?),
|
||||
2 => println!(" type: {}", field.value.as_string()?),
|
||||
_ => {} // skip everything else
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
parse_message(
|
||||
&[
|
||||
0x0a, 0x07, 0x6d, 0x61, 0x78, 0x77, 0x65, 0x6c, 0x6c, 0x10, 0x2a, 0x1a, 0x16,
|
||||
0x0a, 0x0e, 0x2b, 0x31, 0x32, 0x30, 0x32, 0x2d, 0x35, 0x35, 0x35, 0x2d, 0x31,
|
||||
0x32, 0x31, 0x32, 0x12, 0x04, 0x68, 0x6f, 0x6d, 0x65, 0x1a, 0x18, 0x0a, 0x0e,
|
||||
0x2b, 0x31, 0x38, 0x30, 0x30, 0x2d, 0x38, 0x36, 0x37, 0x2d, 0x35, 0x33, 0x30,
|
||||
0x38, 0x12, 0x06, 0x6d, 0x6f, 0x62, 0x69, 0x6c, 0x65,
|
||||
],
|
||||
person_field,
|
||||
)
|
||||
.unwrap()
|
||||
let person: Person = parse_message(&[
|
||||
0x0a, 0x07, 0x6d, 0x61, 0x78, 0x77, 0x65, 0x6c, 0x6c, 0x10, 0x2a, 0x1a, 0x16,
|
||||
0x0a, 0x0e, 0x2b, 0x31, 0x32, 0x30, 0x32, 0x2d, 0x35, 0x35, 0x35, 0x2d, 0x31,
|
||||
0x32, 0x31, 0x32, 0x12, 0x04, 0x68, 0x6f, 0x6d, 0x65, 0x1a, 0x18, 0x0a, 0x0e,
|
||||
0x2b, 0x31, 0x38, 0x30, 0x30, 0x2d, 0x38, 0x36, 0x37, 0x2d, 0x35, 0x33, 0x30,
|
||||
0x38, 0x12, 0x06, 0x6d, 0x6f, 0x62, 0x69, 0x6c, 0x65,
|
||||
])
|
||||
.unwrap();
|
||||
println!("{:#?}", person);
|
||||
}
|
||||
// ANCHOR_END: main
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user