mirror of
https://github.com/google/comprehensive-rust.git
synced 2025-04-20 06:58:03 +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
|
also defines callbacks to handle `Person` and `PhoneNumber` fields, and to parse
|
||||||
a message into a series of calls to those callbacks.
|
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` -->
|
<!-- compile_fail because `mdbook test` does not allow use of `thiserror` -->
|
||||||
```rust,editable,compile_fail
|
```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:parse_message }}
|
||||||
|
|
||||||
|
{{#include exercise.rs:message_types}}
|
||||||
|
|
||||||
|
// TODO: Implement ProtoMessage for Person and PhoneNumber.
|
||||||
|
|
||||||
{{#include exercise.rs:main }}
|
{{#include exercise.rs:main }}
|
||||||
```
|
```
|
||||||
|
@ -62,6 +62,10 @@ struct Field<'a> {
|
|||||||
value: FieldValue<'a>,
|
value: FieldValue<'a>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
trait ProtoMessage<'a>: Default + 'a {
|
||||||
|
fn add_field(&mut self, field: Field<'a>) -> Result<(), Error>;
|
||||||
|
}
|
||||||
|
|
||||||
impl TryFrom<u64> for WireType {
|
impl TryFrom<u64> for WireType {
|
||||||
type Error = Error;
|
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.
|
/// Parse a message in the given data, calling `field_callback` for each field in the message.
|
||||||
///
|
///
|
||||||
/// The entire input is consumed.
|
/// The entire input is consumed.
|
||||||
fn parse_message(
|
fn parse_message<'a, T: ProtoMessage<'a>>(mut data: &'a [u8]) -> Result<T, Error> {
|
||||||
mut data: &[u8],
|
let mut result = T::default();
|
||||||
field_callback: impl Fn(Field) -> Result<(), Error>,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
while !data.is_empty() {
|
while !data.is_empty() {
|
||||||
let parsed = parse_field(data)?;
|
let parsed = parse_field(data)?;
|
||||||
field_callback(parsed.0)?;
|
result.add_field(parsed.0)?;
|
||||||
data = parsed.1;
|
data = parsed.1;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(result)
|
||||||
}
|
}
|
||||||
// ANCHOR_END: parse_message
|
// 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
|
// ANCHOR: main
|
||||||
fn main() {
|
fn main() {
|
||||||
/// Handle a field in a Person message.
|
let person: Person = parse_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, 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,
|
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,
|
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,
|
0x2b, 0x31, 0x38, 0x30, 0x30, 0x2d, 0x38, 0x36, 0x37, 0x2d, 0x35, 0x33, 0x30,
|
||||||
0x38, 0x12, 0x06, 0x6d, 0x6f, 0x62, 0x69, 0x6c, 0x65,
|
0x38, 0x12, 0x06, 0x6d, 0x6f, 0x62, 0x69, 0x6c, 0x65,
|
||||||
],
|
])
|
||||||
person_field,
|
.unwrap();
|
||||||
)
|
println!("{:#?}", person);
|
||||||
.unwrap()
|
|
||||||
}
|
}
|
||||||
// ANCHOR_END: main
|
// ANCHOR_END: main
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user