Packages
caffeine_lang
3.0.2
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/parser/ir_builder.gleam
import caffeine_lang/common/accepted_types
import caffeine_lang/common/helpers
import caffeine_lang/common/modifier_types
import caffeine_lang/common/refinement_types
import caffeine_lang/middle_end/semantic_analyzer.{
type IntermediateRepresentation,
}
import caffeine_lang/parser/blueprints.{type Blueprint}
import caffeine_lang/parser/expectations.{type Expectation}
import gleam/dict
import gleam/dynamic
import gleam/list
import gleam/option
import gleam/string
/// Build intermediate representations from validated expectations across multiple files.
@internal
pub fn build_all(
expectations_with_paths: List(#(List(#(Expectation, Blueprint)), String)),
) -> List(IntermediateRepresentation) {
expectations_with_paths
|> list.map(fn(pair) {
let #(expectations_blueprint_collection, file_path) = pair
build(expectations_blueprint_collection, file_path)
})
|> list.flatten
}
/// Extract a meaningful prefix from the source path.
/// e.g., "examples/org/platform_team/authentication.caffeine" -> #("org", "platform_team", "authentication")
@internal
pub fn extract_path_prefix(path: String) -> #(String, String, String) {
case
path
|> string.split("/")
|> list.reverse
|> list.take(3)
|> list.reverse
|> list.map(fn(segment) {
// Remove file extension if present.
case string.ends_with(segment, ".caffeine") {
True -> string.drop_end(segment, 9)
False ->
case string.ends_with(segment, ".json") {
True -> string.drop_end(segment, 5)
False -> segment
}
}
})
{
[org, team, service] -> #(org, team, service)
// This is not actually a possible state, however for pattern matching completeness we
// include it here.
_ -> #("unknown", "unknown", "unknown")
}
}
/// Build intermediate representations from validated expectations for a single file.
fn build(
expectations_blueprint_collection: List(#(Expectation, Blueprint)),
file_path: String,
) -> List(IntermediateRepresentation) {
let #(org, team, service) = extract_path_prefix(file_path)
expectations_blueprint_collection
|> list.map(fn(expectation_and_blueprint_pair) {
let #(expectation, blueprint) = expectation_and_blueprint_pair
// Merge blueprint inputs with expectation inputs.
// Expectation inputs override blueprint inputs for the same key.
let merged_inputs = dict.merge(blueprint.inputs, expectation.inputs)
let value_tuples = build_value_tuples(merged_inputs, blueprint.params)
let misc_metadata = extract_misc_metadata(value_tuples)
let unique_name = org <> "_" <> service <> "_" <> expectation.name
semantic_analyzer.IntermediateRepresentation(
metadata: semantic_analyzer.IntermediateRepresentationMetaData(
friendly_label: expectation.name,
org_name: org,
service_name: service,
blueprint_name: blueprint.name,
team_name: team,
misc: misc_metadata,
),
unique_identifier: unique_name,
artifact_refs: blueprint.artifact_refs,
values: value_tuples,
vendor: option.None,
)
})
}
/// Build value tuples from merged inputs and params.
/// Includes both provided inputs and unprovided Optional/Defaulted params.
fn build_value_tuples(
merged_inputs: dict.Dict(String, dynamic.Dynamic),
params: dict.Dict(String, accepted_types.AcceptedTypes),
) -> List(helpers.ValueTuple) {
let provided = build_provided_value_tuples(merged_inputs, params)
let unprovided = build_unprovided_optional_value_tuples(merged_inputs, params)
list.append(provided, unprovided)
}
/// Build value tuples from provided inputs.
fn build_provided_value_tuples(
merged_inputs: dict.Dict(String, dynamic.Dynamic),
params: dict.Dict(String, accepted_types.AcceptedTypes),
) -> List(helpers.ValueTuple) {
merged_inputs
|> dict.keys
|> list.map(fn(label) {
// Safe assertions since we've already validated everything.
let assert Ok(value) = merged_inputs |> dict.get(label)
let assert Ok(typ) = params |> dict.get(label)
helpers.ValueTuple(label:, typ:, value:)
})
}
/// Build value tuples for Optional/Defaulted params that weren't provided.
/// These need to be in value_tuples so the templatizer can resolve them.
fn build_unprovided_optional_value_tuples(
merged_inputs: dict.Dict(String, dynamic.Dynamic),
params: dict.Dict(String, accepted_types.AcceptedTypes),
) -> List(helpers.ValueTuple) {
params
|> dict.to_list
|> list.filter_map(fn(param) {
let #(label, typ) = param
case dict.has_key(merged_inputs, label) {
True -> Error(Nil)
False ->
case is_optional_or_defaulted(typ) {
True -> Ok(helpers.ValueTuple(label:, typ:, value: dynamic.nil()))
False -> Error(Nil)
}
}
})
}
/// Checks if a type is optional or has a default value.
/// This includes:
/// - ModifierType(Optional(_))
/// - ModifierType(Defaulted(_, _))
/// - RefinementType(OneOf(ModifierType(Optional(_)), _))
/// - RefinementType(OneOf(ModifierType(Defaulted(_, _)), _))
fn is_optional_or_defaulted(typ: accepted_types.AcceptedTypes) -> Bool {
case typ {
accepted_types.ModifierType(modifier_types.Optional(_)) -> True
accepted_types.ModifierType(modifier_types.Defaulted(_, _)) -> True
accepted_types.RefinementType(refinement_types.OneOf(inner, _)) ->
is_optional_or_defaulted(inner)
_ -> False
}
}
/// Extract misc metadata from value tuples.
/// Filters out non-string values and specific reserved labels.
/// Uses type-aware resolution to apply defaults from Defaulted types.
fn extract_misc_metadata(
value_tuples: List(helpers.ValueTuple),
) -> dict.Dict(String, String) {
value_tuples
|> list.filter_map(fn(value_tuple) {
// Skip reserved labels
case value_tuple.label {
// TODO: Make the tag filtering dynamic.
"window_in_days" | "threshold" | "value" -> Error(Nil)
_ -> {
// Use type-aware resolution to handle defaults
case resolve_value_for_tag(value_tuple) {
Ok(value_string) -> Ok(#(value_tuple.label, value_string))
Error(_) -> Error(Nil)
}
}
}
})
|> dict.from_list
}
/// Resolves a value tuple to a string for use as a tag.
/// Handles Defaulted types by applying their default values when not provided.
fn resolve_value_for_tag(value_tuple: helpers.ValueTuple) -> Result(String, Nil) {
// Identity function for string resolution (tags don't need template transformation)
let identity = fn(s) { s }
// For lists, join with comma (though tags typically don't use lists)
let list_join = fn(items) { string.join(items, ",") }
accepted_types.resolve_to_string(
value_tuple.typ,
value_tuple.value,
identity,
list_join,
)
|> result_to_nil_error
}
/// Convert Result(a, String) to Result(a, Nil)
fn result_to_nil_error(result: Result(a, String)) -> Result(a, Nil) {
case result {
Ok(val) -> Ok(val)
Error(_) -> Error(Nil)
}
}