From 97a31daef6f7b507721847e496ebad2dc888e589 Mon Sep 17 00:00:00 2001 From: Bob Stasyszyn Date: Mon, 28 Oct 2019 10:58:46 -0400 Subject: [PATCH] feat: Generate OpenAPI spec for the REST API The OpenAPI spec is generated from the Sidetree model using swagger annotations. closes #57 Signed-off-by: Bob Stasyszyn --- .gitignore | 3 +- Makefile | 20 +++++- pkg/restapi/diddochandler/doc.go | 75 +++++++++++++++++++++ pkg/restapi/diddochandler/resolvehandler.go | 5 -- pkg/restapi/diddochandler/restapi_test.go | 30 +++++++-- pkg/restapi/diddochandler/updatehandler.go | 5 -- pkg/restapi/model/error.go | 3 + pkg/restapi/model/header.go | 13 +++- pkg/restapi/model/operation_type.go | 1 + pkg/restapi/model/request.go | 15 ++++- pkg/restapi/model/response.go | 2 + scripts/generate-openapi-spec.sh | 24 +++++++ 12 files changed, 175 insertions(+), 21 deletions(-) create mode 100644 pkg/restapi/diddochandler/doc.go create mode 100755 scripts/generate-openapi-spec.sh diff --git a/.gitignore b/.gitignore index 9db08498..e28541ec 100644 --- a/.gitignore +++ b/.gitignore @@ -23,4 +23,5 @@ # OS generated files .DS_Store -coverage.txt \ No newline at end of file +coverage.txt +.build diff --git a/Makefile b/Makefile index 793f4884..7ae85b9d 100644 --- a/Makefile +++ b/Makefile @@ -15,6 +15,12 @@ GO_CMD ?= go export GO111MODULE=on +# Controller API entry point to be used for generating Open API specifications +OPENAPI_SPEC_META=pkg/restapi/diddochandler/doc.go +OPENAPI_DOCKER_IMG=quay.io/goswagger/swagger +# TODO: Switched to dev since release version doesn't support go 1.13 +OPENAPI_DOCKER_IMG_VERSION=dev + checks: license lint license: @@ -26,7 +32,19 @@ lint: unit-test: @scripts/unit.sh -all: checks unit-test +.PHONY: generate-openapi-spec +generate-openapi-spec: + @echo "Generating and validating controller API specifications using Open API" + @mkdir -p .build/rest/openapi/spec + @SPEC_META=$(OPENAPI_SPEC_META) SPEC_LOC=.build/rest/openapi/spec \ + DOCKER_IMAGE=$(OPENAPI_DOCKER_IMG) DOCKER_IMAGE_VERSION=$(OPENAPI_DOCKER_IMG_VERSION) \ + scripts/generate-openapi-spec.sh + +.PHONY: clean +clean: + rm -rf .build + +all: clean checks unit-test generate-openapi-spec diff --git a/pkg/restapi/diddochandler/doc.go b/pkg/restapi/diddochandler/doc.go new file mode 100644 index 00000000..1e0b7f4c --- /dev/null +++ b/pkg/restapi/diddochandler/doc.go @@ -0,0 +1,75 @@ +/* +Copyright SecureKey Technologies Inc. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +// Package diddochandler DID document API. +// +// +// Terms Of Service: +// +// Schemes: http, https +// Host: 127.0.0.1:8080 +// Version: 0.1.0 +// License: SPDX-License-Identifier: Apache-2.0 +// +// Consumes: +// - application/json +// +// Produces: +// - application/json +// +// swagger:meta +package diddochandler + +import ( + "github.com/trustbloc/sidetree-core-go/pkg/restapi/model" +) + +// swagger:route POST /document create-did-document request +// Creates/updates a DID document. +// Responses: +// default: error +// 200: response + +// Resolve swagger:route GET /document/{id} resolve-did-document resolveDocParams +// Resolves a DID document by ID or validates the DID document if provided. +// Responses: +// default: error +// 200: response + +// Contains the DID document. +//swagger:response response +//nolint:deadcode,unused +type responseWrapper struct { + // The body of the response. + // + // required: true + // in: body + Body model.Response +} + +// Contains the request. +//swagger:parameters request +//nolint:deadcode,unused +type requestWrapper struct { + // The body of the request. + // + // required: true + // in: body + Body model.Request +} + +// resolveDocumentParams model +// This is used for getting specific DID document +// +//swagger:parameters resolveDocParams +//nolint:deadcode,unused +type resolveDocumentParams struct { + // The ID of the DID document or the DID document to be validated. + // + // in: path + // required: true + ID string `json:"id"` +} diff --git a/pkg/restapi/diddochandler/resolvehandler.go b/pkg/restapi/diddochandler/resolvehandler.go index 239b5395..431af09f 100644 --- a/pkg/restapi/diddochandler/resolvehandler.go +++ b/pkg/restapi/diddochandler/resolvehandler.go @@ -39,8 +39,3 @@ func (o *ResolveHandler) Method() string { func (o *ResolveHandler) Handler() common.HTTPRequestHandler { return o.Resolve } - -// Resolve resolves a DID document by ID or DID document -func (o *ResolveHandler) Resolve(rw http.ResponseWriter, req *http.Request) { - o.ResolveHandler.Resolve(rw, req) -} diff --git a/pkg/restapi/diddochandler/restapi_test.go b/pkg/restapi/diddochandler/restapi_test.go index e852f9a3..816262fa 100644 --- a/pkg/restapi/diddochandler/restapi_test.go +++ b/pkg/restapi/diddochandler/restapi_test.go @@ -15,6 +15,7 @@ import ( "net/http" "strings" "testing" + "time" "github.com/gorilla/mux" "github.com/stretchr/testify/require" @@ -80,17 +81,23 @@ func httpPut(t *testing.T, url string, req *model.Request) (*model.Response, err require.NoError(t, err) httpReq.Header.Set("Content-Type", "application/json") - - resp, err := client.Do(httpReq) + resp, err := invokeWithRetry( + func() (response *http.Response, e error) { + return client.Do(httpReq) + }, + ) require.NoError(t, err) - return handleHttpResp(t, resp) } // httpGet send a regular GET request to the sidetree-node and expects 'side tree document' argument as a response func httpGet(t *testing.T, url string) (*model.Response, error) { client := &http.Client{} - resp, err := client.Get(url) + resp, err := invokeWithRetry( + func() (response *http.Response, e error) { + return client.Get(url) + }, + ) require.NoError(t, err) return handleHttpResp(t, resp) } @@ -114,6 +121,21 @@ func decode(t *testing.T, response *http.Response, v interface{}) { require.NoError(t, err) } +func invokeWithRetry(invoke func() (*http.Response, error)) (*http.Response, error) { + remainingAttempts := 20 + for { + resp, err := invoke() + if err == nil { + return resp, err + } + remainingAttempts-- + if remainingAttempts == 0 { + return nil, err + } + time.Sleep(100 * time.Millisecond) + } +} + type restService struct { httpServer *http.Server } diff --git a/pkg/restapi/diddochandler/updatehandler.go b/pkg/restapi/diddochandler/updatehandler.go index 692887ad..8c136958 100644 --- a/pkg/restapi/diddochandler/updatehandler.go +++ b/pkg/restapi/diddochandler/updatehandler.go @@ -44,8 +44,3 @@ func (h *UpdateHandler) Method() string { func (h *UpdateHandler) Handler() common.HTTPRequestHandler { return h.Update } - -// Update updates/creates a DID document. -func (h *UpdateHandler) Update(rw http.ResponseWriter, req *http.Request) { - h.UpdateHandler.Update(rw, req) -} diff --git a/pkg/restapi/model/error.go b/pkg/restapi/model/error.go index ddbbc68e..22d3b005 100644 --- a/pkg/restapi/model/error.go +++ b/pkg/restapi/model/error.go @@ -7,6 +7,9 @@ SPDX-License-Identifier: Apache-2.0 package model // Error contains the error message +// swagger:response error type Error struct { + // message + // Required: true Message string `json:"message"` } diff --git a/pkg/restapi/model/header.go b/pkg/restapi/model/header.go index 3143ec7d..c3744822 100644 --- a/pkg/restapi/model/header.go +++ b/pkg/restapi/model/header.go @@ -7,8 +7,17 @@ SPDX-License-Identifier: Apache-2.0 package model // Header is the operation header +// swagger:model Header type Header struct { - Alg string `json:"alg"` - Kid string `json:"kid"` + // alg + // Required: true + Alg string `json:"alg"` + + // kid + // Required: true + Kid string `json:"kid"` + + // operation + // Required: true Operation OperationType `json:"operation"` } diff --git a/pkg/restapi/model/operation_type.go b/pkg/restapi/model/operation_type.go index 37f6f376..f4ade633 100644 --- a/pkg/restapi/model/operation_type.go +++ b/pkg/restapi/model/operation_type.go @@ -7,6 +7,7 @@ SPDX-License-Identifier: Apache-2.0 package model // OperationType is the operation type +// swagger:model OperationType type OperationType string const ( diff --git a/pkg/restapi/model/request.go b/pkg/restapi/model/request.go index dc14307e..cf918a8b 100644 --- a/pkg/restapi/model/request.go +++ b/pkg/restapi/model/request.go @@ -7,8 +7,17 @@ SPDX-License-Identifier: Apache-2.0 package model // Request is the document request +// swagger:model docRequest type Request struct { - Header *Header `json:"header"` - Payload string `json:"payload"` - Signature string `json:"signature"` + // header + // Required: true + Header *Header `json:"header"` + + // payload + // Required: true + Payload string `json:"payload"` + + // signature + // Required: true + Signature string `json:"signature"` } diff --git a/pkg/restapi/model/response.go b/pkg/restapi/model/response.go index f58d2786..82dcf353 100644 --- a/pkg/restapi/model/response.go +++ b/pkg/restapi/model/response.go @@ -7,6 +7,8 @@ SPDX-License-Identifier: Apache-2.0 package model // Response is the document response +// swagger:model docResponse type Response struct { + // in:body Body interface{} `json:"body,omitempty"` } diff --git a/scripts/generate-openapi-spec.sh b/scripts/generate-openapi-spec.sh new file mode 100755 index 00000000..d3da35ef --- /dev/null +++ b/scripts/generate-openapi-spec.sh @@ -0,0 +1,24 @@ +#!/bin/bash +# +# Copyright SecureKey Technologies Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# +set -e + +CODEBASE="$(dirname "$PWD")" +SPEC_LOC="${SPEC_LOC}" +SPEC_META="${SPEC_META:-pkg/restapi/diddochandler/doc.go}" +OUTPUT="$SPEC_LOC/openAPI.yml" +IMAGE="${DOCKER_IMAGE:-quay.io/goswagger/swagger}" +IMAGE_VERSION="${DOCKER_IMAGE_VERSION:-latest}" + +# generate and validate commands +GENERATE_COMMAND="generate spec $SPEC_META -o $OUTPUT" +VALIDATE_COMMAND="validate $OUTPUT" + +echo "Generating Open API spec" +docker run --rm -e GOPATH=$HOME/go:/go -v $HOME:$HOME -w $(pwd) ${IMAGE}:${IMAGE_VERSION} $GENERATE_COMMAND + +echo "Validating generated spec" +docker run --rm -e GOPATH=$HOME/go:/go -v $HOME:$HOME -w $(pwd) ${IMAGE}:${IMAGE_VERSION} $VALIDATE_COMMAND \ No newline at end of file