Packages
caffeine_lang
0.0.14
6.3.1
6.3.0
6.2.2
6.2.1
6.2.0
6.1.2
6.1.1
6.1.0
6.0.0
5.6.0
5.5.0
5.4.4
5.4.3
5.4.2
5.4.1
5.4.0
5.3.0
5.2.0
5.1.1
5.1.0
5.0.12
5.0.11
5.0.10
5.0.8
5.0.7
5.0.6
5.0.5
5.0.4
5.0.1
5.0.0
4.10.0
4.9.0
4.8.3
4.8.2
4.8.1
4.8.0
4.7.9
4.7.8
4.7.7
4.7.6
4.7.5
4.6.7
4.6.6
4.6.5
4.6.4
4.6.3
4.6.2
4.6.0
4.5.1
4.5.0
4.4.4
4.4.3
4.4.1
4.4.0
4.3.7
4.3.6
3.0.6
3.0.5
3.0.4
3.0.3
3.0.2
3.0.1
3.0.0
2.0.5
2.0.4
2.0.3
2.0.2
2.0.1
2.0.0
1.0.2
1.0.1
0.1.0
0.0.24
0.0.23
0.0.22
0.0.21
0.0.20
0.0.19
0.0.18
0.0.17
0.0.16
0.0.15
0.0.14
0.0.13
0.0.12
0.0.11
0.0.10
0.0.9
0.0.8
0.0.7
0.0.6
0.0.5
0.0.4
0.0.2
0.0.1
A compiler for generating reliability artifacts from service expectation definitions.
Current section
Files
Jump to
Current section
Files
src/caffeine_lang/phase_4/slo_resolver.gleam
import caffeine_lang/cql/parser
import caffeine_lang/types/ast/organization
import caffeine_lang/types/ast/sli_type
import caffeine_lang/types/ast/slo
import caffeine_lang/types/common/accepted_types
import caffeine_lang/types/common/generic_dictionary
import caffeine_lang/types/resolved/resolved_sli
import caffeine_lang/types/resolved/resolved_slo
import gleam/dict
import gleam/int
import gleam/list
import gleam/result
import gleam/string
pub fn resolve_slos(
organization: organization.Organization,
) -> Result(List(resolved_slo.Slo), String) {
let sli_types =
organization.service_definitions
|> list.flat_map(fn(service_definition) {
service_definition.supported_sli_types
})
|> list.unique
organization.teams
|> list.map(fn(team) {
team.slos
|> list.map(fn(slo) { resolve_slo(slo, team.name, sli_types) })
|> result.all
})
|> result.all
|> result.map(list.flatten)
}
pub fn resolve_slo(
slo: slo.Slo,
team_name: String,
sli_types: List(sli_type.SliType),
) -> Result(resolved_slo.Slo, String) {
let assert Ok(sli_type) =
sli_types
|> list.find(fn(sli_type) { sli_type.name == slo.sli_type })
// Convert typed instantiation to Dict(String, String)
let filters_dict =
generic_dictionary.to_string_dict(
slo.typed_instatiation_of_query_templatized_variables,
)
use resolve_sli <- result.try(resolve_sli(filters_dict, sli_type, slo.name))
Ok(resolved_slo.Slo(
window_in_days: slo.window_in_days,
threshold: slo.threshold,
service_name: slo.service_name,
team_name: team_name,
sli: resolve_sli,
))
}
pub fn resolve_sli(
filters: dict.Dict(String, String),
sli_type: sli_type.SliType,
slo_name: String,
) -> Result(resolved_sli.Sli, String) {
use resolved_queries <- result.try(
sli_type.typed_instatiation_of_query_templates
|> generic_dictionary.to_string_dict
|> dict.to_list
|> list.try_map(fn(pair) {
let #(metric_attribute, template) = pair
// Process all template variables in the template string
use processed <- result.try(process_template_string(
template,
filters,
sli_type,
))
Ok(#(metric_attribute, processed))
})
|> result.map(dict.from_list),
)
// Resolve the CQL query by substituting words with resolved query values
use resolved_query <- result.try(resolve_cql_query(
sli_type.query_template_type.query,
resolved_queries,
))
Ok(resolved_sli.Sli(
name: slo_name,
query_template_type: sli_type.query_template_type,
metric_attributes: resolved_queries,
resolved_query: resolved_query,
))
}
fn resolve_cql_query(
query: parser.ExpContainer,
resolved_queries: dict.Dict(String, String),
) -> Result(parser.ExpContainer, String) {
case query {
parser.ExpContainer(exp) -> {
use resolved_exp <- result.try(resolve_exp(exp, resolved_queries))
Ok(parser.ExpContainer(resolved_exp))
}
}
}
fn resolve_exp(
exp: parser.Exp,
resolved_queries: dict.Dict(String, String),
) -> Result(parser.Exp, String) {
case exp {
parser.OperatorExpr(left, right, op) -> {
use resolved_left <- result.try(resolve_exp(left, resolved_queries))
use resolved_right <- result.try(resolve_exp(right, resolved_queries))
Ok(parser.OperatorExpr(resolved_left, resolved_right, op))
}
parser.Primary(primary) -> {
use resolved_primary <- result.try(resolve_primary(
primary,
resolved_queries,
))
Ok(parser.Primary(resolved_primary))
}
}
}
fn resolve_primary(
primary: parser.Primary,
resolved_queries: dict.Dict(String, String),
) -> Result(parser.Primary, String) {
case primary {
parser.PrimaryWord(word) -> {
case word {
parser.Word(word_value) -> {
case dict.get(resolved_queries, word_value) {
Ok(resolved_value) -> {
// Parse the resolved value as a new expression
case parser.parse_expr(resolved_value) {
Ok(parsed_exp) -> {
case parsed_exp {
parser.ExpContainer(exp) -> Ok(parser.PrimaryExp(exp))
}
}
Error(_) -> {
// If parsing fails, treat as a literal word
Ok(parser.PrimaryWord(parser.Word(resolved_value)))
}
}
}
Error(_) -> {
// Word not found in resolved queries, keep as is
Ok(parser.PrimaryWord(word))
}
}
}
}
}
parser.PrimaryExp(exp) -> {
use resolved_exp <- result.try(resolve_exp(exp, resolved_queries))
Ok(parser.PrimaryExp(resolved_exp))
}
}
}
// Process a complete template string, finding and replacing all template variables
fn process_template_string(
template: String,
filters: dict.Dict(String, String),
sli_type: sli_type.SliType,
) -> Result(String, String) {
// Find all template variables by splitting on $$
let parts = string.split(template, "$$")
// Process parts: odd indices are template variables, even indices are literal text
use result <- result.try(process_template_parts(parts, [], filters, sli_type, False))
// Clean up any remaining comma issues
Ok(cleanup_empty_optionals(result))
}
// Helper to process template parts alternating between literal text and variables
fn process_template_parts(
parts: List(String),
acc: List(String),
filters: dict.Dict(String, String),
sli_type: sli_type.SliType,
is_variable: Bool,
) -> Result(String, String) {
case parts {
[] -> Ok(string.join(list.reverse(acc), ""))
[part, ..rest] -> {
case is_variable {
False -> {
// This is literal text, keep as is
process_template_parts(rest, [part, ..acc], filters, sli_type, True)
}
True -> {
// This is a template variable, process it
use replacement <- result.try(process_template_variable(
part,
filters,
sli_type,
))
process_template_parts(rest, [replacement, ..acc], filters, sli_type, False)
}
}
}
}
}
// Clean up hanging commas and spaces from empty optional fields
// This handles edge cases where multiple consecutive optional fields are missing
fn cleanup_empty_optionals(query: String) -> String {
query
// Remove multiple consecutive ", , " patterns (handles 2+ missing optionals)
|> string.replace(", , , ", ", ")
|> string.replace(", , ", ", ")
// Remove leading comma after opening brace (all initial optionals missing)
|> string.replace("{, ", "{")
// Remove trailing comma before closing brace (all trailing optionals missing)
|> string.replace(", }", "}")
// Remove trailing comma before closing paren
|> string.replace(", )", ")")
// Handle case where only commas remain in braces
|> string.replace("{,}", "{}")
}
// Parse and process a single template variable
fn process_template_variable(
template_var: String,
filters: dict.Dict(String, String),
sli_type: sli_type.SliType,
) -> Result(String, String) {
// Parse the template variable to extract components
use #(field_name, var_name, is_negated) <- result.try(parse_template_variable(
template_var,
))
// Check if this is an optional type
case find_filter_type(sli_type, var_name) {
Ok(accepted_types.Optional(_)) -> {
// For optional types, if not provided, return empty string
case dict.get(filters, var_name) {
Ok(value) -> {
use processed_value <- result.try(process_filter_value(
value,
sli_type,
var_name,
field_name,
))
// Apply negation if needed
case is_negated {
True -> Ok("!(" <> processed_value <> ")")
False -> Ok(processed_value)
}
}
Error(_) -> Ok("")
}
}
_ -> {
// For non-optional types, require the value
case dict.get(filters, var_name) {
Ok(value) -> {
// Process the value based on its type
use processed_value <- result.try(process_filter_value(
value,
sli_type,
var_name,
field_name,
))
// Apply negation if needed
case is_negated {
True -> Ok("!(" <> processed_value <> ")")
False -> Ok(processed_value)
}
}
Error(_) ->
Error(
"Template variable '" <> var_name <> "' not found in filters",
)
}
}
}
}
// Parse template variable string to extract field name, variable name, and negation flag
// Input: "$$field->var$$" or "$$NOT[field->var]$$"
// Output: #(field_name, var_name, is_negated)
fn parse_template_variable(
template_var: String,
) -> Result(#(String, String, Bool), String) {
// Remove $$ markers
let content =
template_var
|> string.replace("$$", "")
// Check for NOT prefix
let #(content, is_negated) = case string.starts_with(content, "NOT[") {
True -> {
let inner =
content
|> string.replace("NOT[", "")
|> string.replace("]", "")
#(inner, True)
}
False -> #(content, False)
}
// Split by -> to get field and variable name
case string.split(content, "->") {
[field_name, var_name] -> Ok(#(field_name, var_name, is_negated))
_ ->
Error(
"Invalid template variable format: " <> template_var <> ". Expected $$field->var$$ or $$NOT[field->var]$$",
)
}
}
fn process_filter_value(
value: String,
sli_type: sli_type.SliType,
filter_name: String,
field_name: String,
) -> Result(String, String) {
// Check the filter type
case find_filter_type(sli_type, filter_name) {
Ok(accepted_types.NonEmptyList(inner_type)) -> {
case inner_type {
accepted_types.String -> {
case parse_list_value(value, inner_parse_string) {
Ok(parsed_list) -> {
case parsed_list {
[] ->
Error("Empty list not allowed for NonEmptyList field '" <> filter_name <> "': must contain at least one value")
_ -> Ok(convert_list_to_or_expression(parsed_list, field_name))
}
}
Error(err) -> Error("Error parsing NonEmptyList field '" <> filter_name <> "': " <> err)
}
}
accepted_types.Integer -> {
case parse_list_value(value, inner_parse_int) {
Ok(parsed_list) -> {
case parsed_list {
[] ->
Error("Empty list not allowed for NonEmptyList field '" <> filter_name <> "': must contain at least one value")
_ ->
Ok(
convert_list_to_or_expression(
list.map(parsed_list, int.to_string),
field_name,
),
)
}
}
Error(err) -> Error("Error parsing NonEmptyList field '" <> filter_name <> "': " <> err)
}
}
_ -> Ok(field_name <> ":" <> value)
}
}
Ok(accepted_types.Optional(inner_type)) -> {
// For optional types, process the inner type
case inner_type {
accepted_types.String -> Ok(field_name <> ":" <> value)
accepted_types.Integer -> Ok(field_name <> ":" <> value)
accepted_types.Boolean -> Ok(field_name <> ":" <> value)
accepted_types.Decimal -> Ok(field_name <> ":" <> value)
_ -> Ok(field_name <> ":" <> value)
}
}
_ -> Ok(field_name <> ":" <> value)
}
}
pub fn convert_list_to_or_expression(items: List(String), field_name: String) -> String {
case items {
[] -> "[]"
[single] -> field_name <> ":" <> single
_multiple -> {
let or_parts = list.map(items, fn(item) { field_name <> ":" <> item })
"(" <> string.join(or_parts, " OR ") <> ")"
}
}
}
pub fn parse_list_value(
value: String,
inner_parse: fn(String) -> Result(a, String),
) -> Result(List(a), String) {
let splitted = string.split(value, "]")
let splitted = string.split(string.join(splitted, ""), "[")
let content = string.join(splitted, "") |> string.trim
// Handle empty list case
case content {
"" -> Error("Empty list not allowed: list must contain at least one value")
_ -> {
let parse_result =
content
|> string.split(",")
|> list.map(inner_parse)
|> result.all
case parse_result {
Error(parse_error) -> Error("Failed to parse list values: " <> parse_error)
Ok(parsed_list) -> Ok(parsed_list)
}
}
}
}
pub fn inner_parse_string(value: String) -> Result(String, String) {
let splitted = string.split(value, "\"")
let result =
string.join(splitted, "")
|> string.trim
Ok(result)
}
pub fn inner_parse_int(value: String) -> Result(Int, String) {
case int.parse(string.trim(value)) {
Ok(int_value) -> Ok(int_value)
Error(_) -> Error("Invalid integer value: " <> value)
}
}
fn find_filter_type(
sli_type: sli_type.SliType,
filter_name: String,
) -> Result(accepted_types.AcceptedTypes, String) {
sli_type.specification_of_query_templatized_variables
|> list.find(fn(basic_type) { basic_type.attribute_name == filter_name })
|> result.map(fn(basic_type) { basic_type.attribute_type })
|> result.replace_error("Filter type not found")
}