# Content Item Metadata Contracts # # Primitive validation contracts for post metadata fields. # Imported by post_metadata.ncl and recipe_metadata.ncl. # # All contracts are | not_exported — accessible via import but excluded # from JSON export. Nickel evaluates contracts at validation time, # not at export time, so this is safe. { SlugContract | not_exported | doc "URL-safe slug: lowercase letters, digits, and hyphens only. No leading/trailing hyphens." = std.contract.custom ( fun label => fun value => if std.string.is_match "^[a-z0-9]([a-z0-9-]*[a-z0-9])?$" value then 'Ok value else 'Error { message = "Invalid slug '%{value}'.\nValid: lowercase letters, digits, hyphens only. E.g. 'my-rust-post-2024'" } ), DateContract | not_exported | doc "ISO 8601 calendar date only — no time component." = std.contract.custom ( fun label => fun value => if std.string.is_match "^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$" value then 'Ok value else 'Error { message = "Invalid date '%{value}'.\nValid format: YYYY-MM-DD (e.g. '2024-01-15')" } ), NonEmptyString | not_exported | doc "Rejects empty strings. Allows any non-empty content." = std.contract.custom ( fun label => fun value => if (value |> std.string.length) > 0 then 'Ok value else 'Error { message = "Invalid value: empty string not allowed.\nProvide at least one character." } ), DifficultyLevel | not_exported | doc "Skill level required for recipes and tutorials." = std.contract.custom ( fun label => fun value => if std.array.elem value ["beginner", "intermediate", "advanced"] then 'Ok value else 'Error { message = "Invalid difficulty '%{value}'.\nValid values: beginner | intermediate | advanced" } ), ReadTimeContract | not_exported | doc "Human-readable duration string. Must contain a digit." = std.contract.custom ( fun label => fun value => if (std.string.is_match "[0-9]" value) && ((std.string.length value) > 0) then 'Ok value else 'Error { message = "Invalid read_time '%{value}'.\nMust contain a number (e.g. '8 min read', '2 hours')" } ), TranslationEntry = { id | String | doc "Stable identifier of the translated post (shared across all language versions).", slug | String | doc "URL slug of the translated post in its target language.", }, }