【问题标题】:How to generate OpenAPI v3 specification from Go source code?如何从 Go 源代码生成 OpenAPI v3 规范?
【发布时间】:2021-05-16 04:17:50
【问题描述】:

有没有办法从 Go 源代码生成 OpenAPI v3 规范?假设我有机会 API 如下所示,我想从中生成 OpenAPI 规范(yaml 文件)。类似于 Python 的Flask RESTX。我知道有一些工具可以根据规范生成 go 源代码,但是,我想反过来做。

package main

import "net/http"

func main() {
    http.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte("world\n"))
    })
    http.ListenAndServe(":5050", nil)
}

【问题讨论】:

  • 先写一个 API 实现,然后为它生成规范没有多大意义。 OpenAPI 的目的正好相反。而且这样的规范也很难完成(这样的工具如何知道例如由中间件处理的 auth 标头或对处理程序中实现的输入的限制等)
  • 完全同意上述说法。我总是首先编写 openapi YAML,然后编写代码。确保所有路由的输入和输出一致,尤其是在使用架构 $ref 引用时。
  • OpenAPI 作为规范并没有建议以哪种方式创建它更可取。这一切都涉及特定的工具和约定。将有关安全定义的信息连接到 API 文档中绝对是可行的,例如,请检查 github.com/swaggest/rest/blob/v0.1.18/_examples/task-api/…。此外,从代码中表达规范通常更不容易出现人为错误和过时。
  • 我坚信使用从代码生成的 OpenAPI 规范作为如何实现客户端的权威通常是一个坏主意,类似于在您完成后编写 UI/功能设计文档写了你的申请。也就是说,您绝对有理由从源代码生成 OpenAPI 规范。这使您可以将生成的规范与您设计的规范进行比较,以确保其符合设计。
  • 客户看到的 API 规范有两个重要方面。首先,它必须准确。其次,它必须在合理的时间间隔内向后兼容。这些属性都不是由规范优先方法专门授予的。从源反射的规范具有最高可能的准确性。手工编写规范是一个乏味的过程,容易出错和错位。相比之下,在 Go 中编写零实现受到编译时类型安全的保护,一旦导出的 API 规范得到同行批准,就可以为进一步的实际实现做好准备。

标签: go openapi


【解决方案1】:

您可以使用github.com/swaggest/rest 来构建一个自记录的 HTTP REST API。该库建立了一种约定,以一种可用于反映文档和架构并维护有关它的单一事实来源的方式声明处理程序。

在我个人看来,代码优先方法与规范优先方法相比具有优势。它可以通过不需要成为规范语言语法专家来降低输入栏。提出一个与实现细节完美平衡的规范可能会有所帮助。

使用代码优先方法,无需实施完整服务即可获得规范。您只需要定义结构和接口,可能会推迟实际的逻辑实现。

请查看example的简要用法。

package main

import (
    "context"
    "errors"
    "fmt"
    "log"
    "net/http"
    "time"

    "github.com/go-chi/chi"
    "github.com/go-chi/chi/middleware"
    "github.com/swaggest/rest"
    "github.com/swaggest/rest/chirouter"
    "github.com/swaggest/rest/jsonschema"
    "github.com/swaggest/rest/nethttp"
    "github.com/swaggest/rest/openapi"
    "github.com/swaggest/rest/request"
    "github.com/swaggest/rest/response"
    "github.com/swaggest/rest/response/gzip"
    "github.com/swaggest/swgui/v3cdn"
    "github.com/swaggest/usecase"
    "github.com/swaggest/usecase/status"
)

func main() {
    // Init API documentation schema.
    apiSchema := &openapi.Collector{}
    apiSchema.Reflector().SpecEns().Info.Title = "Basic Example"
    apiSchema.Reflector().SpecEns().Info.WithDescription("This app showcases a trivial REST API.")
    apiSchema.Reflector().SpecEns().Info.Version = "v1.2.3"

    // Setup request decoder and validator.
    validatorFactory := jsonschema.NewFactory(apiSchema, apiSchema)
    decoderFactory := request.NewDecoderFactory()
    decoderFactory.ApplyDefaults = true
    decoderFactory.SetDecoderFunc(rest.ParamInPath, chirouter.PathToURLValues)

    // Create router.
    r := chirouter.NewWrapper(chi.NewRouter())

    // Setup middlewares.
    r.Use(
        middleware.Recoverer,                          // Panic recovery.
        nethttp.OpenAPIMiddleware(apiSchema),          // Documentation collector.
        request.DecoderMiddleware(decoderFactory),     // Request decoder setup.
        request.ValidatorMiddleware(validatorFactory), // Request validator setup.
        response.EncoderMiddleware,                    // Response encoder setup.
        gzip.Middleware,                               // Response compression with support for direct gzip pass through.
    )

    // Create use case interactor.
    u := usecase.IOInteractor{}

    // Describe use case interactor.
    u.SetTitle("Greeter")
    u.SetDescription("Greeter greets you.")

    // Declare input port type.
    type helloInput struct {
        Locale string `query:"locale" default:"en-US" pattern:"^[a-z]{2}-[A-Z]{2}$" enum:"ru-RU,en-US"`
        Name   string `path:"name" minLength:"3"` // Field tags define parameter location and JSON schema constraints.
    }
    u.Input = new(helloInput)

    // Declare output port type.
    type helloOutput struct {
        Now     time.Time `header:"X-Now" json:"-"`
        Message string    `json:"message"`
    }
    u.Output = new(helloOutput)

    u.SetExpectedErrors(status.InvalidArgument)
    messages := map[string]string{
        "en-US": "Hello, %s!",
        "ru-RU": "Привет, %s!",
    }
    u.Interactor = usecase.Interact(func(ctx context.Context, input, output interface{}) error {
        var (
            in  = input.(*helloInput)
            out = output.(*helloOutput)
        )

        msg, available := messages[in.Locale]
        if !available {
            return status.Wrap(errors.New("unknown locale"), status.InvalidArgument)
        }

        out.Message = fmt.Sprintf(msg, in.Name)
        out.Now = time.Now()

        return nil
    })

    // Add use case handler to router.
    r.Method(http.MethodGet, "/hello/{name}", nethttp.NewHandler(u))

    // Swagger UI endpoint at /docs.
    r.Method(http.MethodGet, "/docs/openapi.json", apiSchema)
    r.Mount("/docs", v3cdn.NewHandler(apiSchema.Reflector().Spec.Info.Title,
        "/docs/openapi.json", "/docs"))

    // Start server.
    log.Println("http://localhost:8011/docs")
    if err := http.ListenAndServe(":8011", r); err != nil {
        log.Fatal(err)
    }
}

【讨论】:

    猜你喜欢
    • 2018-03-02
    • 1970-01-01
    • 1970-01-01
    • 2019-12-12
    • 2023-03-13
    • 2022-10-13
    • 2019-09-07
    • 2020-02-10
    • 1970-01-01
    相关资源
    最近更新 更多