Fizz

repository·master·Indexed 42 days ago

https://github.com/ovh/fizz

A wrapper for the Gin web framework that leverages gadgeto/tonic to automatically generate OpenAPI 3 specifications. It provides a way to create Gin-compatible handlers that are self-documenting and includes a Markdown builder for generating structured Markdown content in Go.

Tokens
6.1K
Snippets
20
Records
27
Agent score
53%

What's inside Fizz

  1. How block and inline elements work in Fizz

    master

    The markdown.Builder distinguishes between two types of elements:

    1. Block elements: These are concatenated to the underlying builder buffer. They typically represent structural components like headers, paragraphs, or lists.
    2. Inline elements: These are returned directly as strings and are intended to be used within other elements (like inside a paragraph or a list item).

    Block Elements

    • Headers: H1, H2, H3, H4, H5, H6
    • Paragraph: P
    • Line: Line (similar to a paragraph but without a trailing line break)
    • Line break: BR
    • Blockquote: Blockquote
    • Horizontal rule: HR
    • Code: Code
    • Lists: BulletedList, NumberedList (use builder.Block().NumberedList(...) for nested lists)

    Inline Elements

    • Alternative titles: AltH1, AltH2
    • Emphasis: Emphasis, Italic
    • Strong emphasis: StrongEmphasis, Bold
    • Combined emphasis: StrongEmphasis
    • Strikethrough: Strikethrough
    • Code: InlineCode
    • Link: Link
    • Image: Image
  2. How tonic handles parameter binding

    master

    The tonic subpackage provides a way to consolidate path, query, header, and body parameters into a single input object. This removes boilerplate code for retrieving and validating parameters. The OpenAPI generator uses the input/output types of a tonic-wrapped handler to document the operation in the specification.

    To wrap a handler, use tonic.Handler. The handler must follow one of these signatures:

    • func(*gin.Context, [input object ptr]) ([output object], error)
    • func(*gin.Context) error (minimal signature)

    Critical Requirement: The input object MUST always be a pointer to a struct, otherwise the tonic wrapping will panic at runtime.

    // Example of a tonic-wrapped handler
    func MyHandler(c *gin.Context, input *MyInput) (*MyOutput, error) {
        return &MyOutput{Message: "Hello"}, nil
    }
    
    // Registering it with Gin and Fizz
    fizz.GET("/foo", tonic.Handler(MyHandler, 200))
  3. How Fizz handlers and Tonic work together

    master

    Fizz provides wrappers for standard HTTP methods (GET, POST, PUT, PATCH, DELETE, OPTIONS, HEAD).

    To ensure your API is correctly documented in the generated OpenAPI specification, exactly one of the handlers registered for an operation must be wrapped with tonic.Handler.

    While you can pass multiple standard gin.HandlerFunc middlewares, the OpenAPI generator relies on the input/output types of the tonic.Handler to build the specification. If you only register standard Gin handlers, the operation will be ignored by the OpenAPI generator.

    // BarHandler is a standard middleware
    func BarHandler(c *gin.Context) { ... }
    
    // FooHandler defines the input (*Foo) and output (*Bar) for OpenAPI
    func FooHandler(*gin.Context, *Foo) (*Bar, error) { ... }
    
    fizz := fizz.New()
    // Registering BarHandler as middleware and FooHandler via tonic.Handler
    fizz.GET("/foo/bar", nil, BarHandler, tonic.Handler(FooHandler, 200))
  4. Initialize a Fizz instance

    master

    You can initialize Fizz in two ways:

    1. From an existing Gin engine: Use fizz.NewFromEngine(engine) if you have already configured a Gin engine with middlewares.
    2. With a new default engine: Use fizz.New() to create a fresh Fizz instance with a default Gin engine.

    A Fizz instance implements the http.HandlerFunc interface, so it can be passed directly to an http.Server as the main handler.

    engine := gin.Default()
    engine.Use(...) // register global middlewares
    
    f := fizz.NewFromEngine(engine)
    
    // Use Fizz as the server handler
    srv := &http.Server{
       Addr:    ":4242",
       Handler: f,
    }
    srv.ListenAndServe()
  5. Group routes and apply OpenAPI tags

    master

    Fizz allows you to group routes similarly to Gin, but with the added ability to apply OpenAPI tags to all routes within that group.

    Use f.Group(path, name, description, middlewares...). If name is provided, it becomes a tag in the OpenAPI specification. Groups can be nested infinitely. You can also use .Use() on a group to register middlewares after creation.

    // Create a group with a name and description (becomes an OpenAPI tag)
    grp := f.Group("/subpath", "MyGroup", "Group description", middlewares...)
    
    // Nested groups
    foo := f.Group("/foo", "Foo", "Foo group")
    bar := f.Group("/bar", "Bar", "Bar group")
    
    // This route will have the path /foo/bar/:barID
    bar.GET("/:barID", nil, tonic.Handler(MyBarHandler, 200))
    
    // Registering middleware to a group after creation
    grp.Use(middleware1, middleware2)
  6. Customize OpenAPI Component names

    master

    By default, component names are generated using CamelCase from the package and type name. To avoid collisions or customize names, you can use one of two methods:

    1. Global Override: Use f.Generator().OverrideTypeName(reflect.TypeOf(T{}), "Name"). This has the highest precedence.
    2. Interface Implementation: Implement the openapi.Typer interface on your type by providing a TypeName() string method.

    Warning: When implementing openapi.Typer, do not rely on the method receiver's state, as the generator calls the method on a new instance created via reflection.

    // Global override
    f.Generator().OverrideTypeName(reflect.TypeOf(T{}), "OverridedName")
    
    // Interface implementation
    func (*T) TypeName() string { return "OverridedName" }
  7. Configure mandatory request body fields

    master

    To make a request body field mandatory, use the validate:"required" tag. To distinguish between a missing value and a type's zero value, use a pointer.

    Note: The OpenAPI generator ignores request body parameters for GET, DELETE, and HEAD methods as per RFC 7231.

  8. Configure OpenAPI Servers and Security Schemes

    master

    If your API is hosted on a different domain or uses a path prefix not included in the spec, use f.Generator().SetServers to declare server information.

    To handle authentication, use f.Generator().SetSecuritySchemes to define global security schemes. These schemes are available to all operations by default, but you can override them on a per-operation basis using the fizz.Security() function.

    f := fizz.New()
    
    // Set server information
    f.Generator().SetServers([]*openapi.Server{
       {
          Description: "Fruits Market - production",
          URL:         "https://example.org/api/1.0",
       },
    })
    
    // Set global security schemes
    f.Generator().SetSecuritySchemes(map[string]*openapi.SecuritySchemeOrRef{
       "apiToken": {
          SecurityScheme: &openapi.SecurityScheme{
             Type: "apiKey",
             In:   "header",
             Name: "x-api-token",
          },
       },
    })
    
    // Override security for a specific operation
    fizz.Security(&openapi.SecurityRequirement{
       "apiToken": []string{},
    })
  9. Define custom OpenAPI schemas and formats

    master

    To manually control how a type is represented in the OpenAPI schema (instead of relying on reflection), you can use several approaches:

    1. Implement DataType interface: Define Type() string and Format() string to control the schema output (e.g., representing a struct as a string with a uuid format).
    2. Implement Nullable interface: Define Nullable() bool to override the nullable property.
    3. Use OverrideDataType: Use f.Generator().OverrideDataType(reflect.TypeOf(T{}), "type", "format") for the highest precedence override.

    Warning: Like openapi.Typer, do not rely on method receivers for state, as they are called on new instances created via reflection.

    // Implementing DataType for a custom UUID type
    type UUIDv4 struct { ... }
    
    func (*UUIDv4) Format() string { return "uuid" }
    func (*UUIDv4) Type() string { return "string" }
    
    // Implementing Nullable
    func (NullString) Nullable() bool { return true }
    
    // Manual override with highest precedence
    fizz.Generator().OverrideDataType(reflect.TypeOf(&UUIDv4{}), "string", "uuid")
  10. Enrich operations with OperationOption

    master
    You can add metadata to your API operations by passing OperationOption functions as the second argument to Fizz's HTTP method functions. These options allow you to define summaries, descriptions, security requirements, and custom responses for the OpenAPI specification.
  11. How Fizz and RouterGroups work together

    master

    Fizz uses a hierarchical routing model similar to Gin. The Fizz struct contains a root RouterGroup. You can create sub-groups using Group(path, name, description, handlers...).

    When you create a RouterGroup, Fizz automatically adds a new Tag to the OpenAPI specification using the provided name and description. This allows you to organize your API documentation into logical sections (e.g., "Users", "Orders").

    Each RouterGroup provides shortcut methods for HTTP verbs (GET, POST, PUT, PATCH, DELETE, etc.) which are used to register routes that are both executed by Gin and documented in the OpenAPI spec.

    // Create a group for 'Users' API
    userGroup := fizzInstance.Group("/users", "Users", "All user-related operations")
    
    // Register a route within that group
    userGroup.GET("/profile", nil, myHandler)