Reputation: 1663
What I'm trying to do is create a libsonnet library with some complex validation on the inputs, but I'm not sure how to implement this in the libsonnet
file without getting null
back.
I'm trying to generate API calls for Hosted Graphite's Alerts API using Jsonnet. The idea being is that we can store all of our alerts in version control, and update them in a CI / CD pipeline. I want to prevent errors, so I've implemented some complex validation based on what the above API spec defines. I have the following saved as alerts.libsonnet
:
local alert_criteria_types = [
'above',
'below',
'missing',
'outside_bounds',
];
local notification_types_strings = [
'state_change',
];
local notification_types_arrays = [
'every',
'state_change',
];
local on_query_failure_types = [
'ignore',
'notify',
null,
];
{
local HostedGraphiteAlerts = self,
new(
name,
metric,
alert_criteria_type,
additional_alert_criteria={},
additional_criteria={},
expression='a',
scheduled_mutes=[],
notification_channels=['Email me'],
notification_type=null,
info=null,
on_query_failure='notify',
)::
// Simple checks
assert std.member(alert_criteria_types, alert_criteria_type) : "Input 'alert_criteria_type' is not one of the types: %s." % std.join(', ', alert_criteria_types);
assert std.member(on_query_failure_types, on_query_failure) : "Input 'on_query_failure_type' is not one of the types: %s." % std.join(', ', on_query_failure_types);
// Advanced checks
if notification_type != null && std.isString(notification_type) then
assert std.member(notification_types_strings, notification_type) : "Input 'notification_type' is not one of the types: %s." % std.join(', ', notification_types_strings);
if notification_type != null && std.isArray(notification_type) then
assert std.member(notification_types_arrays, notification_type[0]) : "Input 'notification_type' is not one of the types: %s." % std.join(', ', notification_types_arrays);
if notification_type != null && std.isArray(notification_type) then
assert std.member(notification_types_arrays, notification_type[0]) : "Input 'notification_type' is not one of the types: %s." % std.join(', ', notification_types_arrays);
if notification_type != null && std.isArray(notification_type) && notification_type[0] == 'every' then
assert notification_type[1] != null : "Input 'notification_type' cannot have an empty entry for 'time_in_minutes' for notification type 'every'.";
if notification_type != null && std.isArray(notification_type) && notification_type[0] == 'every' then
assert std.isNumber(notification_type[1]) : "Input 'notification_type' must have a JSON 'number' type for notification type 'every'.";
// Main
{
name: name,
metric: metric,
alert_criteria: {
type: alert_criteria_type,
} + additional_alert_criteria,
additional_criteria: additional_criteria,
expression: expression,
scheduled_mutes: scheduled_mutes,
notification_channels: notification_channels,
notification_type: notification_type,
info: info,
on_query_failure: on_query_failure,
},
}
This passes a basic jsonnetfmt
check, but the problem is when I go to use it in an alerts.jsonnet
file like so:
local alerts = (import 'hosted_graphite.libsonnet').alerts;
alerts.new(
name='something',
metric='some.graphite.metric',
alert_criteria_type='below',
)
This simply returns null
:
$ jsonnet hosted_graphite/alerts.jsonnet
null
I know this is because it's taking the value of the first assert
statement. But how else can this be done?
Thank you!
Upvotes: 3
Views: 1851
Reputation: 3020
Beware that jsonnet
is not an imperative language, don't expect those if
lines to get evaluated as if where part of a script.
Think of assertions as a "virtual" / invisible field that must always evaluate to true
Below implements what (I think) you're after:
hosted_graphite.libsonnet
local alert_criteria_types = [
'above',
'below',
'missing',
'outside_bounds',
];
local notification_types_strings = [
'state_change',
];
local notification_types_arrays = [
'every',
'state_change',
];
local on_query_failure_types = [
'ignore',
'notify',
null,
];
{
local HostedGraphiteAlerts = self,
new(
name,
metric,
alert_criteria_type,
additional_alert_criteria={},
additional_criteria={},
expression='a',
scheduled_mutes=[],
notification_channels=['Email me'],
notification_type=null,
info=null,
on_query_failure='notify',
)::
// Main
{
name: name,
metric: metric,
alert_criteria: {
type: alert_criteria_type,
} + additional_alert_criteria,
additional_criteria: additional_criteria,
expression: expression,
scheduled_mutes: scheduled_mutes,
notification_channels: notification_channels,
notification_type: notification_type,
info: info,
on_query_failure: on_query_failure,
// Simple checks
assert std.member(alert_criteria_types, self.alert_criteria.type) : (
"Input 'alert_criteria_type' is not one of the types: %s." % std.join(', ', alert_criteria_types)
),
assert std.member(on_query_failure_types, self.on_query_failure) : (
"Input 'on_query_failure_type' is not one of the types: %s." % std.join(', ', on_query_failure_types)
),
// Advanced checks:
// - 1st line is a conditional that must be false ('A||B' construct) to get 2nd line evaluated
// - 2nd line is the "final" type/value check, must be true
assert (self.notification_type == null || !std.isString(self.notification_type) ||
std.member(notification_types_strings, self.notification_type)) : (
"Input 'notification_type' string is not one of the types: %s." % std.join(', ', notification_types_strings)
),
assert (self.notification_type == null || !std.isArray(self.notification_type) ||
std.member(notification_types_arrays, self.notification_type[0])) : (
"Input 'notification_type' array is not one of the types: %s." % std.join(', ', notification_types_arrays)
),
assert (self.notification_type == null || !std.isArray(self.notification_type) ||
self.notification_type != ['every', null]) : (
"Input 'notification_type' cannot have an empty entry for 'time_in_minutes' for notification type 'every'."
),
assert (self.notification_type == null || !std.isArray(self.notification_type) ||
[self.notification_type[0], std.isNumber(self.notification_type[1])] == ['every', true]) : (
"Input 'notification_type' must have a JSON 'number' type for notification type 'every'."
),
},
}
alerts.jsonnet
local alerts = (import 'hosted_graphite.libsonnet');
{
a0: alerts.new(
name='something',
metric='some.graphite.metric',
alert_criteria_type='below',
),
a1: alerts.new(
name='something',
metric='some.graphite.metric',
alert_criteria_type='below',
notification_type='state_change',
),
a2: alerts.new(
name='something',
metric='some.graphite.metric',
alert_criteria_type='below',
notification_type=['every', 10],
),
}
Note that I'm using self.<field>
rather than the function parameter, it's a good pattern to allow derivation/overriding while still getting the assertions evaluated.
BTW I'd also recommend looking at https://cuelang.org/, which plays in the same field as jsonnet
but with type-checking being integral part of the language.
Upvotes: 4