I’m trying to determine what might be the recommen...
# questions
s
I’m trying to determine what might be the recommended approach (in Grails 6) for building out a rest endpoint, including being able to generate an OpenAPI spec
j
We use the swagger plugin io.swagger.core.v3.swagger-gradle-plugin to generate and then just the dependency: `
Copy code
implementation "io.swagger.core.v3:swagger-annotations:${swaggerVersion}"
to annotate our comamnds, etc.
For databinding, the default grails json leaks too much data for our liking. You can either use gson views or you can use something like Jackson if you prefer more restricted data.
A good example is with enums - since grails will send the class name, etc by default.
m
for a grails 2 legacy project we use extensively json marshallers. Named configurations feature is very useful in order to respond with a specific json structure. https://stackoverflow.com/questions/27129892/how-to-have-several-custom-json-marshaller-for-a-class
c
@jdaugherty - is there a howto or easy steps to set up swagger using the annotations you mention? I added this but we don't seem to be generating any output wrt the controllers. Just looking for a simple approach to get the basics working
j
We check in a file at the root of our project called:
openapi.yaml
This file contains all of the app info since we don't use the annotations on the application, etc (only controllers and associated components), so something like this:
#file: noinspection YAMLSchemaValidation
openapi: 3.0.1
info:
title: My API Name
description: |
Markdown syntax here to describe your API
version: API_VERSION
x-logo:
url: URL Logo
href: URL Website
servers:
- url: example link to your api
description: Production API
variables: {}
security:
- Bearer: []
# use use x-tagGroups: & tags: since we use redoc to render our api spec
components:
securitySchemes:
Bearer:
type: http
description: description of your security scheme, any caveats
scheme: bearer
bearerFormat: JWT
Then in our gradle file:
tasks.register('updateOpenAPIVersion', Copy) {
from "openapi.yaml"
into "$buildDir/tmp"
filter { line -> line.replaceAll('API_VERSION', project.version) }
}
// Generates api spec that we serve up via redoc
// However, you can also generate static documentation, install the redoc-cli via 'npm i -g redoc-cli'
// Run 'gradle resolve' and then 'redoc-cli build build/resources/main/openapi/my-api-name.yaml -o build/my-api-name.html'
//
resolve {
dependsOn(tasks.updateOpenAPIVersion, classes)
classpath = sourceSets.main.runtimeClasspath
outputDir = file("$buildDir/resources/main/openapi")
outputFileName = 'my-api-name.yaml'
openApiFile = file("$buildDir/tmp/openapi.yaml")
outputFormat = 'YAML'
resourcePackages = ['com.example'] # packages to search for the annotations
readAllResources = false
prettyPrint = true
}
bootJarMainClassName {
dependsOn(resolve)
}
dependencies {
// other dependencies
implementation "io.swagger.core.v3swagger annotations${swaggerVersion}"
implementation "javax.ws.rs:javax.ws.rs-api:2.1.1"
}
we also add a doc controller :
```@Secured(AuthenticatedVoter.IS_AUTHENTICATED_ANONYMOUSLY)
class RedocController {
def index() {
Resource redocHtml = ApplicationContextHolder.applicationContext.getResource("classpath:redoc/index.html")
try(InputStreamReader reader = new InputStreamReader(redocHtml.inputStream)) {
render(text: reader.text, contentType: "text/html", encoding: "UTF-8")
}
}
def definition() {
Resource apiYaml = ApplicationContextHolder.applicationContext.getResource("classpath:openapi/my-api-name.yaml")
try(InputStreamReader reader = new InputStreamReader(apiYaml.inputStream)) {
render(text: reader.text, contentType: "application/x-yaml", encoding: "UTF-8")
}
}
}```
with a index.html similar to this:
<!DOCTYPE html>
<html lang="en">
<head>
<title>My API Name | API Reference</title>
<!-- needed for adaptive design -->
<meta charset='UTF-8'/>
<meta name='viewport' content='width=device-width, initial-scale=1'/>
<!-- prevent IE from caching this page -->
<meta http-equiv="Cache-Control" content="no-store"/>
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="-1">
<!-- force this page to be rendered with the latest version of IE -->
<meta http-equiv="x-ua-compatible" content="IE=edge">
<meta name="layout" content="none"/>
<link href='https://fonts.googleapis.com/css?family=Montserrat:300,400,700%7CRoboto:300,400,700' rel='stylesheet'/>
<!-- ReDoc doesn't change outer page styles -->
<style>
body {
margin: 0;
padding: 0;
}
</style>
</head>
<body>
<redoc spec-url='/definition' path-in-middle-panel="true" expand-responses="all" showObjectSchemaExamples="true" showExtensions="true"></redoc>
<script src='https://cdn.jsdelivr.net/npm/redoc@next/bundles/redoc.standalone.js'></script>
</body>
</html>
so to recap: use the resolve task to generate the api documentation, in our case we define some of it statically and merge it, use a controller to service the spec, and use an action to serve redocly to render the spec so it's interactive
i missed one piece: you have to add the swagger plugin to your gradle file too:
Copy code
plugins {
    id "io.swagger.core.v3.swagger-gradle-plugin"
}
but that's basically it, it was surprising simple
c
Thanks @jdaugherty, that was most helpful! I had an issue with the SnakeYAML library version but could work around it. I can now annotate my controllers and generate the file with the contents. šŸ™‚
šŸ‘ 1