Goa openapi embedded openapi documentation

Tue, Mar 26, 2024 3-minute read

Goa openapi embedded openapi documentation

A snippet for how I am storing an OpenAPI documentation page within a Goa project docker image.

design/openapi.go

// design/openapi.go
package design

import . "goa.design/goa/v3/dsl"

var _ = Service("openapi", func() {
	Description("OpenAPI endpoints for debugging and demonstration")
	HTTP(func() {
		Path("/openapi")
	})
	Method("file", func() {
		Result(func() {
			Attribute("length", Int64, "Length is the downloaded content length in bytes.", func() {
				Example(4 * 1024 * 1024)
			})
			Attribute("encoding", String, func() {
				Example("application/json")
			})
			Required("length", "encoding")
		})

		Error("invalid_file_path", ErrorResult, "Could not locate file for download")
		Error("internal_error", ErrorResult, "Fault while processing download.")

		HTTP(func() {
			GET("/openapi3.json")
			SkipResponseBodyEncodeDecode()
			Response(func() {
				Header("length:Content-Length")
				Header("encoding:Content-Type")
			})
			Response("invalid_file_path", StatusNotFound)
			Response("internal_error", StatusInternalServerError)
		})
	})
	Method("documentation", func() {

		Result(func() {
			Attribute("length", Int64, "Length is the downloaded content length in bytes.", func() {
				Example(4 * 1024 * 1024)
			})
			Attribute("encoding", String, func() {
				Example("application/json")
			})
			Required("length", "encoding")
		})

		Error("invalid_file_path", ErrorResult, "Could not locate file for download")
		Error("internal_error", ErrorResult, "Fault while processing download.")

		HTTP(func() {
			GET("/docs")
			SkipResponseBodyEncodeDecode()
			Response(func() {
				Header("length:Content-Length")
				Header("encoding:Content-Type")
			})
			Response("invalid_file_path", StatusNotFound)
			Response("internal_error", StatusInternalServerError)
		})
	})
})

openapi.go

The example generated from the above DSL

package goagithub

import (
	"context"
	"embed"
	"github.com/danielmichaels/goa-github/gen/openapi"
	"io"
	"log"
)

//go:embed gen/http/openapi3.json
var openapijson embed.FS

//go:embed assets/static/docs.html
var assets embed.FS

// openapi service example implementation.
// The example methods log the requests and return zero values.
type openapisrvc struct {
	logger *log.Logger
}

// NewOpenapi returns the openapi service implementation.
func NewOpenapi(logger *log.Logger) openapi.Service {
	return &openapisrvc{logger}
}

func (o openapisrvc) File(ctx context.Context) (res *openapi.FileResult, body io.ReadCloser, err error) {
	f, err := openapijson.Open("gen/http/openapi3.json")
	if err != nil {
		return nil, nil, openapi.MakeInvalidFilePath(err)
	}
	fi, err := f.Stat()
	if err != nil {
		return nil, nil, openapi.MakeInternalError(err)
	}
	return &openapi.FileResult{
		Length:   fi.Size(),
		Encoding: "application/json",
	}, f, nil
}

func (o openapisrvc) Documentation(ctx context.Context) (res *openapi.DocumentationResult, body io.ReadCloser, err error) {
	f, err := assets.Open("assets/static/docs.html")
	if err != nil {
		return nil, nil, openapi.MakeInvalidFilePath(err)
	}
	fi, err := f.Stat()
	if err != nil {
		return nil, nil, openapi.MakeInternalError(err)
	}
	return &openapi.DocumentationResult{
		Length:   fi.Size(),
		Encoding: "text/html",
	}, f, nil
}

assets/static/docs.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Open API</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/swagger-ui@5.10.0/dist/swagger-ui.css" integrity="sha256-IgNmYaATqY6z6AuD6hvz9XN0OyeAc94gsTa+lK8ka1Y=" crossorigin="anonymous">
  <style>
      /* Fast dark mode https://github.com/swagger-api/swagger-ui/issues/5327 */
      @media (prefers-color-scheme: dark) {
          body {
              background: #1f1f1f;
          }
          .swagger-ui {
              filter: invert(88%) hue-rotate(180deg);
          }
          .swagger-ui .microlight {
              filter: invert(100%) hue-rotate(180deg);
          }
      }
  </style>
</head>
<body>
<div id="swagger-ui"></div>
<script src="https://cdn.jsdelivr.net/npm/swagger-ui@5.10.0/dist/swagger-ui-bundle.js" integrity="sha256-i050FsZ0MSwm3mVMv7IhpfCdK90RKaXPS/EmiWxv8vc=" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/swagger-ui@5.10.0/dist/swagger-ui-standalone-preset.js" integrity="sha256-IGoJVXW7MRyeZOsKceWVePAShfVpJhnYhDhOQp+Yi38=" crossorigin="anonymous"></script>
<script>
    // Initialize Swagger UI
    window.onload = function() {
        const ui = SwaggerUIBundle({
            url: "/openapi/openapi3.json",  // Provide the URL to your OpenAPI spec file
            dom_id: '#swagger-ui',
            deepLinking: true,
            presets: [
                SwaggerUIBundle.presets.apis,
                SwaggerUIStandalonePreset
            ],
            layout: "BaseLayout"
        });
        window.ui = ui;
    };
</script>
</body>
</html>

Tags:

#goa #dsl #go