Skip to content

Composition

Example

An example that is meant to illustrate protocol composition, or nesting. It involves a Product protocol between a customer and a company, and a Conditions protocol that governs the rate the customer is paying.

The Product is a simple product, where the customer pays for some quantity at some monthly unit rate. This rate is initially specified, but is ultimately governed by Conditions that are subject to change. Unilaterally, the company may increase the rate by no more than 0.5 on a monthly basis via the setRate permission in Conditions. However, other changes are allowed if both parties agree via changeConditions.

Source

protocol[company] Conditions(var currentRate: Number) {
    require(currentRate > 0, "Rate must be strictly positive");

    var lastRateChange: DateTime = now();

    permission[company] setRate(newRate: Number) after lastRateChange.plus(months(1)) {
        require(newRate > 0, "Rate must be strictly positive");
        require(newRate <= (currentRate + 0.5), "Rate increases may not exceed 0.5");

        this.currentRate = newRate;
        this.lastRateChange = now();
    };
};

protocol[customer, company] Product(var quantity: Number, var rate: Number) {
    initial state active;
    final state cancelled;

    var conditions: Conditions = Conditions[company](rate);

    permission[customer] computeMonthlyCost() returns Number | active {
        return quantity * conditions.currentRate;
    };

    permission[customer] cancel() | active {
        become cancelled;
    };

    permission[company & customer] changeConditions(newConditions: Conditions) {
        this.conditions = newConditions;
    };
};

Tests

const CUSTOMER = 'customer';
const COMPANY = 'company';

@test
function testMonthlyCost(test: Test) -> {
    var p = Product[CUSTOMER, COMPANY](5, 0.85);

    // Compute and verify initial cost
    var initialCost = p.computeMonthlyCost[CUSTOMER]();
    test.assertEquals(4.25, initialCost, "Monthly cost should be correct");
};

@test
function testIllegalRateIncrease(test: Test) -> {
    var p = Product[CUSTOMER, COMPANY](5, 0.85);

    // Attempt an illegal rate increase, and expect failure
    test.assertFails(function() returns Unit -> p.conditions.setRate[COMPANY](0.9));
};

@test
function testRateIncrease(test: Test) -> {
    var p = Product[CUSTOMER, COMPANY](5, 0.85);

    // Forward the clock, attempt increase
    test.setTime(now().plus(months(1)));
    p.conditions.setRate[COMPANY](0.9);

    // Compute and verify new cost
    var newCost = p.computeMonthlyCost[CUSTOMER]();
    test.assertEquals(4.5, newCost, "Monthly cost should be updated and correct");
};

@test
function testChangeOfConditions(test: Test) -> {
    var p = Product[CUSTOMER, COMPANY](5, 0.85);

    // Compute and verify initial cost
    var initialCost = p.computeMonthlyCost[CUSTOMER]();
    test.assertEquals(4.25, initialCost, "Monthly cost should be correct");

    // Set new conditions
    var newCnd = Conditions[COMPANY](0.80);
    p.changeConditions[COMPANY, CUSTOMER](newCnd);

    // Compute and verify new cost
    var newCost = p.computeMonthlyCost[CUSTOMER]();
    test.assertEquals(4.00, newCost, "New monthly cost should be correct");
};