schema.md - maoxiaoyue/hypgo GitHub Wiki

pkg/schema — Schema-first 路由註冊

讓路由攜帶 metadata(輸入/輸出型別、描述、標籤),AI 無需追蹤 handler 實作即可理解 API 行為。

設計理念

傳統路由只告訴框架「這個 URL 對應哪個 handler」,AI 必須閱讀 handler 原始碼才能理解 API。Schema-first 路由讓 metadata 直接附著在路由上:

傳統:r.POST("/api/users", createUserHandler)     ← AI 需讀 handler 原始碼
Schema:r.Schema(Route{...}).Handle(handler)       ← AI 直接從 metadata 理解

快速上手

import "github.com/maoxiaoyue/hypgo/pkg/schema"

// 定義請求/回應型別
type CreateUserRequest struct {
    Name  string `json:"name"`
    Email string `json:"email"`
}

type UserResponse struct {
    ID    int    `json:"id"`
    Name  string `json:"name"`
    Email string `json:"email"`
}

// 註冊帶 schema 的路由
r := router.New()

r.Schema(schema.Route{
    Method:  "POST",
    Path:    "/api/users",
    Summary: "建立使用者",
    Tags:    []string{"users"},
    Input:   CreateUserRequest{},
    Output:  UserResponse{},
    Responses: map[int]schema.ResponseSchema{
        201: {Description: "User created"},
        400: {Description: "Invalid input"},
    },
}).Handle(createUserHandler)

搭配路由群組

api := r.NewGroup("/api/v1")

api.Schema(schema.Route{
    Method:  "GET",
    Path:    "/products",
    Summary: "取得商品列表",
    Tags:    []string{"products"},
    Output:  []ProductResponse{},
}).Handle(listProductsHandler)

// 路由自動註冊為 GET /api/v1/products

混合使用

Schema 路由與傳統路由可以並存,完全向後相容:

r.GET("/health", healthHandler)                    // 傳統路由 — 照常使用
r.Schema(schema.Route{...}).Handle(createHandler)   // Schema 路由 — 附帶 metadata

核心型別

Route

type Route struct {
    Method      string                 // HTTP 方法(GET, POST, PUT, DELETE...)
    Path        string                 // 路由路徑(支援 :param 和 *catchall)
    Summary     string                 // 一行描述(給 AI 和文件用)
    Description string                 // 詳細描述
    Tags        []string               // 分類標籤
    Input       interface{}            // 請求 body 的 Go struct(用於驗證)
    Output      interface{}            // 回應 body 的 Go struct(用於驗證)
    InputName   string                 // 自動填入,無需手動設定
    OutputName  string                 // 自動填入,無需手動設定
    Params      []ParamSchema          // 路徑/查詢參數描述
    Headers     []HeaderSchema         // 必要標頭描述
    Responses   map[int]ResponseSchema // 各狀態碼的回應描述
}

ParamSchema

type ParamSchema struct {
    Name     string // 參數名稱
    In       string // "path", "query", "header"
    Required bool   // 是否必填
    Type     string // "string", "int", "bool"
    Desc     string // 描述
}

ResponseSchema

type ResponseSchema struct {
    Description string      // 回應描述
    Type        interface{} // Go struct type(用於 Contract Testing 驗證)
    TypeName    string      // 自動填入
}

Registry(全域註冊表)

所有透過 Schema() 註冊的路由都會自動存入全域 Registry:

// 查詢特定路由的 schema
route, ok := schema.Global().Get("POST", "/api/users")

// 取得所有已註冊的 schema
all := schema.Global().All()

// 查看已註冊數量
count := schema.Global().Len()

// 清空(測試用)
schema.Global().Reset()

Registry 為 thread-safe,使用 sync.RWMutex 保護。

反射工具

提供型別內省工具,供 Manifest 和 Contract Testing 使用:

// 取得型別名稱
schema.TypeName(UserResponse{})  // → "UserResponse"

// 取得 struct 欄位資訊
fields := schema.FieldsOf(UserResponse{})
// → [{Name:"id", Type:"integer", Required:true}, ...]

// 驗證 JSON 是否符合 struct(含必填欄位檢查)
err := schema.ValidateJSON([]byte(`{"name":"alice"}`), CreateUserRequest{})

// 生成零值 JSON(用於自動測試)
data := schema.GenerateZeroJSON(CreateUserRequest{})
// → {"name":"","email":""}

Required 判定規則

條件 Required
Name string \json:"name"`` ✅ 是
Email string \json:"email,omitempty"`` ❌ 否(omitempty)
Bio *string \json:"bio"`` ❌ 否(pointer)
json:"-" 跳過(不出現)

架構

pkg/schema/
├── schema.go       Route、SchemaRoute、ParamSchema 等型別定義
├── registry.go     全域 thread-safe Registry
├── reflect.go      TypeName、FieldsOf、ValidateJSON 反射工具
└── schema_test.go  18 個單元測試

依賴關係

pkg/schema ← 僅依賴 pkg/context(HandlerFunc 型別)
           ← 不依賴 pkg/router(避免迴圈依賴)

SchemaRegistrar 介面由 schema 定義,Router 和 Group 實作,避免迴圈依賴。

測試

go test ./pkg/schema/... -v

涵蓋:TypeName、FieldsOf、ValidateJSON、GenerateZeroJSON、Registry CRUD、SchemaRoute builder、goTypeToSchemaType。