Skip to content

Commit

Permalink
Compile OpenAPI spec to Go representation
Browse files Browse the repository at this point in the history
  • Loading branch information
Sergey Vilgelm committed Dec 23, 2021
1 parent 386f0c5 commit 583e558
Show file tree
Hide file tree
Showing 64 changed files with 7,243 additions and 35 deletions.
4 changes: 3 additions & 1 deletion .github/workflows/checks.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ concurrency:
cancel-in-progress: true

env:
GO: "1.18beta1"
GO: "1.18.0-beta1"

jobs:
CodeQL:
Expand All @@ -42,6 +42,7 @@ jobs:
- name: Install Go
uses: actions/setup-go@v2
with:
stable: false
go-version: ${{ env.GO }}
- name: Run Unit Tests
run: go test -race -cover -coverprofile=coverage.out -covermode=atomic
Expand All @@ -59,6 +60,7 @@ jobs:
- name: Install Go
uses: actions/setup-go@v2
with:
stable: false
go-version: ${{ env.GO }}
- name: Run GolangCi-Lint
uses: golangci/golangci-lint-action@v2.5.2
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ permissions:
contents: write

env:
GO: "1.18beta1"
GO: "1.18"

jobs:
bump-version:
Expand Down
55 changes: 22 additions & 33 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,42 +1,31 @@
# go-repo-template
# OpenAPI v3.1 Specification

[![Code Analysis](https://github.com/sv-tools/go-repo-template/actions/workflows/checks.yaml/badge.svg)](https://github.com/sv-tools/go-repo-template/actions/workflows/checks.yaml)
[![Go Reference](https://pkg.go.dev/badge/github.com/sv-tools/go-repo-template.svg)](https://pkg.go.dev/github.com/sv-tools/go-repo-template)
[![codecov](https://codecov.io/gh/sv-tools/go-repo-template/branch/main/graph/badge.svg?token=0XVOTDR1CW)](https://codecov.io/gh/sv-tools/go-repo-template)
[![GitHub tag (latest SemVer)](https://img.shields.io/github/v/tag/sv-tools/go-repo-template?style=flat)](https://github.com/sv-tools/go-repo-template/releases)
[![Code Analysis](https://github.com/sv-tools/openapi/actions/workflows/checks.yaml/badge.svg)](https://github.com/sv-tools/openapi/actions/workflows/checks.yaml)
[![Go Reference](https://pkg.go.dev/badge/github.com/sv-tools/openapi.svg)](https://pkg.go.dev/github.com/sv-tools/openapi)
[![codecov](https://codecov.io/gh/sv-tools/openapi/branch/main/graph/badge.svg?token=0XVOTDR1CW)](https://codecov.io/gh/sv-tools/openapi)
[![GitHub tag (latest SemVer)](https://img.shields.io/github/v/tag/sv-tools/openapi?style=flat)](https://github.com/sv-tools/openapi/releases)

The template for new go repositories
The implementation of OpenAPI v3.1 Specification for Go v1.18.

## Features

1. `Makefile` to run some basic validations:
1. `golangci-lint`
2. `nancy`
3. unit and benchmarks tests
4. installing all needed tools on macOS using `brew`
5. cleaning up the `go.sum` file by removing it and re-creating by `go mod tidy`
2. MIT License by default
3. GitHub Action workflows:
1. testing all pull requests by running same tools and checking code coverage using `codecov` action
2. making a new release, triggered by closed milestone
1. creates a new tag using `bumptag` tool
2. creates new `Next` milestone
3. runs `goreleaser` to build a new release

## Usage

1. Create a repository using this repo as template
2. Replace in all files `go-repo-template` to the project's name
3. In case of:
1. library
1. Remove `.github/Dockerfile`, `.github/goreleaser-cli.yml` files
2. Remove `release-cli` section in the `.github/workflows/release.yaml` file
2. command line tool (cli)
1. Remove `.github/goreleaser-lib.yml` file
2. Remove `release-lib` section in the `.github/workflows/release.yaml` file
4. Modify `README.md` by removing this text
5. Feel free to modify any other files
* The `spec` folder contains full implementation of the v3.1 Specification using generics, so the minimum supported version of Go is `v1.18beta1`.
* The `validate` folder uses [jsonschema/v5](https://github.com/santhosh-tekuri/jsonschema) with draft 2020-12 to validate the specifications.
* The official v3.0 and v3.1 [examples](https://github.com/OAI/OpenAPI-Specification/tree/main/examples) are tested.
In most cases v3.0 specification can be converted to v3.1 by changing the version's parameter only.
```diff
@@ -1,4 +1,4 @@
-openapi: "3.0.0"
+openapi: "3.1.0"
```

**NOTE**: The descriptions of most structures and their fields are taken from the official documentations.

## Links

* OpenAPI Specification: <https://github.com/OAI/OpenAPI-Specification> and <https://spec.openapis.org/oas/v3.1.0>
* JSON Schema: <https://json-schema.org/understanding-json-schema/index.html> and <https://json-schema.org/draft/2020-12/json-schema-core.html>
* The list of most popular alternatives: <https://github.com/OAI/OpenAPI-Specification/blob/main/IMPLEMENTATIONS.md#low-level-tooling>

## License

Expand Down
11 changes: 11 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
module github.com/sv-tools/openapi

go 1.18

require (
github.com/santhosh-tekuri/jsonschema/v5 v5.0.0
github.com/stretchr/testify v1.7.0
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
)

require (
github.com/davecgh/go-spew v1.1.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
)
14 changes: 14 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/santhosh-tekuri/jsonschema/v5 v5.0.0 h1:TToq11gyfNlrMFZiYujSekIsPd9AmsA2Bj/iv+s4JHE=
github.com/santhosh-tekuri/jsonschema/v5 v5.0.0/go.mod h1:FKdcjfQW6rpZSnxxUvEA5H/cDPdvJ/SZJQLWWXWGrZ0=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
73 changes: 73 additions & 0 deletions spec/bool_or_schema.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package spec

import (
"encoding/json"

"gopkg.in/yaml.v3"
)

// BoolOrSchema handles Boolean or Schema type.
//
// It MUST be used as a pointer,
// otherwise the `false` can be omitted by json or yaml encoders in case of `omitempty` tag is set.
type BoolOrSchema struct {
Allowed bool
Schema *RefOrSpec[Schema]
}

// NewBoolOrSchema creates BoolOrSchema object.
func NewBoolOrSchema(allowed bool, spec *RefOrSpec[Schema]) *BoolOrSchema {
return &BoolOrSchema{
Allowed: allowed,
Schema: spec,
}
}

// UnmarshalJSON implements json.Unmarshaler interface.
func (o *BoolOrSchema) UnmarshalJSON(data []byte) error {
if json.Unmarshal(data, &o.Allowed) == nil {
o.Schema = nil
return nil
}
if err := json.Unmarshal(data, &o.Schema); err != nil {
return err
}
o.Allowed = true
return nil
}

// MarshalJSON implements json.Marshaler interface.
func (o *BoolOrSchema) MarshalJSON() ([]byte, error) {
var v any
if o.Schema != nil {
v = o.Schema
} else {
v = o.Allowed
}
return json.Marshal(&v)
}

// UnmarshalYAML implements yaml.Unmarshaler interface.
func (o *BoolOrSchema) UnmarshalYAML(node *yaml.Node) error {
if node.Decode(&o.Allowed) == nil {
o.Schema = nil
return nil
}
if err := node.Decode(&o.Schema); err != nil {
return err
}
o.Allowed = true
return nil
}

// MarshalYAML implements yaml.Marshaler interface.
func (o BoolOrSchema) MarshalYAML() (any, error) {
var v any
if o.Schema != nil {
v = o.Schema
} else {
v = o.Allowed
}

return v, nil
}
84 changes: 84 additions & 0 deletions spec/bool_or_schema_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package spec_test

import (
"encoding/json"
"testing"

"github.com/stretchr/testify/require"
"gopkg.in/yaml.v3"

"github.com/sv-tools/openapi/spec"
)

type testAD struct {
Name string `json:"name,omitempty" yaml:"name,omitempty"`
AP *spec.BoolOrSchema `json:"ap,omitempty" yaml:"ap,omitempty"`
}

func TestAdditionalPropertiesJSON(t *testing.T) {
for _, tt := range []struct {
name string
data string
nilAP bool
allowed bool
nilSchema bool
}{
{
name: "no AP",
data: `{"name": "foo"}`,
nilAP: true,
},
{
name: "false",
data: `{"name": "foo", "ap": false}`,
nilAP: false,
allowed: false,
nilSchema: true,
},
{
name: "true",
data: `{"name": "foo", "ap": true}`,
nilAP: false,
allowed: true,
nilSchema: true,
},
{
name: "schema",
data: `{"name": "foo", "ap": {"title": "bar", "description": "test"}}`,
nilAP: false,
allowed: true,
nilSchema: false,
},
} {
t.Run(tt.name, func(t *testing.T) {
var j testAD
require.NoError(t, json.Unmarshal([]byte(tt.data), &j))
require.Equal(t, "foo", j.Name)
if tt.nilAP {
require.Nil(t, j.AP)
} else {
require.NotNil(t, j.AP)
require.Equal(t, tt.allowed, j.AP.Allowed)
require.Equal(t, tt.nilSchema, j.AP.Schema == nil)
}
newJson, err := json.Marshal(&j)
require.NoError(t, err)
require.JSONEq(t, tt.data, string(newJson))

var y testAD
require.NoError(t, yaml.Unmarshal([]byte(tt.data), &y))
require.Equal(t, "foo", y.Name)
if tt.nilAP {
require.Nil(t, y.AP)
} else {
require.NotNil(t, y.AP)
require.Equal(t, tt.allowed, y.AP.Allowed)
require.Equal(t, tt.nilSchema, y.AP.Schema == nil)
}
newYaml, err := yaml.Marshal(&y)
require.NoError(t, err)
require.YAMLEq(t, tt.data, string(newYaml))
})
}

}
69 changes: 69 additions & 0 deletions spec/callback.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package spec

import (
"encoding/json"

"gopkg.in/yaml.v3"
)

// Callback is a map of possible out-of band callbacks related to the parent operation.
// Each value in the map is a Path Item Object that describes a set of requests that may be initiated by
// the API provider and the expected responses.
// The key value used to identify the path item object is an expression, evaluated at runtime,
// that identifies a URL to use for the callback operation.
// To describe incoming requests from the API provider independent from another API call, use the webhooks field.
//
// https://spec.openapis.org/oas/v3.1.0#callback-object
//
// Example:
// myCallback:
// '{$request.query.queryUrl}':
// post:
// requestBody:
// description: Callback payload
// content:
// 'application/json':
// schema:
// $ref: '#/components/schemas/SomePayload'
// responses:
// '200':
// description: callback successfully processed
type Callback struct {
Callback map[string]*RefOrSpec[Extendable[PathItem]]
}

// NewCallbackSpec creates Callback object.
func NewCallbackSpec() *RefOrSpec[Extendable[Callback]] {
o := make(map[string]*RefOrSpec[Extendable[PathItem]])
spec := NewExtendable(&Callback{
Callback: o,
})
return NewRefOrSpec[Extendable[Callback]](nil, spec)
}

// NewCallbackRef creates Ref object.
func NewCallbackRef(ref *Ref) *RefOrSpec[Extendable[Callback]] {
return NewRefOrSpec[Extendable[Callback]](ref, nil)
}

// MarshalJSON implements json.Marshaler interface.
func (o *Callback) MarshalJSON() ([]byte, error) {
return json.Marshal(&o.Callback)
}

// UnmarshalJSON implements json.Unmarshaler interface.
func (o *Callback) UnmarshalJSON(data []byte) error {
return json.Unmarshal(data, &o.Callback)
}

// MarshalYAML implements yaml.Marshaler interface.
func (o *Callback) MarshalYAML() (any, error) {
return o.Callback, nil
}

// UnmarshalYAML implements yaml.Unmarshaler interface.
func (o *Callback) UnmarshalYAML(node *yaml.Node) error {
return node.Decode(&o.Callback)
}

func (o Callback) OpenAPIConstraint() {}
Loading

0 comments on commit 583e558

Please sign in to comment.