Rustelo/resources/nickel/content/metadata/contracts.ncl
Jesús Pérez f1010e9d07
chore: update
2026-07-18 20:16:16 +01:00

90 lines
2.8 KiB
Text

# 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.",
},
}