chix 是一个基于 chi 的、强约束的 JSON API 边界内核。
它不是新的 Web 框架,也不打算替代 chi。它做的是把 chi 项目里最容易重复、最容易失控的边界动作收紧成一组稳定约定:
- path/query/header 参数读取
- JSON body 解码与 DTO 校验
- 请求错误建模
- 业务错误映射
- 成功/错误响应写回
仓库与模块路径:
github.com/kanata996/chix
安装:
go get github.com/kanata996/chix做什么:
- handler 只做解请求、调 service、写响应
- path/query/header 和 body 分流,不做大而全 binding
- 请求错误与业务错误分流,不混用
- 边界自故障 fail-closed
不做什么:
- 不引入自己的
Context/Engine/App - 不做 path/query/header/body 混合自动绑定
- 不做 ORM、配置、鉴权、OpenAPI 生成
- 不把
chi包成另一个大而全框架
chix:推荐入口,暴露 path/query/header facadereqx:JSON body 解码、DTO 校验、请求错误建模errx:业务/系统错误语义、feature mapper、Mapping 校验resp:统一成功/请求错误/业务错误响应写回
内部实现:
internal/paramx:chi下的 path/query/header 参数读取与基础解析,由根包chix对外转发
请求参数/请求体错误:
handler -> chix/reqx -> resp.Problem
业务/系统错误:
service/repo -> errx -> resp.Error
最小示例见 examples/basic/main.go。
package main
import (
"errors"
"net/http"
"github.com/go-chi/chi/v5"
"github.com/kanata996/chix"
"github.com/kanata996/chix/errx"
"github.com/kanata996/chix/reqx"
"github.com/kanata996/chix/resp"
)
type createItemRequest struct {
Name string `json:"name" validate:"required"`
}
var ErrItemNotFound = errors.New("item not found")
var itemMapper = errx.NewMapper(500101,
errx.Map(ErrItemNotFound, errx.AsNotFound(404101, "item not found")),
)
func main() {
r := chi.NewRouter()
r.Get("/items/{uuid}", getItem)
r.Post("/items", createItem)
_ = http.ListenAndServe(":8080", r)
}
func getItem(w http.ResponseWriter, r *http.Request) {
itemUUID, err := chix.Path(r).UUID("uuid")
if err != nil {
resp.Problem(w, r, err)
return
}
if itemUUID == "00000000-0000-0000-0000-000000000000" {
resp.Error(w, r, ErrItemNotFound, itemMapper)
return
}
resp.Success(w, map[string]any{"uuid": itemUUID})
}
func createItem(w http.ResponseWriter, r *http.Request) {
var body createItemRequest
if err := reqx.DecodeValidateJSON(w, r, &body); err != nil {
resp.Problem(w, r, err)
return
}
resp.Created(w, map[string]any{"name": body.Name})
}对人和 AI Agent,都按下面的规则理解这个库的公开面:
- 只依赖本 README 中列出的
chix、reqx、errx、resp公开 API - 不要导入或依赖
internal/* internal/*的实现和结构可以随时调整,不承诺兼容- 如果某个导出符号没有出现在本 README 的 API 总览中,则不视为稳定公开契约
- 预发布阶段允许小幅调整,但会优先保持本 README 中已列出的 API 和语义稳定
下面按公开包列出所有对外 API。
职责:
- 读取 path 参数
- 读取 query 参数
- 读取 header 参数
入口:
type PathReader struct { /* opaque */ }
type QueryReader struct { /* opaque */ }
type HeaderReader struct { /* opaque */ }
func Path(r *http.Request) PathReader
func Query(r *http.Request) QueryReader
func Header(r *http.Request) HeaderReaderPathReader 方法:
func (PathReader) String(name string) (string, error)
func (PathReader) UUID(name string) (string, error)
func (PathReader) Int(name string) (int, error)QueryReader 方法:
func (QueryReader) String(name string) (string, bool, error)
func (QueryReader) Strings(name string) ([]string, bool, error)
func (QueryReader) RequiredString(name string) (string, error)
func (QueryReader) RequiredStrings(name string) ([]string, error)
func (QueryReader) Int(name string) (int, bool, error)
func (QueryReader) RequiredInt(name string) (int, error)
func (QueryReader) Int16(name string) (int16, bool, error)
func (QueryReader) RequiredInt16(name string) (int16, error)
func (QueryReader) UUID(name string) (string, bool, error)
func (QueryReader) UUIDs(name string) ([]string, bool, error)
func (QueryReader) RequiredUUID(name string) (string, error)
func (QueryReader) RequiredUUIDs(name string) ([]string, error)
func (QueryReader) Bool(name string) (bool, bool, error)
func (QueryReader) RequiredBool(name string) (bool, error)HeaderReader 方法:
func (HeaderReader) String(name string) (string, bool, error)
func (HeaderReader) Strings(name string) ([]string, bool, error)
func (HeaderReader) RequiredString(name string) (string, error)
func (HeaderReader) RequiredStrings(name string) ([]string, error)
func (HeaderReader) Int(name string) (int, bool, error)
func (HeaderReader) RequiredInt(name string) (int, error)
func (HeaderReader) UUID(name string) (string, bool, error)
func (HeaderReader) RequiredUUID(name string) (string, error)
func (HeaderReader) Bool(name string) (bool, bool, error)
func (HeaderReader) RequiredBool(name string) (bool, error)稳定语义:
Path(r).Xxx(...)默认是 required 语义,缺失或空值直接返回请求错误Query(r).Xxx(...)默认是 optional 语义,缺失时返回ok=falseHeader(r).Xxx(...)默认是 optional 语义,缺失时返回ok=false- optional query/header 参数如果“出现但无效”,仍然返回请求错误,不会偷偷按缺失处理
- query/header 的标量参数重复出现时,统一返回
multiple_values - query 列表使用重复 key 形式读取,例如
?tag=a&tag=b - header 列表使用重复字段形式读取,不做 CSV 自动拆分
bool只接受小写true/falseUUID解析成功后统一规范化为 canonical 字符串
职责:
- 解 JSON body
- 校验 body/query/path DTO
- 统一建模请求侧错误
JSON 解码:
const DefaultJSONMaxBytes int64 = 1 << 20
type DecodeOptions struct {
MaxBytes int64
AllowUnknownFields bool
SkipContentTypeCheck bool
}
func DecodeJSON(w http.ResponseWriter, r *http.Request, target any) error
func DecodeJSONWith(w http.ResponseWriter, r *http.Request, target any, options DecodeOptions) error
func DecodeValidateJSON(w http.ResponseWriter, r *http.Request, target any) error校验:
type Normalizer interface {
Normalize()
}
func ValidateBody(target any) error
func ValidateQuery(target any) error
func ValidatePath(target any) error请求错误模型:
const (
InBody = "body"
InHeader = "header"
InPath = "path"
InQuery = "query"
)
const (
DetailCodeRequired = "required"
DetailCodeMalformedJSON = "malformed_json"
DetailCodeInvalidType = "invalid_type"
DetailCodeUnknownField = "unknown_field"
DetailCodeTrailingData = "trailing_data"
DetailCodeMultipleValues = "multiple_values"
DetailCodeUnsupportedMediaType = "unsupported_media_type"
DetailCodePayloadTooLarge = "payload_too_large"
DetailCodeInvalidUUID = "invalid_uuid"
DetailCodeInvalidInteger = "invalid_integer"
DetailCodeOutOfRange = "out_of_range"
DetailCodeInvalidValue = "invalid_value"
)
type Detail struct {
In string
Field string
Code string
}
type Problem struct {
StatusCode int
Details []Detail
}
func (*Problem) Error() string
func AsProblem(err error) (*Problem, bool)
func BadRequest(details ...Detail) *Problem
func ValidationFailed(details ...Detail) *Problem
func UnsupportedMediaType(details ...Detail) *Problem
func PayloadTooLarge(details ...Detail) *ProblemDetail helper:
func Required(in string, field string) Detail
func InvalidType(in string, field string) Detail
func UnknownField(field string) Detail
func MalformedJSON() Detail
func TrailingData() Detail
func MultipleValues(in string, field string) Detail
func InvalidUUID(in string, field string) Detail
func InvalidInteger(in string, field string) Detail
func OutOfRange(in string, field string) Detail
func InvalidValue(in string, field string) Detail编程错误:
var (
ErrNilResponseWriter error
ErrNilRequest error
ErrNilTarget error
ErrInvalidDecodeTarget error
ErrInvalidValidateTarget error
)稳定语义:
DecodeJSON默认检查Content-Type- 默认 body 大小上限是
1 MiB - 默认拒绝 unknown field
- 拒绝 trailing data
- 空 body 和 top-level
nullbody 统一按缺失 payload 处理 DecodeValidateJSON只是DecodeJSON + ValidateBody的组合 helperValidateBody只接受非 nil*struct,校验失败返回422ValidateQuery/ValidatePath只接受非 nil*struct,校验失败返回400- 嵌套 DTO 的
details.field使用稳定路径格式,例如items[0].name、billing.id - DTO 若实现
Normalizer,会先执行Normalize()再校验
职责:
- 定义通用业务/系统错误语义
- 提供 feature 级错误到 HTTP 响应语义的映射
- 对
Mapping和 mapper 配置做构造期校验
标准 code:
const (
CodeInvalidRequest int64 = 400000
CodeUnauthorized int64 = 400001
CodeForbidden int64 = 400003
CodeNotFound int64 = 400004
CodeConflict int64 = 400009
CodePayloadTooLarge int64 = 400013
CodeUnsupportedMediaType int64 = 400015
CodeUnprocessableEntity int64 = 400022
CodeTooManyRequests int64 = 400029
CodeClientClosed int64 = 499000
CodeInternal int64 = 500000
CodeServiceUnavailable int64 = 500003
CodeTimeout int64 = 500004
)标准语义错误:
var (
ErrInvalidRequest error
ErrUnauthorized error
ErrForbidden error
ErrNotFound error
ErrConflict error
ErrUnprocessableEntity error
ErrTooManyRequests error
ErrServiceUnavailable error
ErrTimeout error
)映射模型:
type Mapping struct {
StatusCode int
Code int64
Message string
}
func (Mapping) Validate() error
func Lookup(err error) (Mapping, bool)
func Internal(code int64) Mapping
func FormatChain(err error) stringfeature rule:
type Rule struct { /* opaque */ }
type Mapper struct { /* opaque */ }
func (*Mapper) Map(err error) Mapping
func Map(match error, mapping Mapping) Rule
func NewMapper(fallbackCode int64, rules ...Rule) *Mapper状态预设 constructor:
func AsUnauthorized(code int64, message string) Mapping
func AsForbidden(code int64, message string) Mapping
func AsNotFound(code int64, message string) Mapping
func AsConflict(code int64, message string) Mapping
func AsUnprocessable(code int64, message string) Mapping
func AsTooManyRequests(code int64, message string) Mapping
func AsServiceUnavailable(code int64, message string) Mapping
func AsTimeout(code int64, message string) Mapping稳定语义:
Lookup(err)只处理内建标准语义和 transport 生命周期错误context.Canceled会映射到499 / CodeClientClosedcontext.DeadlineExceeded会映射到504 / CodeTimeoutMap(match, mapping)会在构造期校验mappingMapper是不透明类型,只通过NewMapper(...)构造NewMapper(fallbackCode, rules...)的顺序是rules -> Lookup -> fallbackfallbackCode会通过Internal(code)构造,非法 code 会直接 panic- 预设
AsXxx(...)固定 HTTP status,业务方提供自定义code和message
推荐模式:
var (
ErrItemNotFound = errors.New("item not found")
ErrTagExists = errors.New("tag exists")
)
var mapper = errx.NewMapper(500101,
errx.Map(ErrItemNotFound, errx.AsNotFound(404101, "item not found")),
errx.Map(ErrTagExists, errx.AsConflict(409201, "tag already exists")),
)职责:
- 写统一成功响应
- 写请求错误响应
- 写业务/系统错误响应
公开 API:
func Success(w http.ResponseWriter, data any)
func Created(w http.ResponseWriter, data any)
func NoContent(w http.ResponseWriter)
func Problem(w http.ResponseWriter, r *http.Request, err error)
func Error(w http.ResponseWriter, r *http.Request, err error, mapper *errx.Mapper)稳定语义:
Success写200Created写201NoContent写204Success/Created要求data顶层编码后不能是null- success 边界自身编码失败时,fail-closed 为裸
500 Problem只接受reqx.ProblemProblem收到非reqx.Problem或非法请求状态码时,会回退到 internal 包络Error有mapper时优先走 feature 规则;未命中时再回落到errx内建语义或 fallbackError收到非法Mapping时,会回退到 internal 包络并记录原因Success/Created/NoContent/Problem/Error收到nilwriter 时只记录日志并放弃写回,不会 panic
什么时候直接用标准 errx:
- 只需要通用的
401/403/404/409/422/429/503/504 - 不需要 feature 级业务 code
什么时候用 errx.NewMapper(...):
- 同一个 HTTP status 下需要区分多个业务 code
- 想给终端用户返回更明确的业务 message
- feature 需要自己的 fallback internal code
什么时候只用 chix + reqx + resp.Problem:
- 业务层还没有稳定错误模型
- 当前接口只涉及请求边界,不涉及业务错误分层
make ci- 贡献指南:
CONTRIBUTING.md - 行为准则:
CODE_OF_CONDUCT.md - 安全策略:
SECURITY.md - 许可证:
LICENSE