Packages
caffeine_lang
4.8.0
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/linker/validations.gleam
import caffeine_lang/errors.{type CompilationError}
import caffeine_lang/types.{type AcceptedTypes}
import caffeine_lang/value.{type Value}
import gleam/dict.{type Dict}
import gleam/list
import gleam/option
import gleam/result
import gleam/set
import gleam/string
/// Validates that a Value matches the expected AcceptedType.
/// Returns the original value if valid, or a CompilationError describing the type mismatch.
@internal
pub fn validate_value_type(
val: Value,
expected_type: AcceptedTypes,
type_key_identifier: String,
) -> Result(Value, CompilationError) {
types.validate_value(expected_type, val)
|> result.map_error(fn(err) {
errors.linker_value_validation_error(
msg: errors.format_validation_error_message(
err,
option.Some(type_key_identifier),
option.Some(val),
),
)
})
}
/// Validates that inputs match the expected params in both keys and types.
/// Returns an error if there are missing keys, extra keys, or type mismatches.
/// Note: Optional and Defaulted params are allowed to be omitted from inputs.
@internal
pub fn inputs_validator(
params params: Dict(String, AcceptedTypes),
inputs inputs: Dict(String, Value),
missing_inputs_ok missing_inputs_ok: Bool,
) -> Result(Nil, String) {
// Filter out optional and defaulted params - they're not required
let required_params =
params
|> dict.filter(fn(_, typ) { !types.is_optional_or_defaulted(typ) })
let required_param_keys = required_params |> dict.keys |> set.from_list
let param_keys = params |> dict.keys |> set.from_list
let input_keys = inputs |> dict.keys |> set.from_list
// Only required params must be present
let missing_required_keys =
set.difference(required_param_keys, input_keys) |> set.to_list
// Inputs must only contain keys that exist in params (required or optional)
let keys_only_in_inputs =
set.difference(input_keys, param_keys) |> set.to_list
// see if we have the same inputs and params. Extra keys are always rejected;
// missing keys only rejected when missing_inputs_ok is False
use _ <- result.try(
case missing_required_keys, keys_only_in_inputs, missing_inputs_ok {
[], [], _ -> Ok(Nil)
_, [], True -> Ok(Nil)
_, [], False ->
Error(
"Missing keys in input: "
<> { missing_required_keys |> string.join(", ") },
)
[], _, _ ->
Error(
"Extra keys in input: "
<> { keys_only_in_inputs |> string.join(", ") },
)
_, _, True ->
Error(
"Extra keys in input: "
<> { keys_only_in_inputs |> string.join(", ") },
)
_, _, False ->
Error(
"Extra keys in input: "
<> { keys_only_in_inputs |> string.join(", ") }
<> " and missing keys in input: "
<> { missing_required_keys |> string.join(", ") },
)
},
)
let type_validation_errors =
inputs
|> dict.to_list
|> list.filter_map(fn(pair) {
let #(key, value) = pair
// only validate types for keys that exist in params (extra keys handled above)
case params |> dict.get(key) {
Error(Nil) -> Error(Nil)
Ok(expected_type) ->
case validate_value_type(value, expected_type, key) {
Ok(_) -> Error(Nil)
Error(errs) -> Ok(errs)
}
}
})
|> list.map(errors.to_message)
|> string.join(", ")
case type_validation_errors {
"" -> Ok(Nil)
_ -> Error(type_validation_errors)
}
}
/// Validates that all items in a list have unique values for a given property.
/// Returns a LinkerDuplicateError listing any duplicate values found.
@internal
pub fn validate_relevant_uniqueness(
items: List(a),
by fetch_property: fn(a) -> String,
label thing_label: String,
) -> Result(Nil, CompilationError) {
let dupe_names =
items
|> list.group(fn(thing) { fetch_property(thing) })
|> dict.filter(fn(_, occurrences) { list.length(occurrences) > 1 })
|> dict.keys
case dupe_names {
[] -> Ok(Nil)
_ ->
Error(errors.linker_duplicate_error(
msg: "Duplicate "
<> thing_label
<> ": "
<> { dupe_names |> string.join(", ") },
))
}
}
/// Validates inputs against params for a collection of paired items.
/// Aggregates all validation errors across the collection into a single result.
@internal
pub fn validate_inputs_for_collection(
input_param_collections input_param_collections: List(#(a, b)),
get_inputs get_inputs: fn(a) -> Dict(String, Value),
get_params get_params: fn(b) -> Dict(String, AcceptedTypes),
with get_identifier: fn(a) -> String,
missing_inputs_ok missing_inputs_ok: Bool,
) -> Result(Nil, CompilationError) {
let validation_errors =
input_param_collections
|> list.filter_map(fn(collection) {
let #(input_collection, param_collection) = collection
case
inputs_validator(
params: get_params(param_collection),
inputs: get_inputs(input_collection),
missing_inputs_ok: missing_inputs_ok,
)
{
Ok(_) -> Error(Nil)
Error(msg) -> Ok(get_identifier(input_collection) <> " - " <> msg)
}
})
|> string.join(", ")
case validation_errors {
"" -> Ok(Nil)
_ ->
Error(errors.linker_value_validation_error(
msg: "Input validation errors: " <> validation_errors,
))
}
}
/// Validates that no items in a collection have overshadowing keys.
/// Applies `check_collection_key_overshadowing` to each pair and aggregates errors.
@internal
pub fn validate_no_overshadowing(
items: List(#(a, b)),
get_check_collection get_check_collection: fn(a) -> Dict(String, c),
get_against_collection get_against_collection: fn(b) -> Dict(String, d),
get_error_label get_error_label: fn(a) -> String,
) -> Result(Nil, CompilationError) {
let overshadow_errors =
items
|> list.filter_map(fn(pair) {
let #(item, against) = pair
case
check_collection_key_overshadowing(
in: get_check_collection(item),
against: get_against_collection(against),
with: get_error_label(item),
)
{
Ok(_) -> Error(Nil)
Error(msg) -> Ok(msg)
}
})
|> string.join(", ")
case overshadow_errors {
"" -> Ok(Nil)
_ -> Error(errors.linker_duplicate_error(msg: overshadow_errors))
}
}
/// Checks if any keys in the referrer collection overlap with the reference collection.
/// Returns an error with the overlapping keys if overshadowing is detected.
@internal
pub fn check_collection_key_overshadowing(
in reference_collection: Dict(String, a),
against referrer_collection: Dict(String, b),
with error_msg: String,
) -> Result(Nil, String) {
let reference_names = reference_collection |> dict.keys |> set.from_list
let referrer_names = referrer_collection |> dict.keys |> set.from_list
let overshadowing_params = {
set.intersection(reference_names, referrer_names) |> set.to_list
}
case overshadowing_params {
[] -> Ok(Nil)
_ -> Error(error_msg <> overshadowing_params |> string.join(", "))
}
}