Packages
caffeine_lang
2.0.5
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/expectations.gleam
import caffeine_lang/common/decoders
import caffeine_lang/common/errors.{type CompilationError}
import caffeine_lang/common/helpers
import caffeine_lang/common/validations
import caffeine_lang/parser/blueprints.{type Blueprint}
import gleam/dict
import gleam/dynamic.{type Dynamic}
import gleam/dynamic/decode
import gleam/json
import gleam/list
import gleam/result
import gleam/string
/// An Expectation is a concrete implementation of an Artifact + Blueprint.
pub type Expectation {
Expectation(
name: String,
blueprint_ref: String,
inputs: dict.Dict(String, Dynamic),
)
}
/// Parse expectations from a file.
@internal
pub fn parse_from_json_file(
file_path: String,
blueprints: List(Blueprint),
) -> Result(List(#(Expectation, Blueprint)), CompilationError) {
use json_string <- result.try(helpers.json_from_file(file_path))
parse_from_json_string(json_string, blueprints)
}
/// Parse expectations from a JSON string.
@internal
pub fn parse_from_json_string(
json_string: String,
blueprints: List(Blueprint),
) -> Result(List(#(Expectation, Blueprint)), CompilationError) {
// Parse the JSON string.
use expectations <- result.try(
case expectations_from_json(json_string, blueprints) {
Ok(expectations) -> Ok(expectations)
Error(err) -> Error(errors.format_json_decode_error(err))
},
)
validate_expectations(expectations, blueprints)
}
/// Parse expectations from a JSON string.
@internal
pub fn expectations_from_json(
json_string: String,
blueprints: List(Blueprint),
) -> Result(List(Expectation), json.DecodeError) {
let expectation_decoded = {
use name <- decode.field("name", decoders.non_empty_string_decoder())
use blueprint_ref <- decode.field(
"blueprint_ref",
decoders.named_reference_decoder(blueprints, fn(b) { b.name }),
)
use inputs <- decode.field(
"inputs",
decode.dict(decode.string, decode.dynamic),
)
decode.success(Expectation(name:, blueprint_ref:, inputs:))
}
let expectations_decoded = {
use expectations <- decode.field(
"expectations",
decode.list(expectation_decoded),
)
decode.success(expectations)
}
json.parse(from: json_string, using: expectations_decoded)
}
/// Validate expectations and return paired with their blueprints.
/// TODO: This provides massive duplication and is an area of low hanging fruit for optimization.
fn validate_expectations(
expectations: List(Expectation),
blueprints: List(Blueprint),
) -> Result(List(#(Expectation, Blueprint)), CompilationError) {
// Map expectations to blueprints since we'll reuse that numerous times
// and we've already validated all blueprint_refs.
let expectations_blueprint_collection =
helpers.map_reference_to_referrer_over_collection(
references: blueprints,
referrers: expectations,
reference_name: fn(b) { b.name },
referrer_reference: fn(e) { e.blueprint_ref },
)
// Validate that expectation inputs don't overshadow blueprint inputs.
use _ <- result.try(check_input_overshadowing(
expectations_blueprint_collection,
))
// Validate that expectation.inputs provides params NOT already provided by blueprint.inputs.
use _ <- result.try(validations.validate_inputs_for_collection(
input_param_collections: expectations_blueprint_collection,
get_inputs: fn(expectation) { expectation.inputs },
get_params: fn(blueprint) {
let blueprint_input_keys = blueprint.inputs |> dict.keys
blueprint.params
|> dict.filter(fn(key, _) { !list.contains(blueprint_input_keys, key) })
},
missing_inputs_ok: False,
))
// Validate unique names within a file.
use _ <- result.try(validations.validate_relevant_uniqueness(
expectations,
fn(e) { e.name },
"expectation names",
))
Ok(expectations_blueprint_collection)
}
fn check_input_overshadowing(
expectations_blueprint_collection: List(#(Expectation, Blueprint)),
) -> Result(Bool, CompilationError) {
let overshadow_errors =
expectations_blueprint_collection
|> list.filter_map(fn(pair) {
let #(expectation, blueprint) = pair
case
validations.check_collection_key_overshadowing(
expectation.inputs,
blueprint.inputs,
"Expectation '"
<> expectation.name
<> "' overshadowing inputs from blueprint: ",
)
{
Ok(_) -> Error(Nil)
Error(msg) -> Ok(msg)
}
})
|> string.join(", ")
case overshadow_errors {
"" -> Ok(True)
_ -> Error(errors.ParserDuplicateError(msg: overshadow_errors))
}
}