Packages
caffeine_lang
3.0.3
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/middle_end/semantic_analyzer.gleam
import caffeine_lang/common/accepted_types
import caffeine_lang/common/collection_types
import caffeine_lang/common/errors.{type CompilationError}
import caffeine_lang/common/helpers.{type ValueTuple}
import caffeine_lang/common/primitive_types
import caffeine_lang/middle_end/templatizer
import caffeine_lang/middle_end/vendor.{type Vendor}
import gleam/dict
import gleam/dynamic
import gleam/dynamic/decode
import gleam/list
import gleam/option.{type Option}
import gleam/result
/// Internal representation of a parsed expectation with metadata and values.
pub type IntermediateRepresentation {
IntermediateRepresentation(
metadata: IntermediateRepresentationMetaData,
unique_identifier: String,
artifact_refs: List(String),
values: List(ValueTuple),
// TODO: make this cleaner. An option is weird.
vendor: Option(Vendor),
)
}
/// Metadata associated with an intermediate representation including organization and service identifiers.
pub type IntermediateRepresentationMetaData {
IntermediateRepresentationMetaData(
friendly_label: String,
org_name: String,
service_name: String,
blueprint_name: String,
team_name: String,
// Metadata specific to any given expectation.
misc: dict.Dict(String, String),
)
}
/// Resolves vendor and queries for a list of intermediate representations.
@internal
pub fn resolve_intermediate_representations(
irs: List(IntermediateRepresentation),
) -> Result(List(IntermediateRepresentation), CompilationError) {
irs
|> list.try_map(fn(ir) {
case ir.artifact_refs |> list.contains("SLO") {
True -> {
use ir_with_vendor <- result.try(resolve_vendor(ir))
resolve_queries(ir_with_vendor)
}
False -> Ok(ir)
}
})
}
/// Resolves the vendor string to a Vendor type for an intermediate representation.
@internal
pub fn resolve_vendor(
ir: IntermediateRepresentation,
) -> Result(IntermediateRepresentation, CompilationError) {
case
ir.values
|> list.filter(fn(value) { value.label == "vendor" })
|> list.first
{
Error(_) ->
Error(errors.SemanticAnalysisVendorResolutionError(
msg: "No vendor input for: " <> ir.unique_identifier,
))
Ok(vendor_value_tuple) -> {
// Safe to assert since already type checked in parser phase.
let assert Ok(vendor_string) =
decode.run(vendor_value_tuple.value, decode.string)
let vendor_value = vendor.resolve_vendor(vendor_string)
Ok(IntermediateRepresentation(..ir, vendor: option.Some(vendor_value)))
}
}
}
/// Resolves query templates in an intermediate representation.
@internal
pub fn resolve_queries(
ir: IntermediateRepresentation,
) -> Result(IntermediateRepresentation, CompilationError) {
case ir.vendor {
option.Some(vendor.Datadog) -> {
let assert Ok(queries_value_tuple) =
ir.values
|> list.filter(fn(vt) { vt.label == "queries" })
|> list.first
let assert Ok(queries_dict) =
decode.run(
queries_value_tuple.value,
decode.dict(decode.string, decode.string),
)
// Resolve all queries and collect results.
use resolved_queries <- result.try(
queries_dict
|> dict.to_list
|> list.try_map(fn(pair) {
let #(key, query) = pair
use resolved <- result.map(
templatizer.parse_and_resolve_query_template(query, ir.values),
)
#(key, resolved)
}),
)
// Build the new queries dict as a dynamic value.
let resolved_queries_dynamic =
resolved_queries
|> list.map(fn(pair) {
let #(key, value) = pair
#(dynamic.string(key), dynamic.string(value))
})
|> dynamic.properties
// Create the new queries ValueTuple.
let new_queries_value_tuple =
helpers.ValueTuple(
"queries",
accepted_types.CollectionType(collection_types.Dict(
accepted_types.PrimitiveType(primitive_types.String),
accepted_types.PrimitiveType(primitive_types.String),
)),
resolved_queries_dynamic,
)
// Also resolve templates in the "value" field if present.
let value_tuple_result =
ir.values
|> list.filter(fn(vt) { vt.label == "value" })
|> list.first
use resolved_value_tuple <- result.try(case value_tuple_result {
Error(_) -> Ok(option.None)
Ok(value_tuple) -> {
let assert Ok(value_string) =
decode.run(value_tuple.value, decode.string)
case
templatizer.parse_and_resolve_query_template(
value_string,
ir.values,
)
{
Ok(resolved_value) ->
Ok(
option.Some(helpers.ValueTuple(
"value",
accepted_types.PrimitiveType(primitive_types.String),
dynamic.string(resolved_value),
)),
)
Error(err) -> Error(err)
}
}
})
// Update the IR with the resolved queries and value.
let new_values =
ir.values
|> list.map(fn(vt) {
case vt.label {
"queries" -> new_queries_value_tuple
"value" ->
case resolved_value_tuple {
option.Some(new_value) -> new_value
option.None -> vt
}
_ -> vt
}
})
Ok(IntermediateRepresentation(..ir, values: new_values))
}
_ ->
Error(errors.SemanticAnalysisTemplateResolutionError(
msg: "No vendor for expectation: " <> ir.unique_identifier,
))
}
}