An ATProto Lexicon validator for Gleam.
at main 9.0 kB view raw
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}