SAM と OpenAPI を組み合わせて使うときに両方を編集するのが大変&漏れやすいので合わせられる仕組みが欲しかった
openapi: 3.0.0
info:
title: OpenAPI sam-app
description: sam-appのAPI
version: 1.0.0
paths:
/hello:
get:
summary: GETサンプル
description: GETのサンプルです
responses:
'200':
description: 成功レスポンス
content:
application/json:
schema:
$ref: '#/components/schemas/Success'
x-amazon-apigateway-integration:
credentials:
Fn::Sub: ${ApiRole.Arn}
uri:
Fn::Sub: arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${GetFunction.Arn}/invocations
passthroughBehavior: when_no_templates
httpMethod: POST
type: aws_proxy
post:
summary: POSTサンプル
description: POSTのサンプルです
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
name:
description: 名前
type: string
age:
description: 年齢
type: integer
format: int32
required:
- name
- age
responses:
'200':
description: 成功レスポンス
content:
application/json:
schema:
$ref: '#/components/schemas/Success'
x-amazon-apigateway-integration:
credentials:
Fn::Sub: ${ApiRole.Arn}
uri:
Fn::Sub: arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${PostFunction.Arn}/invocations
passthroughBehavior: when_no_templates
httpMethod: POST
type: aws_proxy
components:
schemas:
Success:
description: 成功の場合のレスポンス
type: object
properties:
message:
type: string
required:
- message
package main
import (
"encoding/json"
"flag"
"fmt"
"html/template"
"io"
"log"
"sort"
"strings"
"github.com/getkin/kin-openapi/jsoninfo"
"github.com/getkin/kin-openapi/openapi3"
)
// x-amazon-apigateway-integration の型定義
type apigateway struct {
Type string `yaml:"type" json:"type"`
Uri cfn `yaml:"uri" json:"uri"`
}
type cfn struct {
FnSub string `yaml:"Fn::Sub" json:"Fn::Sub"`
}
// template.yaml に記載するLambda Functionのテンプレート
const lamdbaFunctionTemplateString = `{{ .FunctionName }}:
Type: AWS::Serverless::Function
Properties:
FunctionName: !Sub {{ .OperationId }}
Description: !Sub "{{ .Description }}"
CodeUri: {{ .ExecutablePath }}
Role: !Sub ${Role}
Events:
CatchAll:
Type: Api
Properties:
Path: {{ .Path }}
Method: GET
RestApiId: !Ref MyAPI`
type lambdaFunctionTemplate struct {
FunctionName string
OperationId string
Description string
ExecutablePath string
Path string
}
func extractFunctionName(apigw apigateway) string {
uri := apigw.Uri.FnSub
arn := strings.Split(uri, "/")[3]
return strings.ReplaceAll(strings.Trim(arn, "${}"), ".Arn", "")
}
func extractExecutablePath(functionName string) string {
rfn := []rune(strings.ReplaceAll(functionName, "Function", ""))
return strings.ToLower(string(rfn[0])) + string(rfn[1:])
}
func makeTemplateValue(path string, opearation *openapi3.Operation) lambdaFunctionTemplate {
dec, err := jsoninfo.NewObjectDecoder(opearation.Extensions["x-amazon-apigateway-integration"].(json.RawMessage))
if err != nil {
log.Fatal(err)
}
var apigw apigateway
if err = opearation.DecodeWith(dec, &apigw); err != nil {
panic(err)
}
functionName := extractFunctionName(apigw)
executablePath := extractExecutablePath(functionName)
return lambdaFunctionTemplate{
FunctionName: functionName,
OperationId: opearation.OperationID,
Description: opearation.Description,
ExecutablePath: executablePath,
Path: path,
}
}
func convertToTemplateValues(spec *openapi3.T) []lambdaFunctionTemplate {
paths := make([]string, 0, len(spec.Paths))
for path := range spec.Paths {
paths = append(paths, path)
}
sort.Strings(paths)
templates := make([]lambdaFunctionTemplate, 0, len(paths))
for _, path := range paths {
v := spec.Paths[path]
operations := make([]*openapi3.Operation, 0)
operations = append(operations, v.Connect, v.Delete, v.Get, v.Head, v.Options, v.Patch, v.Post, v.Put, v.Trace)
for _, o := range operations {
if o != nil {
templateValue := makeTemplateValue(path, o)
templates = append(templates, templateValue)
}
}
}
return templates
}
func outTemplate(writer io.Writer, templateValues []lambdaFunctionTemplate) {
for _, v := range templateValues {
tmpl, err := template.New("lambda").Parse(lamdbaFunctionTemplateString)
if err != nil {
log.Fatal(err)
}
if err := tmpl.Execute(writer, v); err != nil {
log.Fatal(err)
}
writer.Write([]byte("\n"))
}
}
func main() {
var filePath string
flag.StringVar(&filePath, "f", "openapi.yaml", "OpenAPI specification file path ex.) openapi.yaml")
flag.Parse()
loader := openapi3.NewLoader()
spec, err := loader.LoadFromFile(filePath)
if err != nil {
log.Fatal(err)
}
templates := convertToTemplateValues(spec)
writer := new(strings.Builder)
outTemplate(writer, templates)
fmt.Println(writer.String())
}