Gleam-inspired typed configuration language (POC)
1import gleam/string
2import gleeunit
3import glint
4import glint/check
5import glint/pipeline
6import glint/value
7
8pub fn main() -> Nil {
9 gleeunit.main()
10}
11
12pub fn hello_loads_test() {
13 let source =
14 "
15type Mode {
16 Dev
17 Prod
18}
19
20type Config {
21 Config(
22 name: String,
23 mode: Mode,
24 port: Int,
25 )
26}
27
28pub let config = Config(
29 name: \"hello\",
30 mode: Dev,
31 port: 3000,
32)
33"
34
35 let assert Ok(checked) = pipeline.load(source)
36 let assert "Config" = check.type_to_string(checked.config_type)
37 let assert True =
38 string.contains(value.to_glint(checked.config), "\"hello\"")
39 let assert True = string.contains(value.to_json(checked.config), "hello")
40}
41
42pub fn app_example_test() {
43 let source =
44 "
45type LogLevel {
46 Debug
47 Info
48}
49
50type Database {
51 Database(
52 host: String,
53 port: Int,
54 password: Option(String),
55 )
56}
57
58type Config {
59 Config(
60 log_level: LogLevel,
61 database: Database,
62 origins: List(String),
63 )
64}
65
66let database = Database(
67 host: \"localhost\",
68 port: 5432,
69 password: None,
70)
71
72pub let config = Config(
73 log_level: Info,
74 database: database,
75 origins: [\"https://example.com\"],
76)
77"
78
79 let assert Ok(checked) = glint.load(source)
80 let assert value.VVariant("Config", fields) = checked.config
81 let assert True = list_has_tag(fields, "log_level")
82}
83
84pub fn type_mismatch_test() {
85 let source =
86 "
87type Config {
88 Config(port: Int)
89}
90
91pub let config = Config(port: \"nope\")
92"
93 let assert Error(msg) = glint.load(source)
94 let assert True = string.contains(msg, "type mismatch")
95}
96
97pub fn missing_config_test() {
98 let source =
99 "
100type Mode {
101 Dev
102}
103
104let mode = Dev
105"
106 let assert Error(msg) = glint.load(source)
107 let assert True = string.contains(msg, "pub let config")
108}
109
110pub fn unknown_constructor_test() {
111 let source =
112 "
113type Config {
114 Config(name: String)
115}
116
117pub let config = Config(name: \"x\", extra: 1)
118"
119 let assert Error(msg) = glint.load(source)
120 let assert True = string.contains(msg, "unknown field")
121}
122
123pub fn comments_and_trailing_commas_test() {
124 let source =
125 "
126// leading comment
127type Mode {
128 Dev
129 Prod
130}
131
132type Config {
133 Config(
134 mode: Mode,
135 port: Int,
136 )
137}
138
139pub let config = Config(
140 mode: Dev,
141 port: 1_000,
142)
143"
144 let assert Ok(_) = glint.load(source)
145}
146
147fn list_has_tag(fields: List(#(String, value.Value)), label: String) -> Bool {
148 case fields {
149 [] -> False
150 [#(l, _), ..rest] ->
151 case l == label {
152 True -> True
153 False -> list_has_tag(rest, label)
154 }
155 }
156}