A powerful validator philosophically inspired by Bahlil Lahadalia
Bahlidator is a Zod-like typescript schema-based validation library designed for Go that validates any kind of value: primitive types, complex structures, arrays, enums, dates, and deeply nested combinations of all of them.
It was built with the concepts below:
- Technically inspired by the excellent
zodTypeScript library. - Philosophically inspired by Bahlil Lahadalia, Indonesia’s
uselessMinister of Energy and Mineral Resources.
Because sometimes software exists to remind us that:
If an incompetent figure like Bahlil can pass validation in real life, your data should at least be held to a higher standard.
- 🔤 Rich Primitives: Fluent validation for
string,number, andbooleantypes with checks likeMin,Max,Regex,Positive, etc. - 🧩 Complex Structures: Validate
map[string]anyor customstructs against a shape of schemas, with support forjsonandbtags. - 🌳 Deeply Nested Validation: Recursively validate nested objects and arrays.
- 📦 Arrays: Check array
Min,Max,Length, and whether itContainsa specific element. Validates each element against a given schema. - 📋 Enums: Ensure a value is one of a predefined set of values.
- 📅 Dates: Validate
time.Timeor date-strings (RFC3339) withMin,Max, andBetween. - 🧠 Fluent API: Chainable, Zod-like methods for building complex schemas declaratively.
- 🧪 Path-Aware Errors: Get a full list of validation errors with paths (e.g.,
user.profile.age,items[2].name) to easily locate issues. - 🎨 Customizable Messages: Override default error messages for any rule.
go get github.com/ivanj26/bahlidatorimport "github.com/ivanj26/bahlidator/b"
schema := b.String().
Min(1).
Max(255).
Required()
isValid, err := schema.Validate("hello world") // true, nilimport "github.com/ivanj26/bahlidator/b"
// Numeric schema uses a generic type of numeric (unsigned integers, signed integers, and floating number)
// You can define the specific numeric type in your schema
schemaInt := b.Numeric[int]().
Min(1).
Max(10)
isValid, errs := schemaInt.Validate(1) // true, nil
// As float64
schemaFloat64 := b.Numeric[float64]().
Min(1.0).
Max(10.0)
isValid, errs := schemaFloat64.Validate(1.0) // true, nilimport "github.com/ivanj26/bahlidator/b"
type Role int
const (
SuperAdmin Role = 0
Admin Role = 1
User Role = 2
)
schema := b.Enum([]Role{Admin, User, SuperAdmin}).Required()
isValid, err := schema.Validate(Admin) // true, nilimport (
"time"
"github.com/ivanj26/bahlidator/b"
)
schema := b.Date[time.Time]().
Min(time.Now()).
Required()
isValid, err := schema.Validate(time.Now()) // true, nilimport "github.com/ivanj26/bahlidator/b"
schema := b.Array(
b.String().Min(1),
).Min(1)
isValid, err := schema.Validate([]string{"hello", "world"}) // true, nilimport (
"fmt"
"github.com/ivanj26/bahlidator/b"
"github.com/ivanj26/bahlidator/b/schemas"
)
schema := b.Object(map[string]schemas.BaseSchema{
"user": b.Object(map[string]schemas.BaseSchema{
"email": b.String().Required(),
"fullName": b.String().Min(3),
}),
"items": b.Array(
b.Object(map[string]schemas.BaseSchema{
"name": b.String().Required(),
}),
).Min(1),
})
// Call .Validate() to validate against the input
// `obj` can be a map[string]any or your own struct with "json" or "b" tags
obj := map[string]any{
"user": map[string]any{
"email": "test@example.com",
"fullName": "Bahlil",
},
"items": []map[string]any{
{"name": "Laptop"},
},
}
isValid, errs := schema.Validate(obj)
if !isValid {
for _, e := range errs.Errors {
fmt.Printf("Field: %s, Error: %s\n", *e.Path, e.Message)
}
}Bahlidator validates data the way reality validates authority:
- It validates everything, not just objects
- It is strict where it matters and explicit when it fails
- Confidence alone is not enough — unlike certain real-world systems
- All errors are returned, none are silently ignored
Errors are aggregated and path-aware:
user.email: string is required and cannot be empty
user.fullName: string length is less than the minimum allowed
items[0].name: string is required and cannot be empty
No panics. No guesswork. (Unlike policy decisions.)
You can override the default error message for any validation rule by passing opts.WithCustomMessage("your message") as the last argument.
import "github.com/ivanj26/bahlidator/b"
import "github.com/ivanj26/bahlidator/b/opts"
schema := b.
String().
Min(
5,
opts.WithCustomMessage("Input string is too short, must be at least 5 characters."),
)
isValid, errs := schema.Validate("oops")
if !isValid {
// errs.Errors[0].Message will be "Input string is too short, must be at least 5 characters."
}Contributions are welcome.
Please:
- document schema behavior changes
- avoid unnecessary reflection
- preserve backward compatibility where possible
- do not introduce panics or magical behavior
MIT License
