An ATProto Lexicon validator for Gleam.
1import gleam/json
2import gleeunit
3import gleeunit/should
4import honk/errors
5import honk/validation/context
6import honk/validation/primary/record
7
8pub fn main() {
9 gleeunit.main()
10}
11
12// Test complete record with nested objects and arrays
13pub fn complex_record_test() {
14 let schema =
15 json.object([
16 #("type", json.string("record")),
17 #("key", json.string("tid")),
18 #(
19 "record",
20 json.object([
21 #("type", json.string("object")),
22 #(
23 "required",
24 json.array([json.string("title"), json.string("tags")], fn(x) { x }),
25 ),
26 #(
27 "properties",
28 json.object([
29 #(
30 "title",
31 json.object([
32 #("type", json.string("string")),
33 #("maxLength", json.int(100)),
34 ]),
35 ),
36 #("description", json.object([#("type", json.string("string"))])),
37 #(
38 "tags",
39 json.object([
40 #("type", json.string("array")),
41 #("items", json.object([#("type", json.string("string"))])),
42 #("maxLength", json.int(10)),
43 ]),
44 ),
45 #(
46 "metadata",
47 json.object([
48 #("type", json.string("object")),
49 #(
50 "properties",
51 json.object([
52 #(
53 "views",
54 json.object([#("type", json.string("integer"))]),
55 ),
56 #(
57 "published",
58 json.object([#("type", json.string("boolean"))]),
59 ),
60 ]),
61 ),
62 ]),
63 ),
64 ]),
65 ),
66 ]),
67 ),
68 ])
69
70 let assert Ok(ctx) = context.builder() |> context.build
71 let result = record.validate_schema(schema, ctx)
72 result |> should.be_ok
73}
74
75// Test valid complex record data
76pub fn complex_record_data_test() {
77 let schema =
78 json.object([
79 #("type", json.string("record")),
80 #("key", json.string("tid")),
81 #(
82 "record",
83 json.object([
84 #("type", json.string("object")),
85 #("required", json.array([json.string("title")], fn(x) { x })),
86 #(
87 "properties",
88 json.object([
89 #("title", json.object([#("type", json.string("string"))])),
90 #(
91 "tags",
92 json.object([
93 #("type", json.string("array")),
94 #("items", json.object([#("type", json.string("string"))])),
95 ]),
96 ),
97 ]),
98 ),
99 ]),
100 ),
101 ])
102
103 let data =
104 json.object([
105 #("title", json.string("My Post")),
106 #(
107 "tags",
108 json.array([json.string("tech"), json.string("gleam")], fn(x) { x }),
109 ),
110 ])
111
112 let assert Ok(ctx) = context.builder() |> context.build
113 let result = record.validate_data(data, schema, ctx)
114 result |> should.be_ok
115}
116
117// Test record data missing required field
118pub fn complex_record_missing_required_test() {
119 let schema =
120 json.object([
121 #("type", json.string("record")),
122 #("key", json.string("tid")),
123 #(
124 "record",
125 json.object([
126 #("type", json.string("object")),
127 #("required", json.array([json.string("title")], fn(x) { x })),
128 #(
129 "properties",
130 json.object([
131 #("title", json.object([#("type", json.string("string"))])),
132 ]),
133 ),
134 ]),
135 ),
136 ])
137
138 let data = json.object([#("description", json.string("No title"))])
139
140 let assert Ok(ctx) = context.builder() |> context.build
141 let result = record.validate_data(data, schema, ctx)
142 result |> should.be_error
143}
144
145// Test deeply nested object structure
146pub fn deeply_nested_object_test() {
147 let schema =
148 json.object([
149 #("type", json.string("record")),
150 #("key", json.string("any")),
151 #(
152 "record",
153 json.object([
154 #("type", json.string("object")),
155 #(
156 "properties",
157 json.object([
158 #(
159 "level1",
160 json.object([
161 #("type", json.string("object")),
162 #(
163 "properties",
164 json.object([
165 #(
166 "level2",
167 json.object([
168 #("type", json.string("object")),
169 #(
170 "properties",
171 json.object([
172 #(
173 "level3",
174 json.object([#("type", json.string("string"))]),
175 ),
176 ]),
177 ),
178 ]),
179 ),
180 ]),
181 ),
182 ]),
183 ),
184 ]),
185 ),
186 ]),
187 ),
188 ])
189
190 let assert Ok(ctx) = context.builder() |> context.build
191 let result = record.validate_schema(schema, ctx)
192 result |> should.be_ok
193}
194
195// Test array of arrays
196pub fn array_of_arrays_test() {
197 let schema =
198 json.object([
199 #("type", json.string("record")),
200 #("key", json.string("tid")),
201 #(
202 "record",
203 json.object([
204 #("type", json.string("object")),
205 #(
206 "properties",
207 json.object([
208 #(
209 "matrix",
210 json.object([
211 #("type", json.string("array")),
212 #(
213 "items",
214 json.object([
215 #("type", json.string("array")),
216 #(
217 "items",
218 json.object([#("type", json.string("integer"))]),
219 ),
220 ]),
221 ),
222 ]),
223 ),
224 ]),
225 ),
226 ]),
227 ),
228 ])
229
230 let assert Ok(ctx) = context.builder() |> context.build
231 let result = record.validate_schema(schema, ctx)
232 result |> should.be_ok
233}
234
235// Test missing required field error message at record root level
236pub fn record_missing_required_field_message_test() {
237 let schema =
238 json.object([
239 #("type", json.string("record")),
240 #("key", json.string("tid")),
241 #(
242 "record",
243 json.object([
244 #("type", json.string("object")),
245 #("required", json.array([json.string("title")], fn(x) { x })),
246 #(
247 "properties",
248 json.object([
249 #("title", json.object([#("type", json.string("string"))])),
250 ]),
251 ),
252 ]),
253 ),
254 ])
255
256 let data = json.object([#("description", json.string("No title"))])
257
258 let assert Ok(ctx) = context.builder() |> context.build
259 let assert Error(error) = record.validate_data(data, schema, ctx)
260
261 let error_message = errors.to_string(error)
262 error_message
263 |> should.equal("Data validation failed: required field 'title' is missing")
264}
265
266// Test missing required field error message in nested object
267pub fn record_nested_missing_required_field_message_test() {
268 let schema =
269 json.object([
270 #("type", json.string("record")),
271 #("key", json.string("tid")),
272 #(
273 "record",
274 json.object([
275 #("type", json.string("object")),
276 #(
277 "properties",
278 json.object([
279 #("title", json.object([#("type", json.string("string"))])),
280 #(
281 "metadata",
282 json.object([
283 #("type", json.string("object")),
284 #(
285 "required",
286 json.array([json.string("author")], fn(x) { x }),
287 ),
288 #(
289 "properties",
290 json.object([
291 #(
292 "author",
293 json.object([#("type", json.string("string"))]),
294 ),
295 #("tags", json.object([#("type", json.string("string"))])),
296 ]),
297 ),
298 ]),
299 ),
300 ]),
301 ),
302 ]),
303 ),
304 ])
305
306 let data =
307 json.object([
308 #("title", json.string("My Post")),
309 #("metadata", json.object([#("tags", json.string("tech"))])),
310 ])
311
312 let assert Ok(ctx) = context.builder() |> context.build
313 let assert Error(error) = record.validate_data(data, schema, ctx)
314
315 let error_message = errors.to_string(error)
316 error_message
317 |> should.equal(
318 "Data validation failed: metadata: required field 'author' is missing",
319 )
320}