I'm trying to learn and use the API-first methodology with JHipster in Java and Spring. So far I have generated yaml file describing my API in OpenAPI 3.0 specification, then I have generated Java classes like Controllers and Delegates to implement for Services etc. but I got to the point where I wonder about database model of project (db is PostgreSQL).
When or how should I add database schema for API?
Should JHipster automatically generate Hibernate Entities for my project using yaml file (it didn't right now) or should I manually create database model in other tool? or should I have database model ready before creating API?
Is there a way to convert OpenAPI to Hibernate entities and import schema? What is correct workflow in JHipster in context of api-first + Hibernate/database schema?
I think the best way would be to have API ready and the rest to be done automatically.
My data type is described like this in api.yaml:
components:
schemas:
Advert:
description: Advert data type
required:
- id
- title
- description
- ownerId
- phoneNumber
- categoryId
- cityCode
- creationDate
type: object
properties:
id:
description: Id
type: integer
title:
description: Title of advert
type: string
description:
description: Description of advert
type: string
ownerId:
description: Id of owner user.
type: integer
phoneNumber:
description: Advert's phone contact number.
type: number
viewCount:
description: View count.
type: integer
categoryId:
description: Category of advert.
type: integer
images:
description: URLs of images.
type: array
items:
type: string
cityCode:
description: City code.
type: string
creationDate:
description: Creation date.
type: string
Related
With org.openapitools:openapi-generator-maven-plugin, I have noticed that using allOf composed of multiple objects in a response does not generate a class combining these multiple objects. Instead it uses the first class defined in the allOf section.
Here is a minimal example (openapi.yaml) :
openapi: 3.0.0
info:
title: Test
version: v1
paths:
/test:
get:
operationId: get
responses:
'200':
description: Get
content:
application/json:
schema:
allOf:
- $ref: '#/components/schemas/A'
- $ref: '#/components/schemas/B'
components:
schemas:
A:
type: object
properties:
attA:
type: string
B:
type: object
properties:
attB:
type: integer
When generating the classes in Java via :
mvn org.openapitools:openapi-generator-maven-plugin:5.2.0:generate \
-Dopenapi.generator.maven.plugin.inputSpec=openapi.yaml \
-Dopenapi.generator.maven.plugin.generatorName=java
It shows a warning:
[WARNING] allOf with multiple schemas defined. Using only the first one: A
As expected, it generates classes A and B. But, when calling get(), the value returned by the call is of type A:
DefaultApi api = new DefaultApi();
A a = api.get();
Instead, I would have expected a composite object containing A and B properties (attA and attB), like this (result from https://editor.swagger.io/):
I have created an issue on GitHub, but hopefully someone here may have had the same issue and managed to fix it.
Also, I can't modify the openapi.yaml file because it's an OpenAPI spec provided by an API I have to call, so modifying it would make no sense and will make it so difficult to manage if their OpenAPI spec change over time.
I use OpenAPI spec to generate Java POJOs. What do I need to specify in Open API yaml to generate the equivalent of below POJO ?
...
#JsonIgnore
public String ignoredProperty;
...
I have the yaml spec as below
openapi: 3.0.0
info:
title: Cool API
description: A Cool API spec
version: 0.0.1
servers:
- url: http://api.cool.com/v1
description: Cool server for testing
paths:
/
...
components:
schemas:
MyPojo:
type: object
properties:
id:
type: integer
name:
type: string
# I want the below attribute to be ignored as a part of JSON
ignoreProperty:
type: string
the openapi generator supports vendor extensions. Specifically, for the Java generator, it supports the following extensions as of the time of writing. However, an up-to-date list can be found here.
Extension name
Description
Applicable for
Default value
x-discriminator-value
Used with model inheritance to specify value for discriminator that identifies current model
MODEL
x-implements
Ability to specify interfaces that model must implements
MODEL
empty array
x-setter-extra-annotation
Custom annotation that can be specified over java setter for specific field
FIELD
When field is array & uniqueItems, then this extension is used to add #JsonDeserialize(as = LinkedHashSet.class) over setter, otherwise no value
x-tags
Specify multiple swagger tags for operation
OPERATION
null
x-accepts
Specify custom value for 'Accept' header for operation
OPERATION
null
x-content-type
Specify custom value for 'Content-Type' header for operation
OPERATION
null
x-class-extra-annotation
List of custom annotations to be added to model
MODEL
null
x-field-extra-annotation
List of custom annotations to be added to property
FIELD
null
x-webclient-blocking
Specifies if method for specific operation should be blocking or non-blocking(ex: return Mono<T>/Flux<T> or return T/List<T>/Set<T> & execute .block() inside generated method)
OPERATION
false
You can use the x-field-extra-annotation vendor extension listed above to add annotations to any field. So, for your example, you can add the following:
openapi: 3.0.0
info:
title: Cool API
description: A Cool API spec
version: 0.0.1
servers:
- url: http://api.cool.com/v1
description: Cool server for testing
paths:
/
...
components:
schemas:
MyPojo:
type: object
properties:
id:
type: integer
name:
type: string
# I want the below attribute to be ignored as a part of JSON
ignoreProperty:
type: string
x-field-extra-annotation: "#com.fasterxml.jackson.annotation.JsonIgnore"
I would like the OpenAPI Generator (https://github.com/OpenAPITools/openapi-generator) to be able to generate Pageable parameter in API according to the implementation in Spring Boot Data. I've been trying to find a suitable, out of the box solution, but couldn't find one.
Ideally, this Pageable parameter should be added only to GET methods in a following manner:
default ResponseEntity<User> getUser(#ApiParam(value = "value",required=true) #PathVariable("id") Long id, **Pageable pageable**)
So after implementing this interface in my Controller I would need to override it and having this aforementioned Pageable parameter. I don't want to have separate parameters for size or page, only this Pageable here.
Thanks for any tips and help!
Unfortunately this is no final solution but it is half way. Maybe it is of help anyway.
By defining the pageable parameters (size, page etc.) as an object query parameter it is possible to tell the generator to use the Spring object instead of generating a Pageable class from the api. This is done by an import mapping.
in gradle:
openApiGenerate {
....
importMappings = [
'Pageable': 'org.springframework.data.domain.Pageable'
]
}
which tells the generator to use the Spring class instead of the one defined in the api:
openapi: 3.0.2
info:
title: Spring Page/Pageable API
version: 1.0.0
paths:
/page:
get:
parameters:
- in: query
name: pageable
required: false
schema:
$ref: '#/components/schemas/Pageable'
responses:
...
components:
schemas:
Pageable:
description: minimal Pageable query parameters
type: object
properties:
page:
type: integer
size:
type: integer
The issue with the mapping is that the generator still adds a #RequestParam() annotation and that breaks it again. It only works if it is NOT annotated.
If you are a bit adventurous you could try openapi-processor-spring (i'm the author). It it does handle the example above. But it may have other limitations you don't like.
I faced the problem related to Swagger and Java. My lecturer sent me a Swagger file from which I should create a REST API. Also, that REST API should export the same Swagger documentation as Lecturers.
In the Swagger definitions I found that there should be created 2 Models: Odd(object) and Bet(array). Everything is fine with the Odd Model, but I do not find a solution on how to create Bet array. If I simply create an ArrayList named Bet in the getOdd method and put all Odd objects inside, the model will not be created.
I was looking for solutions, but I did not succeed. Thank you in advance.
Lecturer Swagger file:
swagger: "2.0"
info:
description: "Schema"
version: "1.0.0"
title: "API"
tags:
- name: "odds"
description: "Offer and return Odds"
schemes:
- "http"
paths:
/odds:
post:
tags:
- "odds"
summary: "Offer odds for a bet"
consumes:
- "application/json"
produces:
- "application/json"
parameters:
- in: "body"
name: "body"
description: "Odds that should be offered for a bet"
required: true
schema:
$ref: "#/definitions/Odds"
responses:
201:
description: "Odds have been created for bet"
400:
description: "Invalid format of Odds"
/odds/{betId}:
get:
tags:
- "odds"
summary: "Find Odds by Bet ID"
description: "Returns a list of odds for a given bet ID"
produces:
- "application/json"
parameters:
- name: "betId"
in: "path"
description: "ID of bet to return"
required: true
type: "integer"
format: "int64"
responses:
200:
description: "Odds are returned for bet ID"
schema:
$ref: "#/definitions/Bet"
400:
description: "Invalid Bet ID supplied"
404:
description: "Bet not found for given ID"
definitions:
Odds:
type: "object"
properties:
betId:
type: "integer"
format: "int64"
userId:
type: "string"
description: "ID of user who is offering the odds"
odds:
type: "string"
example: "1/10"
**Bet:
type: "array"
items:
$ref: '#/definitions/Odds'**
How Models should look like in Swagger
How getOdd method should look like in Swagger
I will paste some of my work done:
How my Models looks like in Swagger
How my getOdd method looks like in Swagger
My Rest Controller:
#RestController
#RequestMapping("/api")
public class OddController {
#Autowired
OddRepository oddRepository;
#GetMapping("/odds/{betId}")
public Optional<Odd> getOdd(#PathVariable Long betId) {
Optional<Odd> theOdd=oddRepository.findById(betId);
return theOdd;
}
#PostMapping("/odds")
public Odd addOdd(#RequestBody Odd odd) {
odd.setBetId((long) 0);
oddRepository.save(odd);
return odd;
}
My Odd class:
#Entity
#Table(name="odds")
#Data
public class Odd {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="betid")
private Long betId;
#Column(name="userid")
private String userId;
#Column(name="odds")
private String odds;
}
You can use annotations to control the generation of the swagger definitions. There is a old and a new api to do that:
Old:
https://github.com/swagger-api/swagger-core/wiki/Annotations-1.5.X
New:
https://github.com/swagger-api/swagger-core/wiki/Swagger-2.X---Annotations
In the lecture swagger file 'swagger: "2.0"' is used. Therefore it would be the old one. The new one is producing swagger files for OpenApi 3.0.
Specially the annotation #ApiOperation and #ApiModelOperation could be interesting for you to solve your problem.
See also the JavaDoc:
#ApiOperation: https://docs.swagger.io/swagger-core/v1.5.X/apidocs/index.html?io/swagger/annotations/ApiOperation.html
#ApiModelProperty: https://docs.swagger.io/swagger-core/v1.5.X/apidocs/index.html?io/swagger/annotations/ApiModelProperty.html
In my Spring API designed with Swagger 2.0, I'm trying to create Inheritance using swagger. I want to create a base object, which will have common properties for both Request and Response objects. I tried to do it like the example below:
CategoryResponse:
allOf:
- $ref: '#/definitions/Category'
- type: object
properties:
id:
type: integer
example: '1'
CategoryRequest:
type: object
allOf:
- $ref: '#/definitions/Category'
Category:
discriminator: nameCategory
type: object
properties:
nameCategory:
type: string
example: Games
The problem is that I get a Bad Request error when trying to POST or PUT a new CategoryRequest object. It doesn't even gets to the API Controller, so I guess the problem might in the model definition above. I tried many variations, but none of them worked. However, when I try to GET the list of categories, or one category by id, i'm able to do so (My CategoryResponse is working and extending Category fine).
Does anybody knows the correct way of creating this structure using inheritance of a common base model, both for Request and Response objects?
Thanks in advance!
id looks like an auto-generated and read-only property. In that case you don't need inheritance - you can use a single Category schema and mark id as readOnly: true.
Category:
type: object
properties:
nameCategory:
type: string
example: Games
id:
type: integer
readOnly: true # <-----
example: 1
From the OpenAPI Specification:
readOnly
Declares the property as "read only". This means that it MAY be sent as part of a response but MUST NOT be sent as part of the request.