Packages
caffeine_lang
5.1.1
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/codegen/dependency_graph.gleam
import caffeine_lang/linker/artifacts.{Hard, Soft}
import caffeine_lang/linker/ir.{
type IntermediateRepresentation, type Resolved, ir_to_identifier,
}
import gleam/dict
import gleam/list
import gleam/option
import gleam/result
import gleam/string
/// Generates a Mermaid flowchart string from dependency relations in IRs.
/// Nodes are grouped into subgraphs by service.
pub fn generate(irs: List(IntermediateRepresentation(Resolved))) -> String {
let subgraphs = build_subgraphs(irs)
let edges = build_edges(irs)
[["graph TD"], subgraphs, edges]
|> list.flatten
|> string.join("\n")
}
/// Groups IRs by service and generates Mermaid subgraph blocks.
fn build_subgraphs(
irs: List(IntermediateRepresentation(Resolved)),
) -> List(String) {
irs
|> list.group(fn(ir) { service_key(ir) })
|> dict.to_list
|> list.sort(fn(a, b) { string.compare(a.0, b.0) })
|> list.flat_map(fn(group) {
let #(service, group_irs) = group
let header =
" subgraph "
<> sanitize_id(service)
<> "[\""
<> escape_label(service)
<> "\"]"
let nodes = list.map(group_irs, build_node)
list.flatten([[header], nodes, [" end"]])
})
}
/// Builds the service grouping key from IR metadata.
fn service_key(ir: IntermediateRepresentation(Resolved)) -> String {
ir.metadata.service_name.value
}
/// Generates a single Mermaid node declaration with just the expectation name.
fn build_node(ir: IntermediateRepresentation(Resolved)) -> String {
let path = ir_to_identifier(ir)
let id = sanitize_id(path)
let safe_name = escape_label(ir.metadata.friendly_label.value)
" " <> id <> "[\"" <> safe_name <> "\"]"
}
/// Generates Mermaid edge declarations for hard and soft dependencies.
fn build_edges(irs: List(IntermediateRepresentation(Resolved))) -> List(String) {
irs
|> list.flat_map(fn(ir) {
let source_id = sanitize_id(ir_to_identifier(ir))
case ir.slo.depends_on {
option.None -> []
option.Some(relations) -> {
let hard_edges =
dict.get(relations, Hard)
|> result.unwrap([])
|> list.map(fn(target) {
" " <> source_id <> " -->|hard| " <> sanitize_id(target)
})
let soft_edges =
dict.get(relations, Soft)
|> result.unwrap([])
|> list.map(fn(target) {
" " <> source_id <> " -.->|soft| " <> sanitize_id(target)
})
list.append(hard_edges, soft_edges)
}
}
})
}
/// Escapes characters that have special meaning in Mermaid labels.
/// Uses numeric HTML entity codes (e.g. #91; for [) which are broadly compatible.
fn escape_label(text: String) -> String {
text
|> string.replace("\"", "#34;")
|> string.replace("[", "#91;")
|> string.replace("]", "#93;")
|> string.replace("(", "#40;")
|> string.replace(")", "#41;")
|> string.replace("{", "#123;")
|> string.replace("}", "#125;")
|> string.replace("<", "#60;")
|> string.replace(">", "#62;")
}
/// Strips all non-alphanumeric characters (except underscores) for Mermaid-safe node IDs.
fn sanitize_id(path: String) -> String {
path
|> string.to_graphemes
|> list.map(fn(g) {
case g {
"." | " " | "-" -> "_"
_ -> {
case is_id_char(g) {
True -> g
False -> ""
}
}
}
})
|> string.concat
}
fn is_id_char(g: String) -> Bool {
case g {
"_" -> True
_ ->
case string.to_utf_codepoints(g) {
[cp] -> {
let code = string.utf_codepoint_to_int(cp)
{ code >= 65 && code <= 90 }
|| { code >= 97 && code <= 122 }
|| { code >= 48 && code <= 57 }
}
_ -> False
}
}
}