From: Alan T. DeKok Date: Thu, 14 Dec 2023 21:43:59 +0000 (-0500) Subject: add compilation for try / catch X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=aadfb8dd43ef7ceb83d937c6327ef474ccd389a9;p=thirdparty%2Ffreeradius-server.git add compilation for try / catch along with docs and test cases --- diff --git a/doc/antora/modules/reference/nav.adoc b/doc/antora/modules/reference/nav.adoc index 25df91df3e8..e8233cc6d46 100644 --- a/doc/antora/modules/reference/nav.adoc +++ b/doc/antora/modules/reference/nav.adoc @@ -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] @@ -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] diff --git a/doc/antora/modules/reference/pages/unlang/catch.adoc b/doc/antora/modules/reference/pages/unlang/catch.adoc new file mode 100644 index 00000000000..f5c41a60eb7 --- /dev/null +++ b/doc/antora/modules/reference/pages/unlang/catch.adoc @@ -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. diff --git a/doc/antora/modules/reference/pages/unlang/edit.adoc b/doc/antora/modules/reference/pages/unlang/edit.adoc index df28d9909f5..f7c84a2e75e 100644 --- a/doc/antora/modules/reference/pages/unlang/edit.adoc +++ b/doc/antora/modules/reference/pages/unlang/edit.adoc @@ -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 @@ -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 diff --git a/doc/antora/modules/reference/pages/unlang/keywords.adoc b/doc/antora/modules/reference/pages/unlang/keywords.adoc index 239805b42d5..1a39cccdb16 100644 --- a/doc/antora/modules/reference/pages/unlang/keywords.adoc +++ b/doc/antora/modules/reference/pages/unlang/keywords.adoc @@ -15,6 +15,7 @@ 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. @@ -22,6 +23,7 @@ looping, etc. | 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 diff --git a/doc/antora/modules/reference/pages/unlang/try.adoc b/doc/antora/modules/reference/pages/unlang/try.adoc new file mode 100644 index 00000000000..74d710f7e53 --- /dev/null +++ b/doc/antora/modules/reference/pages/unlang/try.adoc @@ -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. diff --git a/src/lib/unlang/compile.c b/src/lib/unlang/compile.c index 2b213f3ba87..7b9188c4fd6 100644 --- a/src/lib/unlang/compile.c +++ b/src/lib/unlang/compile.c @@ -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) @@ -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; @@ -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) { @@ -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 }, @@ -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); diff --git a/src/tests/keywords/try b/src/tests/keywords/try new file mode 100644 index 00000000000..48d5ce377ff --- /dev/null +++ b/src/tests/keywords/try @@ -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 +} diff --git a/src/tests/keywords/try-error b/src/tests/keywords/try-error new file mode 100644 index 00000000000..58d082a7119 --- /dev/null +++ b/src/tests/keywords/try-error @@ -0,0 +1,13 @@ +string foo + +try { # ERROR + &foo := "foo" +} + +# +# No "catch" - that's an issue +# + +if &foo != "foo" { + test_fail +} \ No newline at end of file