Expand description

Emit messages to the user running a policy.

The diagnostics of a Paralegal policy are similar to those used in rustc. In particular we use a strategy of “keep going”, meaning that a policy should not fail at the first error. Instead it should attempt to keep going if possible and emit additional errors, as those messages are useful for the user.

This manifests for instance in Diagnostics::error. This function records a severe error that should fail the policy but it does not exit the program. Instead the message is recorded and emitted later, for instance by Context::emit_diagnostics.

Emitting Messages

The main interface for diagnostics is the Diagnostics trait which defines functions for emitting messages like error for policy failures and warning for indicators to the user that something may be off, but not causing a policy failure.

We also offer two convenience macros assert_error! and assert_warning! that correspond to either function. Much like assert! they only evaluate and emit their messages if the provided condition is false. They should be used like assert_warning!(ctx, condition, message). ctx here is anything that implements Diagnostics.

Diagnostics is implemented directly by Context so you can use ctx.error() or ctx.warning(). You can also call it on scoped contexts (see below).

Scoping messages

You may however add additional contextual information about which policy or combinator is currently executing. Context::named_policy returns a wrapper that can be used the same way that you use Context, but when error or warning is called it also appends the name of the policy to you specified.

Similarly you can use Context::named_combinator or PolicyContext::named_combinator to add context about a named combinator.

Intended Workflow

use paralegal_policy::{
    Context, assert_error, assert_warning,
    paralegal_spdg::Identifier
};
use std::sync::Arc;

fn my_check(ctx: Arc<Context>) {
    ctx.named_policy(Identifier::new_intern("cannot escape"), |ctx| {
        let result_1 = ctx.clone().named_combinator(
            Identifier::new_intern("collect something"),
            |ctx| {
                /* actual computation */
                assert_error!(ctx, 1 + 2 == 4, "Oh oh, fail!");
                true
            }
        );
        let result_2 = ctx.clone().named_combinator(
            Identifier::new_intern("reach something"),
            |ctx| {
                assert_warning!(ctx, 1 - 3 == 0, "maybe wrong?");
                false
            }
        );
        assert_error!(ctx, result_1 || result_2, "combination failure");
    })

}

The messages emitted from here (if all conditions fail true) would be

[policy: cannot escape] [collect something] Oh oh, fail!
[policy: cannot escape] [reach something] maybe wrong?
[policy: cannot escape] combination failure

It is recommended to use this “shadowing” approach, where all contexts are named ctx, to avoid using the outer context by accident. The methods off outer contexts are always available on the inner ones.

Note that some methods, like Context::always_happens_before add a named combinator context by themselves when you use their report functions.

Structs

Enums

  • Severity of a recorded diagnostic message

Constants

Traits

Functions

Type Aliases