shape README

The project's README from github.com/rhevorn/shape. As one markdown file: README.md.

shape

Type-safe validation, transformation, and JSON binding for Go.

shape lets you describe a struct once, then reuse that definition to:

The public API uses Go generics. Invalid field names and incompatible field types are rejected when a schema is created, while normal method and argument mistakes are caught by the Go compiler.

Install

go get github.com/rhevorn/shape

Requires Go 1.24 or newer. Runtime packages use only the standard library.

Quick start

This is a complete program:

package main

import (
	"fmt"

	"github.com/rhevorn/shape"
)

type User struct {
	Name string   `json:"name"`
	Age  int      `json:"age"`
	Tags []string `json:"tags"`
}

var userSchema = shape.New[User](
	shape.String("Name").Trim().NotEmpty().MaxLength(50),
	shape.Int("Age").Min(18).Max(120),
	shape.Slice("Tags", shape.String().Trim().NotEmpty()).
		NotEmpty().
		Unique(),
)

func main() {
	user, err := userSchema.ParseJSON([]byte(`{
		"name": " Pong ",
		"age": 20,
		"tags": [" go ", "shape"]
	}`))
	if err != nil {
		panic(err)
	}

	fmt.Printf("%#v\n", user)
	// main.User{Name:"Pong", Age:20, Tags:[]string{"go", "shape"}}
}

Transformation steps keep the order in which methods are called, and validation rules do the same. A schema JSON operation completes all transformation steps before it starts validation, so NotEmpty sees the trimmed string.

One schema, three operations

Use the same schema with typed Go values or JSON:

raw := User{Name: " Pong ", Age: 20, Tags: []string{" go "}}

user, err := userSchema.Transform(raw)
err = userSchema.Validate(user)

user, err = userSchema.ParseJSON([]byte(`{
	"name": " Pong ",
	"age": 20,
	"tags": [" go "]
}`))

The responsibilities are deliberately separate:

All operations also have context-aware forms.

Struct tags

Tags are a shorter alternative for straightforward request structs:

type CreateUserRequest struct {
	Name string `json:"name" shape:"trim,notempty,maxlength=50"`
	Age  int    `json:"age" shape:"min=18,max=120"`
}

func decodeRequest(data []byte) (CreateUserRequest, error) {
	var request CreateUserRequest
	err := shape.BindJSON(&request, data)
	return request, err
}

BindJSON changes the destination only after decoding, transformation, and validation all succeed. Use shape.Struct[CreateUserRequest]() when the tagged schema needs to be stored or reused.

Tagged schemas can add cross-field behavior with shape.Struct[T]().Apply(...).Refine(...). Use explicit shape.New[T](...) schemas when field behavior should be written in Go rather than tags.

Validation without a schema

The validate package works independently:

username := validate.String().
	NotEmpty().
	MinLength(3).
	Pattern(`^[a-z0-9_]+$`)

err := username.Validate("pong")

It provides validators for strings, numbers, booleans, times, durations, pointers, slices, maps, and custom values.

Transformation without a schema

The transform package also works independently:

canonicalName := transform.String().
	Trim().
	ToLower()

name, err := canonicalName.Transform(" Pong ")
// name is "pong"

Transformers never perform validation. Custom transformations use Apply or ApplyContext.

JSON options

JSON decoding can reject unknown object fields and limit input size:

options := shape.JSONOptions{
	DisallowUnknownFields: true,
	MaxBytes:              1 << 20,
}

user, err := userSchema.ParseJSON(data, options)

Reader, context, Parse, and Bind variants are listed in the API contract.

Errors and languages

Validation errors contain stable issue codes and paths:

var validationError *validate.Error
if errors.As(err, &validationError) {
	for _, issue := range validationError.Issues {
		fmt.Println(issue.Code, issue.Path.String(), issue.Message)
	}
}

Built-in messages support validate.English and validate.SimplifiedChinese. A locale can be attached to a context with validate.WithLocale.

Static checking

Go checks fluent method names, generic types, callback signatures, and argument types. The optional shapevet analyzer additionally checks statically visible struct tags and explicit schema field names:

go install github.com/rhevorn/shape/tools/shapevet@latest
go vet -vettool="$(which shapevet)" ./...

shapevet is optional. Schema construction still rejects invalid definitions at runtime when the analyzer is not installed.

Documentation

JSON Schema Draft 2020-12 and OpenAPI 3.1 adapters are available in the jsonschema and openapi packages. See the export example.

GitHub topics

go, golang, validation, validator, schema-validation, data-validation, struct-tags, json-validation, data-normalization, type-safe

License

MIT

shape (rhevorn/shape)

Searching Database…

181 ·GitHub
What is this?Shape is a type-safe, dependency-free schema validation library for Go that lets you describe a struct once and reuse it to transform, validate, and parse JSON into Go values.
Is it a framework or a library?It is a Go library made of composable packages (validate, transform, jsonschema, openapi) with a generics-based public API, not a framework or standalone app.
How do I add it to a project?Install it as a Go module (requires Go 1.24+), then define a schema with shape.NewT or struct tags and call Transform, Validate, ValidateFirst, or ParseJSON/BindJSON on it.
Do I need any external dependencies to run it?No, the runtime packages rely only on the Go standard library, so there are no third-party dependencies to install.
Any install pitfalls or gotchas with schemas?Invalid field names and incompatible field types are caught when a schema is created, and the optional shapevet static analyzer is needed for extra tag/schema checks at compile time; without it, bad definitions are only caught at runtime.
Does validation stop at the first error or collect all of them?You can choose: Validate collects every issue found, while ValidateFirst stops as soon as the first issue is hit.
Can I generate JSON Schema or OpenAPI definitions from a shape schema?Yes, the jsonschema package produces JSON Schema Draft 2020-12 output and the openapi package produces OpenAPI 3.1 adapters from the same schema definitions.
Are validation error messages only in English?No, built-in error messages support multiple locales out of the box, including English and Simplified Chinese, and you can attach a locale via context.
What is shapevet?shapevet is an optional static analyzer that checks fluent method names, struct tags, and explicit schema field names at compile time; without it, invalid schema definitions are still caught at runtime instead.
Creator
rhevorn
Size
0.8 MB
Stars
181
Updated
CommitSep 20
Age
1mo
Commits · 90 Days