Automate openapi server generation (#308)

* Fix type_assert.patch

* Improve openapi-server generation

* Automate openapi server generation if something changed
This commit is contained in:
Andrea Lamparelli 2024-02-15 11:17:29 +01:00 committed by GitHub
parent dbcacad3c6
commit 90f7ddcd8d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 511 additions and 13 deletions

View File

@ -1,3 +1,5 @@
exclude: '^patches/'
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.4.0

View File

@ -34,6 +34,9 @@ COPY cmd/ cmd/
COPY api/ api/
COPY internal/ internal/
COPY pkg/ pkg/
COPY scripts/ scripts/
COPY patches/ patches/
COPY templates/ templates/
# Build
USER root

View File

@ -67,18 +67,18 @@ gen/converter: gen/grpc internal/converter/generated/converter.go
# validate the openapi schema
.PHONY: openapi/validate
openapi/validate: bin/openapi-generator-cli
openapi-generator-cli validate -i api/openapi/model-registry.yaml
@openapi-generator-cli validate -i api/openapi/model-registry.yaml
# generate the openapi server implementation
# note: run manually only when model-registry.yaml api changes, for model changes gen/openapi is run automatically
.PHONY: gen/openapi-server
gen/openapi-server: bin/openapi-generator-cli openapi/validate
openapi-generator-cli generate \
-i api/openapi/model-registry.yaml -g go-server -o internal/server/openapi --package-name openapi --global-property models \
--ignore-file-override ./.openapi-generator-ignore --additional-properties=outputAsLibrary=true,enumClassPrefix=true,router=chi,sourceFolder=,onlyInterfaces=true,isGoSubmodule=true,enumClassPrefix=true,useOneOfDiscriminatorLookup=true \
--template-dir ./templates/go-server
./scripts/gen_type_asserts.sh
gofmt -w internal/server/openapi
@if git diff --cached --name-only | grep -q "api/openapi/model-registry.yaml" || \
git diff --name-only | grep -q "api/openapi/model-registry.yaml" || \
[ -n "${FORCE_SERVER_GENERATION}" ]; then \
ROOT_FOLDER="." ./scripts/gen_openapi_server.sh; \
else \
echo "INFO api/openapi/model-registry.yaml is not staged or modified, will not re-generate server"; \
fi
# generate the openapi schema model and client
.PHONY: gen/openapi
@ -156,7 +156,7 @@ build/odh: vet
go build
.PHONY: gen
gen: deps gen/grpc gen/openapi gen/converter
gen: deps gen/grpc gen/openapi gen/openapi-server gen/converter
go generate ./...
.PHONY: lint

View File

@ -3,7 +3,7 @@
*
* REST API for Model Registry to create and manage ML model metadata
*
* API version: 1.0.0
* API version: v1alpha1
* Generated by: OpenAPI Generator (https://openapi-generator.tech)
*/
@ -15,6 +15,7 @@ import (
"strings"
"github.com/go-chi/chi/v5"
model "github.com/opendatahub-io/model-registry/pkg/openapi"
)
@ -692,7 +693,7 @@ func (c *ModelRegistryServiceAPIController) GetModelVersion(w http.ResponseWrite
EncodeJSONResponse(result.Body, &result.Code, w)
}
// GetModelVersionArtifacts - List All ModelVersion's artifacts
// GetModelVersionArtifacts - List all artifacts associated with the `ModelVersion`
func (c *ModelRegistryServiceAPIController) GetModelVersionArtifacts(w http.ResponseWriter, r *http.Request) {
query := r.URL.Query()
modelversionIdParam := chi.URLParam(r, "modelversionId")

View File

@ -191,6 +191,25 @@ func AssertBaseResourceUpdateConstraints(obj model.BaseResourceUpdate) error {
return nil
}
// AssertDocArtifactRequired checks if the required fields are not zero-ed
func AssertDocArtifactRequired(obj model.DocArtifact) error {
elements := map[string]interface{}{
"artifactType": obj.ArtifactType,
}
for name, el := range elements {
if isZero := IsZeroValue(el); isZero {
return &RequiredError{Field: name}
}
}
return nil
}
// AssertDocArtifactConstraints checks if the values respects the defined constraints
func AssertDocArtifactConstraints(obj model.DocArtifact) error {
return nil
}
// AssertErrorRequired checks if the required fields are not zero-ed
func AssertErrorRequired(obj model.Error) error {
elements := map[string]interface{}{

View File

@ -1,9 +1,9 @@
diff --git a/internal/server/openapi/type_asserts.go b/internal/server/openapi/type_asserts.go
index 4318f15..2aba64a 100644
index 6e8ecb1..6d08bbb 100644
--- a/internal/server/openapi/type_asserts.go
+++ b/internal/server/openapi/type_asserts.go
@@ -18,15 +18,15 @@ import (
// AssertArtifactRequired checks if the required fields are not zero-ed
func AssertArtifactRequired(obj model.Artifact) error {
- elements := map[string]interface{}{
@ -26,3 +26,4 @@ index 4318f15..2aba64a 100644
+ // }
return nil
}

22
scripts/gen_openapi_server.sh Executable file
View File

@ -0,0 +1,22 @@
#!/bin/bash
set -e
echo "Generating the OpenAPI server"
ROOT_FOLDER="${ROOT_FOLDER:-..}"
openapi-generator-cli generate \
-i $ROOT_FOLDER/api/openapi/model-registry.yaml -g go-server -o $ROOT_FOLDER/internal/server/openapi --package-name openapi --global-property models,apis \
--ignore-file-override $ROOT_FOLDER/.openapi-generator-ignore --additional-properties=outputAsLibrary=true,enumClassPrefix=true,router=chi,sourceFolder=,onlyInterfaces=true,isGoSubmodule=true,enumClassPrefix=true,useOneOfDiscriminatorLookup=true \
--template-dir $ROOT_FOLDER/templates/go-server
sed -i 's/, orderByParam/, model.OrderByField(orderByParam)/g' $ROOT_FOLDER/internal/server/openapi/api_model_registry_service.go
sed -i 's/, sortOrderParam/, model.SortOrder(sortOrderParam)/g' $ROOT_FOLDER/internal/server/openapi/api_model_registry_service.go
echo "Assembling type_assert Go file"
./scripts/gen_type_asserts.sh
gofmt -w $ROOT_FOLDER/internal/server/openapi
echo "OpenAPI server generation completed"

View File

@ -0,0 +1,450 @@
{{>partial_header}}
package {{packageName}}
import (
"encoding/json"
{{#isBodyParam}}
{{^required}}
"errors"
"io"
{{/required}}
{{/isBodyParam}}
"net/http"
"strings"
{{#routers}}
{{#mux}}
"github.com/gorilla/mux"
{{/mux}}
{{#chi}}
"github.com/go-chi/chi/v5"
{{/chi}}
model "github.com/opendatahub-io/model-registry/pkg/openapi"
{{/routers}}
)
// {{classname}}Controller binds http requests to an api service and writes the service results to the http response
type {{classname}}Controller struct {
service {{classname}}Servicer
errorHandler ErrorHandler
}
// {{classname}}Option for how the controller is set up.
type {{classname}}Option func(*{{classname}}Controller)
// With{{classname}}ErrorHandler inject ErrorHandler into controller
func With{{classname}}ErrorHandler(h ErrorHandler) {{classname}}Option {
return func(c *{{classname}}Controller) {
c.errorHandler = h
}
}
// New{{classname}}Controller creates a default api controller
func New{{classname}}Controller(s {{classname}}Servicer, opts ...{{classname}}Option) Router {
controller := &{{classname}}Controller{
service: s,
errorHandler: DefaultErrorHandler,
}
for _, opt := range opts {
opt(controller)
}
return controller
}
// Routes returns all the api routes for the {{classname}}Controller
func (c *{{classname}}Controller) Routes() Routes {
return Routes{
{{#operations}}
{{#operation}}
"{{operationId}}": Route{
strings.ToUpper("{{httpMethod}}"),
"{{{basePathWithoutHost}}}{{{path}}}",
c.{{operationId}},
},
{{/operation}}
{{/operations}}
}
}{{#operations}}{{#operation}}
// {{nickname}} - {{{summary}}}
{{#isDeprecated}}
// Deprecated
{{/isDeprecated}}
func (c *{{classname}}Controller) {{nickname}}(w http.ResponseWriter, r *http.Request) {
{{#hasFormParams}}
{{#isMultipart}}
if err := r.ParseMultipartForm(32 << 20); err != nil {
c.errorHandler(w, r, &ParsingError{Err: err}, nil)
return
}
{{/isMultipart}}
{{^isMultipart}}
if err := r.ParseForm(); err != nil {
c.errorHandler(w, r, &ParsingError{Err: err}, nil)
return
}
{{/isMultipart}}
{{/hasFormParams}}
{{#routers}}
{{#mux}}
{{#hasPathParams}}
params := mux.Vars(r)
{{/hasPathParams}}
{{/mux}}
{{/routers}}
{{#hasQueryParams}}
query := r.URL.Query()
{{/hasQueryParams}}
{{#allParams}}
{{#isPathParam}}
{{#isNumber}}
{{paramName}}Param, err := parseNumericParameter[float32](
{{#routers}}{{#mux}}params["{{baseName}}"]{{/mux}}{{#chi}}chi.URLParam(r, "{{baseName}}"){{/chi}}{{/routers}},{{#defaultValue}}
WithDefaultOrParse[float32]({{defaultValue}}, parseFloat32),{{/defaultValue}}{{^defaultValue}}{{#required}}
WithRequire[float32](parseFloat32),{{/required}}{{/defaultValue}}{{^defaultValue}}{{^required}}
WithParse[float32](parseFloat32),{{/required}}{{/defaultValue}}{{#minimum}}
WithMinimum[float32]({{minimum}}),{{/minimum}}{{#maximum}}
WithMaximum[float32]({{maximum}}),{{/maximum}}
)
if err != nil {
c.errorHandler(w, r, &ParsingError{Err: err}, nil)
return
}
{{/isNumber}}
{{#isFloat}}
{{paramName}}Param, err := parseNumericParameter[float32](
{{#routers}}{{#mux}}params["{{baseName}}"]{{/mux}}{{#chi}}chi.URLParam(r, "{{baseName}}"){{/chi}}{{/routers}},{{#defaultValue}}
WithDefaultOrParse[float32]({{defaultValue}}, parseFloat32),{{/defaultValue}}{{^defaultValue}}{{#required}}
WithRequire[float32](parseFloat32),{{/required}}{{/defaultValue}}{{^defaultValue}}{{^required}}
WithParse[float32](parseFloat32),{{/required}}{{/defaultValue}}{{#minimum}}
WithMinimum[float32]({{minimum}}),{{/minimum}}{{#maximum}}
WithMaximum[float32]({{maximum}}),{{/maximum}}
)
if err != nil {
c.errorHandler(w, r, &ParsingError{Err: err}, nil)
return
}
{{/isFloat}}
{{#isDouble}}
{{paramName}}Param, err := parseNumericParameter[float64](
{{#routers}}{{#mux}}params["{{baseName}}"]{{/mux}}{{#chi}}chi.URLParam(r, "{{baseName}}"){{/chi}}{{/routers}},{{#defaultValue}}
WithDefaultOrParse[float64]({{defaultValue}}, parseFloat64),{{/defaultValue}}{{^defaultValue}}{{#required}}
WithRequire[float64](parseFloat64),{{/required}}{{/defaultValue}}{{^defaultValue}}{{^required}}
WithParse[float64](parseFloat64),{{/required}}{{/defaultValue}}{{#minimum}}
WithMinimum[float64]({{minimum}}),{{/minimum}}{{#maximum}}
WithMaximum[float64]({{maximum}}),{{/maximum}}
)
if err != nil {
c.errorHandler(w, r, &ParsingError{Err: err}, nil)
return
}
{{/isDouble}}
{{#isLong}}
{{paramName}}Param, err := parseNumericParameter[int64](
{{#routers}}{{#mux}}params["{{baseName}}"]{{/mux}}{{#chi}}chi.URLParam(r, "{{baseName}}"){{/chi}}{{/routers}},{{#defaultValue}}
WithDefaultOrParse[int64]({{defaultValue}}, parseInt64),{{/defaultValue}}{{^defaultValue}}{{#required}}
WithRequire[int64](parseInt64),{{/required}}{{/defaultValue}}{{^defaultValue}}{{^required}}
WithParse[int64](parseInt64),{{/required}}{{/defaultValue}}{{#minimum}}
WithMinimum[int64]({{minimum}}),{{/minimum}}{{#maximum}}
WithMaximum[int64]({{maximum}}),{{/maximum}}
)
if err != nil {
c.errorHandler(w, r, &ParsingError{Err: err}, nil)
return
}
{{/isLong}}
{{#isInteger}}
{{paramName}}Param, err := parseNumericParameter[int32](
{{#routers}}{{#mux}}params["{{baseName}}"]{{/mux}}{{#chi}}chi.URLParam(r, "{{baseName}}"){{/chi}}{{/routers}},{{#defaultValue}}
WithDefaultOrParse[int32]({{defaultValue}}, parseInt32),{{/defaultValue}}{{^defaultValue}}{{#required}}
WithRequire[int32](parseInt32),{{/required}}{{/defaultValue}}{{^defaultValue}}{{^required}}
WithParse[int32](parseInt32),{{/required}}{{/defaultValue}}{{#minimum}}
WithMinimum[int32]({{minimum}}),{{/minimum}}{{#maximum}}
WithMaximum[int32]({{maximum}}),{{/maximum}}
)
if err != nil {
c.errorHandler(w, r, &ParsingError{Err: err}, nil)
return
}
{{/isInteger}}
{{^isNumber}}
{{^isFloat}}
{{^isDouble}}
{{^isLong}}
{{^isInteger}}
{{paramName}}Param := {{#routers}}{{#mux}}params["{{baseName}}"]{{/mux}}{{#chi}}chi.URLParam(r, "{{baseName}}"){{/chi}}{{/routers}}
{{/isInteger}}
{{/isLong}}
{{/isDouble}}
{{/isFloat}}
{{/isNumber}}
{{/isPathParam}}
{{#isQueryParam}}
{{#isNumber}}
{{paramName}}Param, err := parseNumericParameter[float32](
query.Get("{{baseName}}"),{{#defaultValue}}
WithDefaultOrParse[float32]({{defaultValue}}, parseFloat32),{{/defaultValue}}{{^defaultValue}}{{#required}}
WithRequire[float32](parseFloat32),{{/required}}{{/defaultValue}}{{^defaultValue}}{{^required}}
WithParse[float32](parseFloat32),{{/required}}{{/defaultValue}}{{#minimum}}
WithMinimum[float32]({{minimum}}),{{/minimum}}{{#maximum}}
WithMaximum[float32]({{maximum}}),{{/maximum}}
)
if err != nil {
c.errorHandler(w, r, &ParsingError{Err: err}, nil)
return
}
{{/isNumber}}
{{#isFloat}}
{{paramName}}Param, err := parseNumericParameter[float32](
query.Get("{{baseName}}"),{{#defaultValue}}
WithDefaultOrParse[float32]({{defaultValue}}, parseFloat32),{{/defaultValue}}{{^defaultValue}}{{#required}}
WithRequire[float32](parseFloat32),{{/required}}{{/defaultValue}}{{^defaultValue}}{{^required}}
WithParse[float32](parseFloat32),{{/required}}{{/defaultValue}}{{#minimum}}
WithMinimum[float32]({{minimum}}),{{/minimum}}{{#maximum}}
WithMaximum[float32]({{maximum}}),{{/maximum}}
)
if err != nil {
c.errorHandler(w, r, &ParsingError{Err: err}, nil)
return
}
{{/isFloat}}
{{#isDouble}}
{{paramName}}Param, err := parseNumericParameter[float64](
query.Get("{{baseName}}"),{{#defaultValue}}
WithDefaultOrParse[float64]({{defaultValue}}, parseFloat64),{{/defaultValue}}{{^defaultValue}}{{#required}}
WithRequire[float64](parseFloat64),{{/required}}{{/defaultValue}}{{^defaultValue}}{{^required}}
WithParse[float64](parseFloat64),{{/required}}{{/defaultValue}}{{#minimum}}
WithMinimum[float64]({{minimum}}),{{/minimum}}{{#maximum}}
WithMaximum[float64]({{maximum}}),{{/maximum}}
)
if err != nil {
c.errorHandler(w, r, &ParsingError{Err: err}, nil)
return
}
{{/isDouble}}
{{#isLong}}
{{paramName}}Param, err := parseNumericParameter[int64](
query.Get("{{baseName}}"),{{#defaultValue}}
WithDefaultOrParse[int64]({{defaultValue}}, parseInt64),{{/defaultValue}}{{^defaultValue}}{{#required}}
WithRequire[int64](parseInt64),{{/required}}{{/defaultValue}}{{^defaultValue}}{{^required}}
WithParse[int64](parseInt64),{{/required}}{{/defaultValue}}{{#minimum}}
WithMinimum[int64]({{minimum}}),{{/minimum}}{{#maximum}}
WithMaximum[int64]({{maximum}}),{{/maximum}}
)
if err != nil {
c.errorHandler(w, r, &ParsingError{Err: err}, nil)
return
}
{{/isLong}}
{{#isInteger}}
{{paramName}}Param, err := parseNumericParameter[int32](
query.Get("{{baseName}}"),{{#defaultValue}}
WithDefaultOrParse[int32]({{defaultValue}}, parseInt32),{{/defaultValue}}{{^defaultValue}}{{#required}}
WithRequire[int32](parseInt32),{{/required}}{{/defaultValue}}{{^defaultValue}}{{^required}}
WithParse[int32](parseInt32),{{/required}}{{/defaultValue}}{{#minimum}}
WithMinimum[int32]({{minimum}}),{{/minimum}}{{#maximum}}
WithMaximum[int32]({{maximum}}),{{/maximum}}
)
if err != nil {
c.errorHandler(w, r, &ParsingError{Err: err}, nil)
return
}
{{/isInteger}}
{{#isBoolean}}
{{paramName}}Param, err := parseBoolParameter(
query.Get("{{baseName}}"),{{#defaultValue}}
WithDefaultOrParse[bool]({{defaultValue}}, parseBool),{{/defaultValue}}{{^defaultValue}}{{#required}}
WithRequire[bool](parseBool),{{/required}}{{/defaultValue}}{{^defaultValue}}{{^required}}
WithParse[bool](parseBool),{{/required}}{{/defaultValue}}
)
if err != nil {
w.WriteHeader(500)
return
}
{{/isBoolean}}
{{#isArray}}
{{#items.isNumber}}
{{paramName}}Param, err := parseNumericArrayParameter[float32](
query.Get("{{baseName}}"), ",", {{required}},
WithParse[float32](parseFloat32),{{#minimum}}
WithMinimum[float32]({{minimum}}),{{/minimum}}{{#maximum}}
WithMaximum[float32]({{maximum}}),{{/maximum}}
)
if err != nil {
c.errorHandler(w, r, &ParsingError{Err: err}, nil)
return
}
{{/items.isNumber}}
{{#items.isFloat}}
{{paramName}}Param, err := parseNumericArrayParameter[float32](
query.Get("{{baseName}}"), ",", {{required}},
WithParse[float32](parseFloat32),{{#minimum}}
WithMinimum[float32]({{minimum}}),{{/minimum}}{{#maximum}}
WithMaximum[float32]({{maximum}}),{{/maximum}}
)
if err != nil {
c.errorHandler(w, r, &ParsingError{Err: err}, nil)
return
}
{{/items.isFloat}}
{{#items.isDouble}}
{{paramName}}Param, err := parseNumericArrayParameter[float64](
query.Get("{{baseName}}"), ",", {{required}},
WithParse[float64](parseFloat64),{{#minimum}}
WithMinimum[float64]({{minimum}}),{{/minimum}}{{#maximum}}
WithMaximum[float64]({{maximum}}),{{/maximum}}
)
if err != nil {
c.errorHandler(w, r, &ParsingError{Err: err}, nil)
return
}
{{/items.isDouble}}
{{#items.isLong}}
{{paramName}}Param, err := parseNumericArrayParameter[int64](
query.Get("{{baseName}}"), ",", {{required}},
WithParse[int64](parseInt64),{{#minimum}}
WithMinimum[int64]({{minimum}}),{{/minimum}}{{#maximum}}
WithMaximum[int64]({{maximum}}),{{/maximum}}
)
if err != nil {
c.errorHandler(w, r, &ParsingError{Err: err}, nil)
return
}
{{/items.isLong}}
{{#items.isInteger}}
{{paramName}}Param, err := parseNumericArrayParameter[int32](
query.Get("{{baseName}}"), ",", {{required}},
WithParse[int32](parseInt32),{{#minimum}}
WithMinimum[int32]({{minimum}}),{{/minimum}}{{#maximum}}
WithMaximum[int32]({{maximum}}),{{/maximum}}
)
if err != nil {
c.errorHandler(w, r, &ParsingError{Err: err}, nil)
return
}
{{/items.isInteger}}
{{^items.isNumber}}
{{^items.isFloat}}
{{^items.isDouble}}
{{^items.isLong}}
{{^items.isInteger}}
{{paramName}}Param := strings.Split(query.Get("{{baseName}}"), ",")
{{/items.isInteger}}
{{/items.isLong}}
{{/items.isDouble}}
{{/items.isFloat}}
{{/items.isNumber}}
{{/isArray}}
{{^isNumber}}
{{^isFloat}}
{{^isDouble}}
{{^isLong}}
{{^isInteger}}
{{^isBoolean}}
{{^isArray}}
{{#defaultValue}}
{{paramName}}Param := "{{defaultValue}}"
if query.Has("{{baseName}}") {
{{paramName}}Param = query.Get("{{baseName}}")
}
{{/defaultValue}}
{{^defaultValue}}
{{paramName}}Param := query.Get("{{baseName}}")
{{/defaultValue}}
{{/isArray}}
{{/isBoolean}}
{{/isInteger}}
{{/isLong}}
{{/isDouble}}
{{/isFloat}}
{{/isNumber}}
{{/isQueryParam}}
{{#isFormParam}}
{{#isFile}}{{#isArray}}
{{paramName}}Param, err := ReadFormFilesToTempFiles(r, "{{baseName}}"){{/isArray}}{{^isArray}}
{{paramName}}Param, err := ReadFormFileToTempFile(r, "{{baseName}}")
{{/isArray}}
if err != nil {
c.errorHandler(w, r, &ParsingError{Err: err}, nil)
return
}
{{/isFile}}
{{#isLong}}{{#isArray}}
{{paramName}}Param, err := parseNumericArrayParameter[int64](
r.FormValue("{{baseName}}"), ",", {{required}},
WithParse[int64](parseInt64),{{#minimum}}
WithMinimum[int64]({{minimum}}),{{/minimum}}{{#maximum}}
WithMaximum[int64]({{maximum}}),{{/maximum}}
)
if err != nil {
c.errorHandler(w, r, &ParsingError{Err: err}, nil)
return
}
{{/isArray}}{{/isLong}}
{{#isInteger}}{{#isArray}}
{{paramName}}Param, err := parseNumericArrayParameter[int32](
r.FormValue("{{baseName}}"), ",", {{required}},
WithParse[int32](parseInt32),{{#minimum}}
WithMinimum[int32]({{minimum}}),{{/minimum}}{{#maximum}}
WithMaximum[int32]({{maximum}}),{{/maximum}}
)
if err != nil {
c.errorHandler(w, r, &ParsingError{Err: err}, nil)
return
}
{{/isArray}}{{/isInteger}}
{{^isFile}}
{{^isLong}}
{{paramName}}Param := r.FormValue("{{baseName}}")
{{/isLong}}
{{/isFile}}
{{/isFormParam}}
{{#isHeaderParam}}
{{paramName}}Param := r.Header.Get("{{baseName}}")
{{/isHeaderParam}}
{{#isBodyParam}}
{{paramName}}Param := model.{{dataType}}{}
d := json.NewDecoder(r.Body)
{{^isAdditionalPropertiesTrue}}
d.DisallowUnknownFields()
{{/isAdditionalPropertiesTrue}}
if err := d.Decode(&{{paramName}}Param); err != nil {{^required}}&& !errors.Is(err, io.EOF) {{/required}}{
c.errorHandler(w, r, &ParsingError{Err: err}, nil)
return
}
{{#isArray}}
{{#items.isModel}}
for _, el := range {{paramName}}Param {
if err := Assert{{baseType}}Required(el); err != nil {
c.errorHandler(w, r, err, nil)
return
}
}
{{/items.isModel}}
{{/isArray}}
{{^isArray}}
{{#isModel}}
if err := Assert{{baseType}}Required({{paramName}}Param); err != nil {
c.errorHandler(w, r, err, nil)
return
}
if err := Assert{{baseType}}Constraints({{paramName}}Param); err != nil {
c.errorHandler(w, r, err, nil)
return
}
{{/isModel}}
{{/isArray}}
{{/isBodyParam}}
{{/allParams}}
result, err := c.service.{{nickname}}(r.Context(){{#allParams}}, {{paramName}}Param{{/allParams}})
// If an error occurred, encode the error with the status code
if err != nil {
c.errorHandler(w, r, err, &result)
return
}
// If no error, encode the body and the result code
EncodeJSONResponse(result.Body, &result.Code,{{#addResponseHeaders}} result.Headers,{{/addResponseHeaders}} w)
}{{/operation}}{{/operations}}