paralegal_policy

Module diagnostics

Source
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 RootContext::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 RootContext 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. RootContext::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 RootContext::named_combinator or PolicyContext::named_combinator to add context about a named combinator.

§Intended Workflow

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

fn my_check(ctx: Arc<RootContext>) {
    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 RootContext::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§