pub struct Recipe { /* private fields */ }
Expand description

Recipe describes how a set of data structures can be produced from the set of ingredients (other data structures). It attempts to solve the problem of conversion between data structures that arises often especially during testing, when concrete structures need to be produced from the abstract ones and vice versa.

Example

Imagine your application processes phone records, which it receives from another system, and needs to check them for structural correctness. The records could look like that:

pub struct Record {
    pub name: String,
    pub address: Address,
    pub landline: Phone,
    pub mobile: Phone,
}
pub struct Address {
    pub postal_code: u32,
    pub city: String,
    pub street: String,
    pub door: u32,
}
pub struct Phone {
    pub area_code: u32,
    pub number: u32,
}

And the checking function could look like that:

fn check_record(r: Record) -> bool {
    // Name is not too short or too long
    r.name.len() >= 6 &&
    r.name.len() <= 100 &&
    // Postal code is 5 digits
    r.address.postal_code >= 10000 &&
    r.address.postal_code <= 99999 &&
    // Phone codes are 3 digits
    r.landline.area_code >= 100 &&
    r.landline.area_code <= 999 &&
    r.mobile.area_code >= 100 &&
    r.mobile.area_code <= 999
    // ...
}

Now, the question is how do you prepare tests for this function? You could of course each time create concrete records:

let record = Record {
    name: "John Smith".to_string(),
    address: Address {
        ...
    },
    landline: Phone {
        area_code: 123,
        number: 456789,
    },
    mobile: ...,
}

The problem with that approach is that it is too verbose and inflexible. It can be simplified somehow by introducing local variables (dummies), but using dummies is also quite ad-hoc and inflexible, Our data chef recommends you to define a recipe instead!

let mut r = Recipe::new();
// default phone
r.put(|_| Phone {
    area_code: 123,
    number: 456789,
});
// default address
r.put(|_| Address {
    postal_code: 10179,
    city: "Berlin".to_string(),
    street: "Molkenmarkt".to_string(),
    door: 1,
});
// a recipe to produce `Record` from a name, using default address and phones
r.add(|r, name: String| Record {
    name,
    address: r.take(),
    landline: r.take(),
    mobile: r.take(),
});

In the recipe above you define (put on the table) default values for Phone and Address, and define a simple recipe to make (prepare) a Record from a name, using defaults for other fields. With that recipe we can already test check_record():

assert!(check_record(r.make("John Smith".to_string())));
assert!(!check_record(r.make("short".repeat(1))));
assert!(!check_record(r.make("long".repeat(100))));

Now, what if we want to test how the phones are handled? Add more recipes to cook phones!

// Recipe for cooking a phone out of a code and a number
r.add(|_, phone: (u32, u32)| Phone {
    area_code: phone.0,
    number: phone.1,
});
// Recipe for cooking a record with the landline number provided,
// and defaults for the rest of the fields.
r.add(|r, phone: (u32, u32)| Record {
    name: "John Smith".to_string(),
    address: r.take(),
    landline: r.make(phone),
    mobile: r.take(),
});

// A generic test for phones: the phone being tested
// depends on what the recipe produces from the code and the number.
let test_phone = |r: &Recipe| {
    assert!(check_record(r.make((123u32,1234567u32))));
    assert!(!check_record(r.make((1u32,1234567u32))));
    assert!(!check_record(r.make((1234u32,1234567u32))));
};
test_phone(&r); // tests landline phone

// Redefine the recipe to generate a mobile phone
r.add(|r, phone: (u32, u32)| Record {
    name: "John Smith".to_string(),
    address: r.take(),
    landline: r.take(),
    mobile: r.make(phone),
});
test_phone(&r); // tests mobile phone

Implementations

Create a new recipe

Add conversion from From into To. Use make() to apply the conversion.

Add named conversion from From into To. Use make_as() to apply the conversion.

Put default value for type T. Use take() to retrieve the default.

Put named default value for type T. Use take_as() to retrieve the default.

Makes from From a To, applying a previously defined conversion.

Makes from From a To, applying a previously defined named conversion

Take default value of type T.

Take named default value of type T.

Trait Implementations

Formats the value using the given formatter. Read more

Returns the “default value” for a type. Read more

Auto Trait Implementations

Blanket Implementations

Gets the TypeId of self. Read more

Immutably borrows from an owned value. Read more

Mutably borrows from an owned value. Read more

Performs the conversion.

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more

Instruments this type with the current Span, returning an Instrumented wrapper. Read more

Performs the conversion.

The alignment of pointer.

The type for initializers.

Initializes a with the given initializer. Read more

Dereferences the given pointer. Read more

Mutably dereferences the given pointer. Read more

Drops the object pointed to by the given pointer. Read more

Should always be Self

The type returned in the event of a conversion error.

Performs the conversion.

The type returned in the event of a conversion error.

Performs the conversion.

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more