unless it's followed by an "else" or "elsif".
With documentation updates and fixed tests
}
----
+== Expansions in an "if" condition
+
+An `if` condition may contain a xref:xlat/index.adoc[dynamic
+expansion], such as a call to the SQL module in the following example:
+
+.Example of Dynamic Expansion in Condition
+[source,unlang]
+----
+if %sql("SELECT reply_message FROM users where username = '%{User-Name}'") {
+ reject
+}
+----
+
+Unlike a simple check as with `if 1==2`, this dynamic expansion may fail.
+This failure can happen when the SQL database is unreachable, for
+example. Different failures are possible for different functions.
+
+When a xref:xlat/index.adoc[dynamic expansion] fails, and `if`
+statement is followed by a trailing xref:unlang/else.adoc[else] or
+xref:unlang/elsif.adoc[elsif] statement, then `if` condition is treated
+as "false", and the `else` or `elsif statement is processed.
+
+Otherwise, the failed `if` statement returns the `fail`
+xref:unlang/return_codes.adoc[rcode], which is processed according to
+the rules for the current section.
+
+This distinction between "condition which evaluates to `false`" and
+"condition which fails to be processed" allow policies to distinguish
+those two cases.
+
+In previous versions of the server, a failed dynamic expansion was
+treated as a condition which returned `false`, which was not always
+optimal.
+
== Practical Suggestions
There are a number of practical suggestions which make it easier to work with conditions.
The last entry in an `if` section can also be an xref:unlang/actions.adoc[actions] subsection.
-// Copyright (C) 2021 Network RADIUS SAS. Licenced under CC-by-NC 4.0.
+// Copyright (C) 2025 Network RADIUS SAS. Licenced under CC-by-NC 4.0.
// This documentation was developed by Network RADIUS SAS.
unlang_group_t *g;
unlang_cond_t *gext;
+ CONF_ITEM *ci;
xlat_exp_head_t *head = NULL;
bool is_truthy = false, value = false;
.allow_unresolved = false,
.allow_unknown = false,
.allow_wildcard = true,
- },
+ },
.literals_safe_for = unlang_ctx->rules->literals_safe_for,
};
gext->is_truthy = is_truthy;
gext->value = value;
+ ci = cf_section_to_item(cs);
+ while ((ci = cf_item_next(parent->ci, ci)) != NULL) {
+ if (cf_item_is_data(ci)) continue;
+
+ break;
+ }
+
+ /*
+ * If there's an 'if' without an 'else', then remember that.
+ */
+ if (ci && cf_item_is_section(ci)) {
+ char const *name;
+
+ name = cf_section_name1(cf_item_to_section(ci));
+ fr_assert(name != NULL);
+
+ gext->has_else = (strcmp(name, "else") == 0) || (strcmp(name, "elsif") == 0);
+ }
+
return c;
}
fr_value_box_t *box = fr_value_box_list_head(&state->out);
bool value;
+ /*
+ * Something in the conditional evaluation failed.
+ */
+ if (!state->success) {
+ unlang_group_t *g = unlang_generic_to_group(frame->instruction);
+ unlang_cond_t *gext = unlang_group_to_cond(g);
+
+ REDEBUG2("... failed to evaluate condition ...");
+
+ if (!gext->has_else) RETURN_MODULE_FAIL;
+ return UNLANG_ACTION_EXECUTE_NEXT;
+ }
+
if (!box) {
value = false;
xlat_exp_head_t *head;
bool is_truthy;
bool value;
+ bool has_else;
} unlang_cond_t;
/** Cast a group structure to the cond keyword extension
* Each argument is a FR_TYPE_GROUP, with one or more elements in a list.
*/
a = fr_value_box_list_head(in);
- b = fr_value_box_list_next(in, a);
-
- if (!a && !b) return XLAT_ACTION_FAIL;
+ if (!a) {
+ REDEBUG("Left argument to %s is missing", fr_tokens[op]);
+ return XLAT_ACTION_FAIL;
+ }
- fr_assert(!a || (a->type == FR_TYPE_GROUP));
- fr_assert(!b || (b->type == FR_TYPE_GROUP));
+ b = fr_value_box_list_next(in, a);
+ if (!b) {
+ REDEBUG("Right argument to %s is missing", fr_tokens[op]);
+ return XLAT_ACTION_FAIL;
+ }
fr_assert(!fr_comparison_op[op]);
* Each argument is a FR_TYPE_GROUP, with one or more elements in a list.
*/
a = fr_value_box_list_head(in);
- b = fr_value_box_list_next(in, a);
+ if (!a) {
+ REDEBUG("Left argument to %s is missing", fr_tokens[op]);
+ return XLAT_ACTION_FAIL;
+ }
- if (!a || !b) return XLAT_ACTION_FAIL;
+ b = fr_value_box_list_next(in, a);
+ if (!b) {
+ REDEBUG("Right argument to %s is missing", fr_tokens[op]);
+ return XLAT_ACTION_FAIL;
+ }
fr_assert(a->type == FR_TYPE_GROUP);
fr_assert(b->type == FR_TYPE_GROUP);
if ("%{ test_date}" <= 0) {
test_fail
+} else {
+ # nothing! Previous expansion has failed, so it MUST have an "else"
}
+
if (test_date <= 0) {
test_fail
}
result_integer := %unpack(%{test_octets}, 4, 'uint16')
# Octets 4 and 5 == 0x0304 == 772
-if ~(result_integer == 772) {
+if (result_integer != 772) {
test_fail
}