Skip to content

d1vbyz3r0/typed

Repository files navigation

Typed - OpenAPI Specification Generator for Echo Framework

Go Version License

Typed is a Go tool that automatically generates OpenAPI 3.0 specifications for projects using the Echo web framework. It uses a sophisticated two-stage approach combining AST (Abstract Syntax Tree) parsing and reflection to analyze your code and produce accurate, comprehensive API documentation.

🎯 Why Typed?

Most of existing code-first OpenAPI generators for Go require extensive manual annotations. Typed solves this by automatically analyzing your Echo handlers and generating accurate OpenAPI specifications without large code modifications.

🚀 Features

  • Two-Stage Generation Process: Combines AST analysis with runtime reflection for maximum accuracy
  • Code-first OpenAPI Generation approach: Generates OpenAPI 3.0 specifications from your echo server, no magic comments required
  • Intelligent Parameter Detection: Automatically detects path, query, and form parameters from inline usage
  • Type Inference: Determines parameter types from usage context (e.g. strconv.Atoi, uuid.Parse)
  • Extensible Architecture: Customizable type providers and schema customizers
  • Multiple Parameter Sources: Supports path parameters, query parameters, and form data (including file uploads)
  • Type Registry Generation: Creates Go code with type registry for reflection-based analysis
  • Multiple Output Formats: Supports both YAML and JSON output formats
  • Echo Framework Integration: Specifically designed for Echo-based projects
  • Library Mode: Generated code can be used as a library

📦 Installation

go install github.com/d1vbyz3r0/typed/cmd/typed@latest
go get github.com/d1vbyz3r0/typed@latest

Demo

You can find demo swagger here. Used openapi spec was generated with typed, you can find it in examples

🛠️ Usage

Basic Usage

To generate OpenAPI specification for your Echo project, write yaml config and add go generate directives to your code ( or run them manually). Example of config file can be found here

You should have somewhere an object, which will expose routes and middlewares and implement following interface:

type RoutesProvider interface {
	OnRouteAdded(func(
		host string,
		route echo.Route,
		handler echo.HandlerFunc,
		middleware []echo.MiddlewareFunc,
	))
	ProvideRoutes()
}

Then provide a package of your implementation and name of object constructor in configuration file:

routes-provider-ctor: NewServerBuilder
routes-provider-pkg: github.com/d1vbyz3r0/typed/examples/api
//go:generate typed -config ../typed.yaml
//go:generate go run ../gen/spec_gen.go

If you don't want to implement interface, you can use typed as library and provide options to Generate function

Command Line Options

typed [flags]

Flags:
  -config string Path to config file

Supported Output Formats

The tool automatically detects the output format based on file extension:

  • YAML: .yaml or .yml extensions
  • JSON: .json extension

🧠 Intelligent Parameter Detection

One of Typed's most powerful features is its ability to automatically detect and analyze parameter usage from your Echo handlers:

Path Parameters

func GetUser(c echo.Context) error {
    // Typed detects 'id' parameter and infers int type from strconv.Atoi usage. Note that for now you should pass c.Param directly as argument
    id, err := strconv.Atoi(c.Param("id"))
    if err != nil {
        return err
    }
    // Handler logic...
    return c.JSON(http.StatusOK, user)
}

// Route: e.GET("/users/:id", GetUser)
// Result: OpenAPI path parameter 'id' with integer type

Query Parameters

func SearchUsers(c echo.Context) error {
    // Typed detects 'limit' as integer and 'active' as boolean
    limit, _ := strconv.Atoi(c.QueryParam("limit"))
    active, _ := strconv.ParseBool(c.QueryParam("active"))
    
    // Handler logic...
    return c.JSON(http.StatusOK, users)
}

// Result: OpenAPI query parameters with correct types

Form Parameters & File Uploads

func UpdateProfile(c echo.Context) error {
    // Typed detects form fields and file uploads
    name := c.FormValue("name")
    email := c.FormValue("email")
    
    // File upload detection
    avatar, err := c.FormFile("avatar")
    if err != nil {
        return err
    }
    
    // Handler logic...
    return c.JSON(http.StatusOK, response)
}

// Result: OpenAPI form schema with string fields and binary file field

Of course, you also can declare parameters as struct fields with necessary tags. When both struct tag and inline usage are found, the struct field will have priority.

For more look at examples.

Some limitations

For now, xml and form tags are not fully supported. You can still use them, but names should be as they occur in field names if the tag is standalone:

type AuthForm struct {
	Name string `form:"Name"`
	Age  int    `xml:"Age"`
}

Or same as json tag, if you for some reason need to process multiple data formats

type AuthForm struct {
	Name string `json:"name" form:"name" xml:"name"`
	Age  int    `json:"age" form:"name" xml:"name"`
}

It will be fixed in the nearest future, when I figure out how to properly implement that

Supported Type Inference

Typed automatically infers parameter types from common conversion functions:

Package Function Inferred Type
strconv Atoi int
strconv ParseInt int64
strconv ParseUint uint
strconv ParseFloat float64
strconv ParseBool bool
uuid Parse, MustParse uuid.UUID
time Parse time.Time

📋 Enum Support

Typed automatically detects and extracts Go enums (constants with custom types) and includes them in the OpenAPI specification as enum values. This ensures your API documentation accurately reflects the allowed values for enum fields.

Automatic Enum Detection

Typed analyzes your Go constants and automatically generates OpenAPI enum schemas:

// String-based enums
type Role string

const (
    RoleAdmin = Role("admin")
    RoleUser = Role("user")
    RoleGuest = Role("guest")
)

// Integer-based enums
type Status int

const (
    StatusNew Status = 1
    StatusDone Status = 2
    StatusCancelled Status = 3
)

// Usage in structs
type User struct {
    ID     int    `json:"id"`
    Name   string `json:"name"`
    Role   Role   `json:"role"`
    Status Status `json:"status"`
}

Generated OpenAPI Schema

The above Go enums are automatically converted to OpenAPI enum schemas:

components:
  schemas:
    User:
      type: object
      properties:
        id:
          type: integer
        name:
          type: string
        role:
          type: string
          enum: [ "admin", "user", "guest" ]
        status:
          type: integer
          enum: [ 1, 2, 3 ]

Supported Enum Types

Typed supports various enum value types:

Go Type Example OpenAPI Type
string Role("admin") string with enum values
int Status(1) integer with enum values
float64 Priority(1.5) number with enum values
bool Flag(true) boolean with enum values

🔧 Extensibility

Custom Type Providers

You can extend type inference by registering custom type providers:

// Example from common/typing/type.go
func RegisterTypeProvider(p Provider) {
    providers = append(providers, p)
}

// Custom provider example
func customProvider(pkg string, funcName string) (reflect.Type, bool) {
    if pkg == "mypackage" && funcName == "ParseCustomType" {
        return reflect.TypeOf(MyCustomType{}), true
    }
    return nil, false
}

Schema Customizers

Customize OpenAPI schema generation with custom functions:

// Example from customizers.go
func RegisterCustomizer(fn openapi3gen.SchemaCustomizerFn) {
    customizers = append(customizers, fn)
}

// Built-in customizers include:
// - Field name overrides from struct tags
// - File upload handling
// - UUID format specification
// - Enum value support

🔌 Handler Processing Hooks

Typed provides a hook system that allows you to customize OpenAPI specification generation based on handler analysis. This is particularly useful for automatically detecting and documenting middleware-specific behavior.

Built-in Hooks

JWT Authentication Hook

Typed includes a built-in hook that automatically detects Echo JWT middleware usage and adds appropriate security schemes to your OpenAPI specification: To enable it, add following to your config file:

processing-hooks:
  - "EchoJWTMiddlewareHook"
func GetProtectedResource(c echo.Context) error {
    // Your protected handler logic
    return c.JSON(http.StatusOK, data)
}

// Route with JWT middleware
protected := e.Group("/api")
protected.Use(echojwt.WithConfig(echojwt.Config{
    SigningKey: []byte("secret"),
}))
protected.GET("/users", GetProtectedResource)

Result: Automatically adds Bearer token security scheme to the OpenAPI specification:

components:
  securitySchemes:
    bearerAuthScheme:
      type: http
      scheme: bearer
      bearerFormat: JWT
paths:
  /api/users:
    get:
      security:
        - bearerAuthScheme: [ ]

Custom Hooks

You can register custom hooks to extend the specification generation process:

// Example from middlewares.go
func RegisterHandlerProcessingHook(hook HandlerProcessingHookFn) {
    handlerProcessingHooks = append(handlerProcessingHooks, hook)
}

// Custom hook example
func CustomAuthHook(spec *openapi3.T, operation *openapi3.Operation, handler handlers.Handler) {
    // Analyze handler middlewares
    for _, mw := range handler.Middlewares() {
        middlewareName := typed.GetMiddlewareFuncName(mw)
        
        if strings.Contains(middlewareName, "myauth") {
            // Add custom security scheme
            if spec.Components.SecuritySchemes == nil {
                spec.Components.SecuritySchemes = make(map[string]*openapi3.SecuritySchemeRef)
            }
            
            spec.Components.SecuritySchemes["customAuth"] = &openapi3.SecuritySchemeRef{
                Value: &openapi3.SecurityScheme{
                    Type: "apiKey",
                    In:   "header",
                    Name: "X-API-Key",
                },
            }
            
            // Apply to operation
            if operation.Security == nil {
				operation.Security = openapi3.NewSecurityRequirements()
            }
            operation.Security.With(openapi3.SecurityRequirement{
                "customAuth": []string{},
            })
        }
    }
}

// Register your custom hook
func init() {
    typed.RegisterHandlerProcessingHook(CustomAuthHook)
}

Hook Function Signature

type HandlerProcessingHookFn func (spec *openapi3.T, operation *openapi3.Operation, handler handlers.Handler)

Parameters:

  • spec: The OpenAPI specification being built
  • operation: The current operation being processed
  • handler: Handler information including middlewares, route, and metadata

Use Cases for Custom Hooks

  • Authentication/Authorization: Automatically detect auth middlewares and add security schemes
  • Rate Limiting: Add rate limit headers and responses based on middleware detection
  • CORS: Document CORS headers and preflight responses
  • Validation: Add validation error responses based on validation middleware
  • Logging/Monitoring: Add operation IDs or tags based on middleware configuration
  • Custom Headers: Document custom headers added by middlewares

Middleware Detection

Typed provides utilities to analyze middleware functions:

// Get middleware function name for analysis
middlewareName := GetMiddlewareFuncName(middleware)

// Example middleware names:
// "github.com/labstack/echo-jwt.(*Config).ToMiddleware.func1"
// "github.com/labstack/echo/v4/middleware.CORS.func1"
// "myproject/middleware.CustomAuth"

This hook system makes Typed highly extensible and allows it to automatically document complex middleware behavior without manual specification.

🏗️ How It Works

Typed uses a sophisticated two-stage approach to overcome the limitations of pure AST analysis:

Stage 1: Code Generation & Type Registry

  1. AST Parsing: Analyzes your Go source code using Go's AST parser
  2. Type Discovery: Identifies all types used in Echo handlers and routes
  3. Registry Generation: Generates Go code with a type registry containing all discovered types
  4. Standard Functions: Includes utility functions for reflection-based analysis
  5. Library Output: The generated code can be used as a standalone library

Stage 2: Specification Generation

  1. Registry Execution: Runs the generated code to access reflect.Type information
  2. Echo Route Analysis: Maps Echo routes to their corresponding handlers
  3. Schema Generation: Uses kin-openapi to create OpenAPI schemas from reflection data
  4. Specification Assembly: Builds complete OpenAPI 3.0 specification with proper SchemaRefs
  5. Output Generation: Saves specification in requested format (YAML/JSON)

Why Two Stages?

The two-stage approach is necessary because:

  • AST Limitation: During AST analysis, we cannot access reflect.Type information
  • Runtime Reflection: We need actual type information to generate accurate schemas
  • Best of Both Worlds: Combines compile-time analysis with runtime type information

Key Dependencies

  • kin-openapi: Powers the OpenAPI 3.0 specification generation, schema creation, and SchemaRef handling
  • Go AST: For source code analysis and type discovery
  • Go Reflection: For runtime type information access

🤝 Contributing

Contributions are welcome! This project was created because similar tools weren't available for Echo framework projects.

📄 License

This project is licensed under the MIT License - see the LICENSE file for details.

Acknowledgments

  • kin-openapi - Essential for OpenAPI 3.0 specification generation and schema handling
  • Echo Framework - High performance, minimalist Go web framework
  • Go AST & Reflection - Powerful code analysis and runtime type inspection capabilities

🐛 Issues & Support

If you encounter any issues or have questions:

  1. Check existing Issues
  2. Create a new issue with detailed description
  3. Include code examples and error messages

🔮 Roadmap

  • Add support for form and xml tags
  • Write doc for configuration
  • Enhanced comment parsing for OpenAPI descriptions
  • Headers support
  • Add more std hooks

About

Code-first OpenAPI 3.0 generator for Echo framework — powered by Go AST and reflection

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages