Skip to content

Commit

Permalink
Generate OpenAPI v2 and v3 specs (#146)
Browse files Browse the repository at this point in the history
Expose the OpenAPI docs generated in our main API repo
  • Loading branch information
tdeebswihart authored Feb 23, 2024
1 parent d8404d8 commit 6dc0f17
Show file tree
Hide file tree
Showing 5 changed files with 175 additions and 2 deletions.
14 changes: 12 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ all: install test
install: grpc-install mockgen-install goimports-install update-proto

# Compile proto files.
proto: grpc goimports proxy grpc-mock copyright
proto: http-api-docs grpc goimports proxy grpc-mock copyright

# Update submodule and compile proto files.
update-proto: update-proto-submodule proto update-dependencies gomodtidy
Expand All @@ -32,6 +32,9 @@ PROTO_IMPORTS = \
-I=$(PROTO_ROOT)
PROTO_PATHS = paths=source_relative:$(PROTO_OUT)

OAPI_ROOT := $(PROTO_ROOT)/openapi
OAPI_OUT := temporalproto/openapi

$(PROTO_OUT):
mkdir $(PROTO_OUT)

Expand All @@ -41,7 +44,7 @@ update-proto-submodule:
git -c protocol.file.allow=always submodule update --init --force --remote $(PROTO_ROOT)

##### Compile proto files for go #####
grpc: go-grpc copy-helpers
grpc: http-api-docs go-grpc copy-helpers

# Only install helper when its source has changed
HELPER_FILES = $(shell find ./cmd/protoc-gen-go-helpers)
Expand All @@ -65,6 +68,13 @@ go-grpc: clean .go-helpers-installed $(PROTO_OUT)

mv -f $(PROTO_OUT)/temporal/api/* $(PROTO_OUT) && rm -rf $(PROTO_OUT)/temporal

http-api-docs: go-grpc
go run cmd/encode-openapi-spec/main.go \
-v2=$(OAPI_ROOT)/openapiv2.json \
-v2-out=$(OAPI_OUT)/openapiv2.go \
-v3=$(OAPI_ROOT)/openapiv3.yaml \
-v3-out=$(OAPI_OUT)/openapiv3.go

# Copy the payload helpers
copy-helpers:
chmod +w $(PROTO_OUT)/common/v1/payload_json.go 2>/dev/null || true
Expand Down
109 changes: 109 additions & 0 deletions cmd/encode-openapi-spec/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
// The MIT License
//
// Copyright (c) 2022 Temporal Technologies Inc. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

package main

import (
"bytes"
"compress/gzip"
"flag"
"fmt"
"go/format"
"html/template"
"io"
"os"
"path/filepath"
"strings"
)

type templateInput struct {
Version int
Format string
Spec string
}

const tmpl = `package openapi
// OpenAPIV{{.Version}}{{.Format}}Spec contains a gzip-compressed {{.Format}} file specifying the Temporal HTTP API
var OpenAPIV{{.Version}}{{.Format}}Spec = {{.Spec}}`

func die(msg string, args ...any) {
fmt.Fprintf(os.Stderr, msg+"\n", args...)
os.Exit(1)
}

func prepareSpec(version int, input, output string) {
extension := strings.TrimPrefix(filepath.Ext(input), ".")
f, err := os.Open(input)
if err != nil {
die("Failed to open spec file %q: %v", input, err)
}
defer f.Close()

var b bytes.Buffer
w := gzip.NewWriter(&b)
if _, err := io.Copy(w, f); err != nil {
die("Failed to compress v%d spec: %s", version, err)
}
if err := w.Close(); err != nil {
die("Failed to compress v%d spec: %s", version, err)
}

var src bytes.Buffer
t := template.Must(template.New("spec").Parse(tmpl))
t.Execute(&src, templateInput{
Version: version,
Format: strings.ToTitle(extension),
Spec: fmt.Sprintf("%#v", b.Bytes()),
})

fmtd, err := format.Source(src.Bytes())
if err != nil {
die("Failed to format generated v%d code: %s", version, err)
}

out, err := os.Create(output)
if err != nil {
die("Failed to open %q: %s", output, err)
}
defer out.Close()
if _, err := out.Write(fmtd); err != nil {
die("Failed to write v%d code: %s", version, err)
}

}

func main() {
var v2Path, v3Path, v2Out, v3Out string
flag.StringVar(&v2Path, "v2", "", "The path to the OpenAPI v2 spec file. Required.")
flag.StringVar(&v3Path, "v3", "", "The path to the OpenAPI v3 spec file. Required.")
flag.StringVar(&v2Out, "v2-out", "", "The path to the v2 output file. Required.")
flag.StringVar(&v3Out, "v3-out", "", "The path to the v3 output file. Required.")
flag.Parse()
if v2Path == "" || v3Path == "" || v2Out == "" || v3Out == "" {
flag.Usage()
os.Exit(127)
}

prepareSpec(2, v2Path, v2Out)
prepareSpec(3, v3Path, v3Out)
}
26 changes: 26 additions & 0 deletions temporalproto/openapi/openapiv2.go

Large diffs are not rendered by default.

26 changes: 26 additions & 0 deletions temporalproto/openapi/openapiv3.go

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions temporalproto/openapi/payload_description.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Arbitrary payload data in an unconstrained format.
This may be activity input parameters, a workflow result, a memo, etc.

0 comments on commit 6dc0f17

Please sign in to comment.