Tech News
← Back to articles

SocketAddrV6 is not roundtrip serializable

read original related products more articles

A few weeks ago at Oxide, we encountered a bug where a particular, somewhat large, data structure was erroring on serialization to JSON via serde . The problem was that JSON only supports map keys that are strings or numbers, and the data structure had an infrequently-populated map with keys that were more complex than that.

We fixed the bug, but a concern still remained: what if some other map that was empty most of the time had a complex key in it? The easiest way to guard against this is by generating random instances of the data structure and attempting to serialize them, checking that this operation doesn’t panic. The most straightforward way to do this is with property-based testing, where you define:

a way to generate random instances of a particular type, and

given a failing input, a way to shrink it down to a minimal failing value.

Modern property-based testing frameworks like proptest , which we use at Oxide, combine these two algorithms into a single strategy, through a technique known as integrated shrinking. (For a more detailed overview, see my monad tutorial, where I talk about the undesirable performance characteristics of monadic composition when it comes to integrated shrinking.)

The proptest library has a notion of a canonical strategy for a type, expressed via the Arbitrary trait. The easiest way to define Arbitrary instances for large, complex types is to use a derive macro. Annotate your type with the macro:

use test_strategy :: Arbitrary ; #[derive(Arbitrary)] struct MyType { id : String , data : BTreeMap < String , MyInnerType > , } #[derive(Arbitrary)] struct MyInnerType { value : usize , // ... }

As long as all the fields have Arbitrary defined for them—and the proptest library defines the trait for most types in the standard library—your type has a working random generator and shrinker associated with it. It’s pretty neat!

I put together an Arbitrary implementation for our very complex type, then wrote a property-based test to ensure that it serializes properly:

use test_strategy :: proptest ; // The outermost struct is called PlanningReport. #[proptest] fn planning_report_json_serialize ( planning_report : PlanningReport ) { serde_json :: to_string ( & planning_report ). unwrap (); }

... continue reading