Skip to content

Commit

Permalink
add compilation for try / catch
Browse files Browse the repository at this point in the history
along with docs and test cases
  • Loading branch information
alandekok committed Dec 14, 2023
1 parent 2817f91 commit aadfb8d
Show file tree
Hide file tree
Showing 8 changed files with 198 additions and 17 deletions.
2 changes: 2 additions & 0 deletions doc/antora/modules/reference/nav.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
**** xref:unlang/call.adoc[call]
**** xref:unlang/caller.adoc[caller]
**** xref:unlang/case.adoc[case]
**** xref:unlang/catch.adoc[catch]
**** xref:unlang/detach.adoc[detach]
**** xref:unlang/edit.adoc[editing]
**** xref:unlang/else.adoc[else]
Expand All @@ -30,6 +31,7 @@
**** xref:unlang/switch.adoc[switch]
**** xref:unlang/timeout.adoc[timeout]
**** xref:unlang/transaction.adoc[transaction]
**** xref:unlang/try.adoc[try]
**** xref:unlang/update.adoc[update]

*** xref:unlang/module.adoc[Modules]
Expand Down
34 changes: 34 additions & 0 deletions doc/antora/modules/reference/pages/unlang/catch.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
= The catch Statement

.Syntax
[source,unlang]
----
try {
...
}
catch {
[ statements ]
}
----

The `catch` statement runs a series of substatements in a block, but only if the previous xref:unlang/try.adoc[try] failed.

[ statements ]:: The `unlang` commands which will be executed. A
`catch` block can be empty.

.Example

[source,unlang]
----
try {
sql
}
catch {
# ... run only if sql failed
}
----

There is some overlap in functionality between `try` / xref:unlang/catch.adoc[catch] and xref:unlang/redundant.adoc[redundant]. The main difference (TODO) is that a xref:unlang/catch.adoc[catch] statement can catch specific actions.

// Copyright (C) 2023 Network RADIUS SAS. Licenced under CC-by-NC 4.0.
// This documentation was developed by Network RADIUS SAS.
22 changes: 8 additions & 14 deletions doc/antora/modules/reference/pages/unlang/edit.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ In version 4, the `update` statement has been deprecated. Using
will simply not work with them. We recommend switching to the new
edit syntax, which is more powerful, and less verbose.

Unlike version 3, attribute editing can result in a `fail` return
code. That is, edits either return `noop` on success, or `fail` on failure.

In most cases, an edit failure will result in the processing section
exiting, and returning `fail`. This behavior can be overriden with a xref:unlang/transaction.adoc[transaction], or a xref:unlang/try.adoc[try] / xref:unlang/catch.adoc[catch] block.

An edit statement has the following standard syntax:

.Syntax
Expand Down Expand Up @@ -77,24 +83,12 @@ and do not result in any changes.

=== Grouping Edits

Multiple attributes may be grouped into a set by using the `group`
keyword. When changes are done in a `group`, then either all of the
Multiple attributes may be grouped into a set by using the xref:unlang/transaction.adoc[transaction]
keyword. When changes are done in a `transaction`, then either all of the
changes are applied, or none of them are applied. This functionality
is best used to conditionally apply attribute changes, generally when
retrieving data from a database.

.Grouping multiple edits
[source,unlang]
----
group {
&reply.Reply-Message += %sql("SELECT ...")
&reply.Filter-Id := "foo"
}
----

If the above SQL `select` statement fails, then then
`&reply.Filter-Id` attribute will not exist.

== List Editing

.Syntax
Expand Down
2 changes: 2 additions & 0 deletions doc/antora/modules/reference/pages/unlang/keywords.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,15 @@ looping, etc.
| Keyword | Description
| xref:unlang/break.adoc[break] | Exit early from a `foreach` loop.
| xref:unlang/case.adoc[case] | Match inside of a `switch`.
| xref:unlang/catch.adoc[catch] | catch an error from a previous "try"
| xref:unlang/else.adoc[else] | Do something when an `if` does not match.
| xref:unlang/elsif.adoc[elsif] | Check for condition when a previous `if` does not match.
| xref:unlang/foreach.adoc[foreach] | Loop over a list of attributes.
| xref:unlang/if.adoc[if] | Check for a condition, and execute a sub-policy if it matches.
| xref:unlang/return.adoc[return] | Immediately stop processing a section.
| xref:unlang/switch.adoc[switch] | Check for multiple values.
| xref:unlang/transaction.adoc[transaction] | Group operations into a transaction which can be reverted on failure.
| xref:unlang/try.adoc[try] | try / catch blocks
|=====

== Attribute Editing Keywords
Expand Down
40 changes: 40 additions & 0 deletions doc/antora/modules/reference/pages/unlang/try.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
= The try Statement

.Syntax
[source,unlang]
----
try {
[ statements ]
}
catch {
...
}
----

The `try` statement runs a series of substatements in a block. If the
block returns an error such as `fail`, `reject`, `invalid`, or
`disallow`, a subsequent xref:unlang/catch.adoc[catch] block is
executed.

[ statements ]:: The `unlang` commands which will be executed. A
`try` block cannot be empty.

Every `try` block must be followed by a xref:unlang/catch.adoc[catch]
block.

.Example

[source,unlang]
----
try {
sql
}
catch {
# ... run only if sql failed
}
----

There is some overlap in functionality between `try` / xref:unlang/catch.adoc[catch] and xref:unlang/redundant.adoc[redundant]. The main difference (TODO) is that a xref:unlang/catch.adoc[catch] statement can catch specific actions.

// Copyright (C) 2023 Network RADIUS SAS. Licenced under CC-by-NC 4.0.
// This documentation was developed by Network RADIUS SAS.
78 changes: 75 additions & 3 deletions src/lib/unlang/compile.c
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ RCSID("$Id$")
#include "timeout_priv.h"
#include "limit_priv.h"
#include "transaction_priv.h"
#include "try_priv.h"
#include "catch_priv.h"

#define UNLANG_IGNORE ((unlang_t *) -1)

Expand Down Expand Up @@ -2573,9 +2575,6 @@ static unlang_t *compile_transaction(unlang_t *parent, unlang_compile_t *unlang_
unlang_ctx2.actions.actions[RLM_MODULE_INVALID] = MOD_ACTION_RETURN;
unlang_ctx2.actions.actions[RLM_MODULE_DISALLOW] = MOD_ACTION_RETURN;

/*
* We always create a group, even if the section is empty.
*/
g = group_allocate(parent, cs, &transaction);
if (!g) return NULL;

Expand All @@ -2597,6 +2596,77 @@ static unlang_t *compile_transaction(unlang_t *parent, unlang_compile_t *unlang_
return c;
}

static unlang_t *compile_try(unlang_t *parent, unlang_compile_t *unlang_ctx, CONF_SECTION *cs)
{
unlang_group_t *g;
unlang_t *c;
CONF_SECTION *next;

static unlang_ext_t const ext = {
.type = UNLANG_TYPE_TRY,
.len = sizeof(unlang_try_t),
.type_name = "unlang_try_t",
};

/*
* The transaction is empty, ignore it.
*/
if (!cf_item_next(cs, NULL)) {
cf_log_err(cs, "'try' sections cannot be empty");
return NULL;
}

next = cf_section_next(cf_item_to_section(cf_parent(cs)), cs);
if (!next || (strcmp(cf_section_name1(next), "catch") != 0)) {
cf_log_err(cs, "'try' sections must be followed by a 'catch'");
return NULL;
}

g = group_allocate(parent, cs, &ext);
if (!g) return NULL;

c = unlang_group_to_generic(g);
c->debug_name = c->name = cf_section_name1(cs);

return compile_children(g, unlang_ctx, true);
}

static unlang_t *compile_catch(unlang_t *parent, unlang_compile_t *unlang_ctx, CONF_SECTION *cs)
{
unlang_group_t *g;
unlang_t *c;

static unlang_ext_t const ext = {
.type = UNLANG_TYPE_CATCH,
.len = sizeof(unlang_catch_t),
.type_name = "unlang_catch_t",
};

g = group_allocate(parent, cs, &ext);
if (!g) return NULL;

c = unlang_group_to_generic(g);
c->debug_name = c->name = cf_section_name1(cs);

/*
* No arg2: catch all errors
*/
if (!cf_section_name2(cs)) {
unlang_catch_t *ca = unlang_group_to_catch(g);

ca->catching[RLM_MODULE_REJECT] = true;
ca->catching[RLM_MODULE_FAIL] = true;
ca->catching[RLM_MODULE_INVALID] = true;
ca->catching[RLM_MODULE_DISALLOW] = true;
}

/*
* @todo - Else parse and limit the things we catch
*/

return compile_children(g, unlang_ctx, true);
}


static int8_t case_cmp(void const *one, void const *two)
{
Expand Down Expand Up @@ -4465,6 +4535,7 @@ static fr_table_ptr_sorted_t unlang_section_keywords[] = {
{ L("call"), (void *) compile_call },
{ L("caller"), (void *) compile_caller },
{ L("case"), (void *) compile_case },
{ L("catch"), (void *) compile_catch },
{ L("else"), (void *) compile_else },
{ L("elsif"), (void *) compile_elsif },
{ L("foreach"), (void *) compile_foreach },
Expand All @@ -4480,6 +4551,7 @@ static fr_table_ptr_sorted_t unlang_section_keywords[] = {
{ L("switch"), (void *) compile_switch },
{ L("timeout"), (void *) compile_timeout },
{ L("transaction"), (void *) compile_transaction },
{ L("try"), (void *) compile_try },
{ L("update"), (void *) compile_update },
};
static int unlang_section_keywords_len = NUM_ELEMENTS(unlang_section_keywords);
Expand Down
24 changes: 24 additions & 0 deletions src/tests/keywords/try
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#
# PRE: if
#
string foo
string bar

try {
&foo := "hello"

fail

&bar := "nope"
}
catch {
if &foo != "hello" {
test_fail
}

if &bar {
test_fail
}

success
}
13 changes: 13 additions & 0 deletions src/tests/keywords/try-error
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
string foo

try { # ERROR
&foo := "foo"
}

#
# No "catch" - that's an issue
#

if &foo != "foo" {
test_fail
}

0 comments on commit aadfb8d

Please sign in to comment.