]> git.ipfire.org Git - thirdparty/freeradius-server.git/commitdiff
add compilation for try / catch
authorAlan T. DeKok <aland@freeradius.org>
Thu, 14 Dec 2023 21:43:59 +0000 (16:43 -0500)
committerAlan T. DeKok <aland@freeradius.org>
Thu, 14 Dec 2023 21:43:59 +0000 (16:43 -0500)
along with docs and test cases

doc/antora/modules/reference/nav.adoc
doc/antora/modules/reference/pages/unlang/catch.adoc [new file with mode: 0644]
doc/antora/modules/reference/pages/unlang/edit.adoc
doc/antora/modules/reference/pages/unlang/keywords.adoc
doc/antora/modules/reference/pages/unlang/try.adoc [new file with mode: 0644]
src/lib/unlang/compile.c
src/tests/keywords/try [new file with mode: 0644]
src/tests/keywords/try-error [new file with mode: 0644]

index 25df91df3e8294ad2530cd02460094e71b1a8ee6..e8233cc6d464f579e0c62d421a9f1c41921c2a9c 100644 (file)
@@ -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 (file)
index 0000000..f5c41a6
--- /dev/null
@@ -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.
index df28d9909f5e10bf0ee91a66a7b3828fc728d620..f7c84a2e75e37bc67da4ee26f85ddb4d5cdfcb26 100644 (file)
@@ -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
index 239805b42d584a367f7abf856d2c4200640ed324..1a39cccdb16aafc4319cd97c7294059db6c0b018 100644 (file)
@@ -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 (file)
index 0000000..74d710f
--- /dev/null
@@ -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.
index 2b213f3ba8707cb094983e2617976794cd1526b2..7b9188c4fd66673ea39ab15af149d7bb61d4d440 100644 (file)
@@ -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 (file)
index 0000000..48d5ce3
--- /dev/null
@@ -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 (file)
index 0000000..58d082a
--- /dev/null
@@ -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