Full Blog TOC

Full Blog Table Of Content with Keywords Available HERE

Monday, November 11, 2024

OpenAPI Schema In Go



 

In this post we present a utilty to produce an OpenAPI file using GO commands. This is useful in case the application detects the schema, and is required to supply an OpenAPI file of the detected schema.



package openapi

import (
"gopkg.in/yaml.v3"
"strings"
)

const applicationJson = "application/json"

type InLocation int

const (
InLocationQuery InLocation = iota + 1
InLocationHeader
InLocationCookie
InLocationPath
)

type SchemaType int

const (
SchemaTypeString SchemaType = iota + 1
SchemaTypeInt
SchemaTypeObject
SchemaTypeArray
)

type Info struct {
Title string `yaml:"title,omitempty"`
Description string `yaml:"description,omitempty"`
Version string `yaml:"version,omitempty"`
}

type Schema struct {
Type string `yaml:"type,omitempty"`
Enum []string `yaml:"enum,omitempty"`
Properties map[string]*Schema `yaml:"properties,omitempty"`
Items *Schema `yaml:"items,omitempty"`
}

type Parameter struct {
In string `yaml:"in,omitempty"`
Name string `yaml:"name,omitempty"`
Description string `yaml:"description,omitempty"`
Required bool `yaml:"required,omitempty"`
Schema *Schema `yaml:"schema,omitempty"`
}

type Content map[string]*Schema

type RequestBody struct {
Description string `yaml:"description,omitempty"`
Required bool `yaml:"required,omitempty"`
Content *Content `yaml:"content,omitempty"`
}

type Response struct {
Description string `yaml:"description,omitempty"`
Content *Content `yaml:"content,omitempty"`
}

type Method struct {
Summary string `yaml:"summary,omitempty"`
Description string `yaml:"description,omitempty"`
Deprecated string `yaml:"deprecated,omitempty"`
Parameters []*Parameter `yaml:"parameters,omitempty"`
RequestBody *RequestBody `yaml:"requestBody,omitempty"`
Responses map[string]*Response `yaml:"responses,omitempty"`
}

type Path map[string]*Method

type OpenApi struct {
OpenApi string `yaml:"openapi,omitempty"`
Info *Info `yaml:"info,omitempty"`
Paths map[string]*Path `yaml:"paths,omitempty"`
}

func produceSchema() *Schema {
return &Schema{
Properties: make(map[string]*Schema),
}
}

func ProduceOpenApi() *OpenApi {
return &OpenApi{
OpenApi: "3.0.0",
Paths: make(map[string]*Path),
}
}

func (o *OpenApi) CreateYamlBytes() []byte {
bytes, err := yaml.Marshal(o)
kiterr.RaiseIfError(err)
return bytes
}

func (o *OpenApi) CreateYamlString() string {
return string(o.CreateYamlBytes())
}

func (o *OpenApi) SetPath(
path string,
) *Path {
for pathUrl, pathObject := range o.Paths {
if pathUrl == path {
return pathObject
}
}
pathObject := make(Path)
o.Paths[path] = &pathObject
return &pathObject
}

func (p *Path) SetMethod(
method string,
) *Method {
method = strings.ToLower(method)

pathObject := *p
existingMethod := pathObject[method]
if existingMethod != nil {
return existingMethod
}
methodObject := Method{
Responses: make(map[string]*Response),
}
pathObject[method] = &methodObject
return &methodObject
}

func (m *Method) SetParameter(
name string,
) *Parameter {
for _, parameter := range m.Parameters {
if parameter.Name == name {
return parameter
}
}

parameter := Parameter{
Name: name,
}
m.Parameters = append(m.Parameters, &parameter)
return &parameter
}

func (p *Parameter) SetInLocation(
in InLocation,
) *Parameter {
switch in {
case InLocationQuery:
p.In = "query"
case InLocationCookie:
p.In = "cookie"
case InLocationHeader:
p.In = "header"
case InLocationPath:
p.In = "path"
}
return p
}

func (p *Parameter) SetSchema(
schemaType SchemaType,
) *Parameter {
schema := p.Schema
if schema == nil {
schema = produceSchema()
p.Schema = schema
}

schema.SetType(schemaType)
return p
}

func (s *Schema) SetType(
schemaType SchemaType,
) *Schema {
switch schemaType {
case SchemaTypeString:
s.Type = "string"
case SchemaTypeInt:
s.Type = "integer"
case SchemaTypeObject:
s.Type = "object"
case SchemaTypeArray:
s.Type = "array"
}
return s
}

func (s *Schema) SetProperty(
name string,
schemaType SchemaType,
) *Schema {
property := s.Properties[name]
if property == nil {
property = produceSchema()
s.Properties[name] = property
}

property.SetType(schemaType)
return property
}

func (s *Schema) SetPropertyArray(
name string,
) *Schema {
array := s.SetProperty(name, SchemaTypeArray)
array.Items = produceSchema()
return array.Items
}

func (m *Method) SetRequestContent(
contentType string,
) *Schema {
body := m.RequestBody
if body == nil {
body = &RequestBody{}
m.RequestBody = body
}

content := body.Content
if content == nil {
content = &Content{}
body.Content = content
}

contentObject := *content
schema := contentObject[contentType]
if schema == nil {
schema = produceSchema()
contentObject[contentType] = schema
}

return schema
}

func (m *Method) SetContentApplicationJson() *Schema {
return m.SetRequestContent(applicationJson)
}

func (m *Method) SetResponseContent(
responseCode string,
contentType string,
) *Schema {
response := m.Responses[responseCode]
if response == nil {
response = &Response{}
m.Responses[responseCode] = response
}

content := response.Content
if content == nil {
content = &Content{}
response.Content = content
}

contentObject := *content
schema := contentObject[contentType]
if schema == nil {
schema = produceSchema()
contentObject[contentType] = schema
}

return schema
}

func (m *Method) SetResponseSuccessContentApplicationJson() *Schema {
return m.SetResponseContent("200", applicationJson)
}



The sample usage below creates 2 endpoints of list-items and add-item.


api := openapi.ProduceOpenApi()

method := api.SetPath("/api/list-items").SetMethod("GET")
method.Description = "list all store items"

method.SetParameter("store-id").
SetInLocation(openapi.InLocationQuery).
SetSchema(openapi.SchemaTypeInt)

listStoreSchema := method.SetResponseSuccessContentApplicationJson()
existingItemSchema := listStoreSchema.SetPropertyArray("items")
existingItemSchema.SetProperty("id", openapi.SchemaTypeInt)
existingItemSchema.SetProperty("name", openapi.SchemaTypeString)
existingItemSchema.SetProperty("price", openapi.SchemaTypeInt)

method = api.SetPath("/api/add-item").SetMethod("POST")
method.Description = "add item to store"
addItemSchema := method.SetContentApplicationJson()
addItemSchema.SetType(openapi.SchemaTypeObject)
newItemSchema := addItemSchema.SetProperty("item", openapi.SchemaTypeObject)
newItemSchema.SetProperty("name", openapi.SchemaTypeString)
newItemSchema.SetProperty("price", openapi.SchemaTypeInt)

t.Log("Schema is:\n\n%v", api.CreateYamlString())


The result openAPI file is:


openapi: 3.0.0
paths:
/api/add-item:
post:
description: add item to store
requestBody:
content:
application/json:
type: object
properties:
item:
type: object
properties:
name:
type: string
price:
type: integer
/api/list-items:
get:
description: list all store items
parameters:
- in: query
name: store-id
schema:
type: integer
responses:
"200":
content:
application/json:
properties:
items:
type: array
items:
properties:
id:
type: integer
name:
type: string
price:
type: integer



No comments:

Post a Comment