···2233import gleam/dict.{type Dict}
44import gleam/json.{type Json}
55+import gleam/list
56import gleam/option.{None, Some}
67import gleam/result
78import honk/errors
···3334pub type ValidationError =
3435 errors.ValidationError
35363636-/// Main validation function for lexicon documents
3737-/// Returns Ok(Nil) if all lexicons are valid
3838-/// Returns Error with a map of lexicon ID to list of error messages
3737+/// Validates lexicon documents
3838+///
3939+/// Validates lexicon structure (id, defs) and ALL definitions within each lexicon.
4040+/// Each definition in the defs object is validated according to its type.
4141+///
4242+/// Returns Ok(Nil) if all lexicons and their definitions are valid.
4343+/// Returns Error with a map of lexicon ID to list of error messages.
4444+/// Error messages include the definition name (e.g., "lex.id#defName: error").
3945pub fn validate(lexicons: List(Json)) -> Result(Nil, Dict(String, List(String))) {
4046 // Build validation context
4147 let builder_result =
···4652 Ok(builder) ->
4753 case context.build(builder) {
4854 Ok(ctx) -> {
4949- // Validate each lexicon's main definition
5555+ // Validate ALL definitions in each lexicon
5056 let error_map =
5157 dict.fold(ctx.lexicons, dict.new(), fn(errors, lex_id, lexicon) {
5252- // Validate the main definition if it exists
5353- case json_helpers.get_field(lexicon.defs, "main") {
5454- Some(main_def) -> {
5555- let lex_ctx = context.with_current_lexicon(ctx, lex_id)
5656- case validate_definition(main_def, lex_ctx) {
5757- Ok(_) -> errors
5858- Error(e) ->
5959- dict.insert(errors, lex_id, [errors.to_string(e)])
5858+ // Get all definition names from the defs object
5959+ let def_keys = json_helpers.get_keys(lexicon.defs)
6060+ let lex_ctx = context.with_current_lexicon(ctx, lex_id)
6161+6262+ // Validate each definition
6363+ list.fold(def_keys, errors, fn(errors_acc, def_name) {
6464+ case json_helpers.get_field(lexicon.defs, def_name) {
6565+ Some(def) -> {
6666+ case validate_definition(def, lex_ctx) {
6767+ Ok(_) -> errors_acc
6868+ Error(e) -> {
6969+ // Include def name in error for better context
7070+ let error_msg =
7171+ lex_id
7272+ <> "#"
7373+ <> def_name
7474+ <> ": "
7575+ <> errors.to_string(e)
7676+ case dict.get(errors_acc, lex_id) {
7777+ Ok(existing_errors) ->
7878+ dict.insert(errors_acc, lex_id, [
7979+ error_msg,
8080+ ..existing_errors
8181+ ])
8282+ Error(_) ->
8383+ dict.insert(errors_acc, lex_id, [error_msg])
8484+ }
8585+ }
8686+ }
6087 }
8888+ None -> errors_acc
6189 }
6262- None -> errors
6363- // No main definition is OK
6464- }
9090+ })
6591 })
66926793 case dict.is_empty(error_map) {