compoas

package module
v0.1.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 1, 2022 License: MIT Imports: 6 Imported by: 0

README

⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️

This module has been moved to monorepo: https://github.com/graaphscom/monogo

⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️️

CI codecov Go Reference

compoas

Library for building, composing and serving OpenAPI Specification (aka Swagger).

Features

This lib provides:

  • golang structs which reflect OpenAPI Specification (check "Limitations" below),
  • embedded Swagger UI (through go:embed) and http.Handler for serving it,
  • functions for merging OpenAPI Specifications and dumping them into a file

Limitations

  • included golang structs not cover entire OpenAPI Specification
  • Swagger UI cannot be customized (only setting specification URL is possible)

Installation

go get github.com/graaphscom/compoas

Example code

Usage

Let's assume we're building a simple e-commerce app (same as in dbmigrat). This app is split into three modules. For now, each module exposes one REST API endpoint:

  • auth (provides an endpoint for signing up)
  • billing (provides an endpoint for fetching orders list - only for authenticated users)
  • inventory (provides an endpoint for fetching a single product)

At the end of this mini-tutorial we will have documentation for each separate module and one merged doc:

That's how our project's directory layout looks like:

|-- ecommerceapp
|   |-- auth
|   |   `-- openapi.go
|   |-- billing
|   |   `-- openapi.go
|   |-- cmd
|   |   |-- dump_openapi
|   |   |   `-- main.go
|   |   `-- start_http_server
|   |       |-- main.go
|   |       `-- openapi // this dir contains dumped specs
|   |           |-- auth.json
|   |           |-- billing.json
|   |           |-- inventory.json
|   |           `-- merged.json
|   `-- inventory
|       `-- openapi.go

At first, let's define OpenAPI Specification for each module. To save space in this README, I'm pasting spec only for the billing module.

billing/openapi.go:

package billing

import (
	"github.com/graaphscom/compoas"
	"github.com/graaphscom/compoas/internal/docs/ecommerceapp/auth"
	"github.com/graaphscom/compoas/internal/docs/ecommerceapp/inventory"
)

var Openapi = compoas.OAS{
	Openapi: "3.0.0",
	Info: compoas.Info{
		Title:   "Billing API",
		Version: "1.0.0",
	},
	Components: &compoas.Components{
		Schemas: map[string]compoas.Schema{
			"Order": {Type: "object", Properties: map[string]compoas.Schema{
				"id":    {Type: "integer"},
				"buyer": {Ref: "#/components/schemas/User-Read"},
				"items": {Type: "array", Items: &compoas.Schema{Ref: "#/components/schemas/Product"}},
			}},
			"User-Read": auth.Openapi.Components.Schemas["User-Read"],
			"Product":   inventory.Openapi.Components.Schemas["Product"],
		},
		SecuritySchemes: auth.Openapi.Components.SecuritySchemes,
	},
	Paths: map[string]compoas.PathItem{
		"/billing/orders": {
			Get: &compoas.Operation{
				Tags: []string{"Billing"},
				Responses: map[string]compoas.Response{
					"200": {Content: map[string]compoas.MediaType{
						"application/json": {Schema: &compoas.Schema{
							Type:  "array",
							Items: &compoas.Schema{Ref: "#/components/schemas/Order"},
						}},
					}},
				},
				Security: []compoas.SecurityRequirement{{"bearerAuth": {}}},
			},
		},
	},
}

inventory/openapi.go: check here

auth/openapi.go: check here

Now it's time to build a command for starting an HTTP server which will be exposing:

  • OpenAPI Specifications as JSON files
  • Swagger UI

Let's begin creating cmd/start_http_server/main.go file:

package main

import (
	"embed"
	"github.com/graaphscom/compoas"
	"log"
	"net/http"
)

//go:embed openapi
var dumpedSpecs embed.FS

func main() {
	oasHandler, err := compoas.UIHandler(
		compoas.SwaggerUIBundleConfig{Urls: []compoas.SwaggerUIBundleUrl{
			{Url: "/openapi/merged.json", Name: "All"},
			{Url: "/openapi/auth.json", Name: "Auth"},
			{Url: "/openapi/billing.json", Name: "Billing"},
			{Url: "/openapi/inventory.json", Name: "Inventory"},
		}},
		"/swagger-ui",
		log.Fatalln,
	)
	if err != nil {
		log.Fatalln(err)
	}

Above code configures handler for serving Swagger UI.

The first argument to the compoas.UIHandler defines URLs where specifications are available.

We want to have Swagger UI under route http://localhost:8080/swagger-ui. For that reason, the second argument to the compoas.UIHandler is /swagger-ui. If Swagger UI should be directly under http://localhost:8080 we would provide / as the second argument.

Now create a handler for static JSON specifications, configure routes and start listening:

package main

import (
	"embed"
	"github.com/graaphscom/compoas"
	"log"
	"net/http"
)

//go:embed openapi
var dumpedSpecs embed.FS

func main() {
	oasHandler, err := compoas.UIHandler(
		compoas.SwaggerUIBundleConfig{Urls: []compoas.SwaggerUIBundleUrl{
			{Url: "/openapi/merged.json", Name: "All"},
			{Url: "/openapi/auth.json", Name: "Auth"},
			{Url: "/openapi/billing.json", Name: "Billing"},
			{Url: "/openapi/inventory.json", Name: "Inventory"},
		}},
		"/swagger-ui",
		log.Fatalln,
	)
	if err != nil {
		log.Fatalln(err)
	}

+ 	mux := http.NewServeMux()
+ 	mux.Handle("/swagger-ui/", oasHandler)
+ 	mux.Handle("/openapi/", http.FileServer(http.FS(dumpedSpecs)))
+ 
+ 	log.Fatalln(http.ListenAndServe(":8080", mux))
+ }

As a final step, we need to prepare command for dumping specifications into JSON files. Let's start creating cmd/dump_openapi/main.go file:

package main

import (
	"github.com/graaphscom/compoas"
	"github.com/graaphscom/compoas/internal/docs/ecommerceapp/auth"
	"github.com/graaphscom/compoas/internal/docs/ecommerceapp/billing"
	"github.com/graaphscom/compoas/internal/docs/ecommerceapp/inventory"
	"log"
	"path"
)

func main() {
	const dir = "cmd/start_http_server/openapi"

	err := auth.Openapi.Dump(true, path.Join(dir, "auth.json"))
	err = billing.Openapi.Dump(true, path.Join(dir, "billing.json"))
	err = inventory.Openapi.Dump(true, path.Join(dir, "inventory.json"))

In the code above we're dumping each module's pretty-printed specification. To dump merged specifications we need to create a new empty specification, the remaining specifications will be merged into it:

package main

import (
	"github.com/graaphscom/compoas"
	"github.com/graaphscom/compoas/internal/docs/ecommerceapp/auth"
	"github.com/graaphscom/compoas/internal/docs/ecommerceapp/billing"
	"github.com/graaphscom/compoas/internal/docs/ecommerceapp/inventory"
	"log"
	"path"
)

func main() {
	const dir = "cmd/start_http_server/openapi"

	err := auth.Openapi.Dump(true, path.Join(dir, "auth.json"))
	err = billing.Openapi.Dump(true, path.Join(dir, "billing.json"))
	err = inventory.Openapi.Dump(true, path.Join(dir, "inventory.json"))

+ 	rootOpenapi := compoas.OAS{
+ 		Openapi: "3.0.0",
+ 		Info: compoas.Info{
+ 			Title:   "e-commerce app",
+ 			Version: "1.0.0",
+ 		},
+ 		Components: &compoas.Components{
+ 			Schemas:         map[string]compoas.Schema{},
+ 			SecuritySchemes: map[string]compoas.SecurityScheme{},
+ 		},
+ 		Paths: map[string]compoas.PathItem{},
+ 	}
+ 	err = rootOpenapi.Merge(auth.Openapi).
+ 		Merge(billing.Openapi).
+ 		Merge(inventory.Openapi).
+ 		Dump(true, path.Join(dir, "merged.json"))
+ 
+ 	if err != nil {
+ 		log.Fatalln(err)
+ 	}
+ }

It's time to start our HTTP server:

  1. make sure that directory cmd/start_http_server/openapi exists
  2. dump specifications - run go run cmd/dump_openapi/main.go
  3. start server - run go run cmd/start_http_server/main.go
  4. visit http://localhost:8080/swagger-ui in your web browser

Documentation

Overview

Package compoas provides structs for building OpenAPI specification and HTTP handler for serving Swagger UI.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func UIHandler

func UIHandler(uiBundle SwaggerUIBundleConfig, pathPrefix string, log func(v ...interface{})) (http.Handler, error)

UIHandler serves Swagger UI (index.html and all required js, css assets)

uiBundle argument is a configuration that will be rendered inside index.html as input to the SwaggerUIBundle.

pathPrefix argument allows for setting path under which Swagger UI will be accessible. Eg If we want to have Swagger UI under http://example.com/swagger-ui, we would set pathPrefix to "/swagger-ui" (leading slash, no trailing slash). If no nesting is needed, set pathPrefix to "/".

log argument is being used for logging error when http.ResponseWriter could not write a response

Types

type Components

type Components struct {
	SecuritySchemes map[string]SecurityScheme `json:"securitySchemes,omitempty"`
	Schemas         map[string]Schema         `json:"schemas,omitempty"`
}

Components https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#componentsObject

type Info

type Info struct {
	Title   string `json:"title"`
	Version string `json:"version"`
}

Info https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#info-object

type MediaType

type MediaType struct {
	Schema *Schema `json:"schema,omitempty"`
}

MediaType https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#mediaTypeObject

type OAS

type OAS struct {
	Openapi    string              `json:"openapi"`
	Info       Info                `json:"info"`
	Components *Components         `json:"components,omitempty"`
	Paths      map[string]PathItem `json:"paths"`
}

OAS https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#schema

func (OAS) Dump

func (oas OAS) Dump(prettyPrint bool, fileName string) error

Dump allows for dumping specification into a file.

func (*OAS) Merge

func (oas *OAS) Merge(source OAS) *OAS

Merge allows for merging multiple specifications. It merges paths, components.schemas and components.securitySchemes. Root specification must have initialized these fields:

 rootOpenapi := compoas.OAS{
		Openapi: "3.0.0",
		Info: compoas.Info{
			Title:   "merged spec",
			Version: "1.0.0",
		},
		Components: &compoas.Components{
			Schemas:         map[string]compoas.Schema{},
			SecuritySchemes: map[string]compoas.SecurityScheme{},
		},
		Paths: map[string]compoas.PathItem{},
	}
 rootOpenapi.Merge(anotherOpenapi)

type Operation

type Operation struct {
	Tags        []string              `json:"tags,omitempty"`
	RequestBody *RequestBody          `json:"requestBody,omitempty"`
	Responses   map[string]Response   `json:"responses"`
	Parameters  []Parameter           `json:"parameters,omitempty"`
	Security    []SecurityRequirement `json:"security,omitempty"`
}

Operation https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#operationObject

type Parameter

type Parameter struct {
	Name        string      `json:"name"`
	In          string      `json:"in"`
	Description string      `json:"description,omitempty"`
	Required    bool        `json:"required,omitempty"`
	Style       string      `json:"style,omitempty"`
	Explode     bool        `json:"explode,omitempty"`
	Schema      *Schema     `json:"schema,omitempty"`
	Example     interface{} `json:"example,omitempty"`
}

Parameter https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#parameterObject

type PathItem

type PathItem struct {
	Post   *Operation `json:"post,omitempty"`
	Put    *Operation `json:"put,omitempty"`
	Get    *Operation `json:"get,omitempty"`
	Delete *Operation `json:"delete,omitempty"`
}

PathItem https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#path-item-object

type RequestBody

type RequestBody struct {
	Content map[string]MediaType `json:"content"`
}

RequestBody https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#requestBodyObject

type Response

type Response struct {
	Description string               `json:"description"`
	Content     map[string]MediaType `json:"content,omitempty"`
}

Response https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#responseObject

type Schema

type Schema struct {
	Type        string            `json:"type,omitempty"`
	Format      string            `json:"format,omitempty"`
	Properties  map[string]Schema `json:"properties,omitempty"`
	Enum        []string          `json:"enum,omitempty"`
	Items       *Schema           `json:"items,omitempty"`
	Nullable    bool              `json:"nullable,omitempty"`
	Ref         string            `json:"$ref,omitempty"`
	Required    []string          `json:"required,omitempty"`
	MinLength   int               `json:"minLength,omitempty"`
	MaxLength   int               `json:"maxLength,omitempty"`
	MinItems    int               `json:"minItems,omitempty"`
	MaxItems    int               `json:"maxItems,omitempty"`
	Description string            `json:"description,omitempty"`
	OneOf       []Schema          `json:"oneOf,omitempty"`
}

Schema https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#schemaObject

type SecurityScheme

type SecurityScheme struct {
	Type         string `json:"type"`
	Scheme       string `json:"scheme"`
	BearerFormat string `json:"bearerFormat,omitempty"`
}

SecurityScheme https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#securitySchemeObject

type SwaggerUIBundleConfig

type SwaggerUIBundleConfig struct {
	Url  string               `json:"url,omitempty"`
	Urls []SwaggerUIBundleUrl `json:"urls,omitempty"`
}

SwaggerUIBundleConfig reflects config to the SwaggerUIBundle. https://github.com/swagger-api/swagger-ui/blob/bb21c6df52eb12cd4bdbf8c29feb500795595fa8/dist/index.html#L41

type SwaggerUIBundleUrl

type SwaggerUIBundleUrl struct {
	Url  string `json:"url,omitempty"`
	Name string `json:"name,omitempty"`
}

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL