An ATProto Lexicon validator for Gleam.
1import gleam/json
2import gleeunit
3import gleeunit/should
4import honk/errors
5import honk/validation/context
6import honk/validation/field
7
8pub fn main() {
9 gleeunit.main()
10}
11
12// Test valid object schema
13pub fn valid_object_schema_test() {
14 let schema =
15 json.object([
16 #("type", json.string("object")),
17 #(
18 "properties",
19 json.object([
20 #("title", json.object([#("type", json.string("string"))])),
21 #("count", json.object([#("type", json.string("integer"))])),
22 ]),
23 ),
24 #("required", json.array([json.string("title")], fn(x) { x })),
25 ])
26
27 let assert Ok(ctx) = context.builder() |> context.build
28 let result = field.validate_object_schema(schema, ctx)
29 result |> should.be_ok
30}
31
32// Test valid object data
33pub fn valid_object_data_test() {
34 let schema =
35 json.object([
36 #("type", json.string("object")),
37 #(
38 "properties",
39 json.object([
40 #("title", json.object([#("type", json.string("string"))])),
41 #("count", json.object([#("type", json.string("integer"))])),
42 ]),
43 ),
44 #("required", json.array([json.string("title")], fn(x) { x })),
45 ])
46
47 let data =
48 json.object([
49 #("title", json.string("Hello")),
50 #("count", json.int(42)),
51 ])
52
53 let assert Ok(ctx) = context.builder() |> context.build
54 let result = field.validate_object_data(data, schema, ctx)
55 result |> should.be_ok
56}
57
58// Test missing required field
59pub fn missing_required_field_test() {
60 let schema =
61 json.object([
62 #("type", json.string("object")),
63 #(
64 "properties",
65 json.object([
66 #("title", json.object([#("type", json.string("string"))])),
67 ]),
68 ),
69 #("required", json.array([json.string("title")], fn(x) { x })),
70 ])
71
72 let data = json.object([#("other", json.string("value"))])
73
74 let assert Ok(ctx) = context.builder() |> context.build
75 let result = field.validate_object_data(data, schema, ctx)
76 result |> should.be_error
77}
78
79// Test missing required field error message at root level (no path)
80pub fn missing_required_field_message_root_test() {
81 let schema =
82 json.object([
83 #("type", json.string("object")),
84 #(
85 "properties",
86 json.object([
87 #("title", json.object([#("type", json.string("string"))])),
88 ]),
89 ),
90 #("required", json.array([json.string("title")], fn(x) { x })),
91 ])
92
93 let data = json.object([#("other", json.string("value"))])
94
95 let assert Ok(ctx) = context.builder() |> context.build
96 let assert Error(error) = field.validate_object_data(data, schema, ctx)
97
98 let error_message = errors.to_string(error)
99 error_message
100 |> should.equal("Data validation failed: required field 'title' is missing")
101}
102
103// Test nullable field accepts null value
104pub fn nullable_field_accepts_null_test() {
105 let schema =
106 json.object([
107 #("type", json.string("object")),
108 #(
109 "properties",
110 json.object([
111 #("name", json.object([#("type", json.string("string"))])),
112 #("duration", json.object([#("type", json.string("integer"))])),
113 ]),
114 ),
115 #("nullable", json.array([json.string("duration")], fn(x) { x })),
116 ])
117
118 let data =
119 json.object([
120 #("name", json.string("test")),
121 #("duration", json.null()),
122 ])
123
124 let assert Ok(ctx) = context.builder() |> context.build
125 let result = field.validate_object_data(data, schema, ctx)
126 result |> should.be_ok
127}
128
129// Test non-nullable field rejects null value
130pub fn non_nullable_field_rejects_null_test() {
131 let schema =
132 json.object([
133 #("type", json.string("object")),
134 #(
135 "properties",
136 json.object([
137 #("name", json.object([#("type", json.string("string"))])),
138 #("count", json.object([#("type", json.string("integer"))])),
139 ]),
140 ),
141 // No nullable array - count cannot be null
142 ])
143
144 let data =
145 json.object([
146 #("name", json.string("test")),
147 #("count", json.null()),
148 ])
149
150 let assert Ok(ctx) = context.builder() |> context.build
151 let result = field.validate_object_data(data, schema, ctx)
152 result |> should.be_error
153}
154
155// Test nullable field must exist in properties (schema validation)
156pub fn nullable_field_not_in_properties_fails_test() {
157 let schema =
158 json.object([
159 #("type", json.string("object")),
160 #(
161 "properties",
162 json.object([
163 #("name", json.object([#("type", json.string("string"))])),
164 ]),
165 ),
166 // "nonexistent" is not in properties
167 #("nullable", json.array([json.string("nonexistent")], fn(x) { x })),
168 ])
169
170 let assert Ok(ctx) = context.builder() |> context.build
171 let result = field.validate_object_schema(schema, ctx)
172 result |> should.be_error
173}
174
175// Test valid nullable schema passes validation
176pub fn valid_nullable_schema_test() {
177 let schema =
178 json.object([
179 #("type", json.string("object")),
180 #(
181 "properties",
182 json.object([
183 #("name", json.object([#("type", json.string("string"))])),
184 #("duration", json.object([#("type", json.string("integer"))])),
185 ]),
186 ),
187 #("nullable", json.array([json.string("duration")], fn(x) { x })),
188 ])
189
190 let assert Ok(ctx) = context.builder() |> context.build
191 let result = field.validate_object_schema(schema, ctx)
192 result |> should.be_ok
193}