OpenAPI: combine JAXB and JSON (WRAPPER_OBJECT) correctly? - openapi

OpenAPI: combine JAXB and JSON (WRAPPER_OBJECT) correctly?
Dear community,
I'm a beginner with swagger/openapi. I want write Java classes with annotations and generate
documentation by OpenAPI library.
In my gradle project I use:
implementation "io.swagger.core.v3:swagger-core:2.2.2"
implementation 'io.swagger.core.v3:swagger-annotations:2.2.2'
implementation "io.swagger.core.v3:swagger-jaxrs2:2.2.2"
implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.13.2'
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.13.2'
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.13.2'
My demo class is:
#XmlRootElement(name = "Cat")
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(propOrder = { "id", "name" })
#JsonTypeName("Cat")
#JsonTypeInfo(include = As.WRAPPER_OBJECT, use = Id.NAME)
public class Cat {
private final String id;
private final String name;
public Cat(String id, String name) {
this.id = id;
this.name = name;
}
public Cat() { // Just for JAXB and demo
this("n/a", "n/a");
}
// Getters
}
Serializing a object Cat cat = new Cat("01", "Kitty"); with JAVA works fine (as expected).
JSON: (it is wrapped, as requested by As.WRAPPER_OBJECT)
{"Cat":{"id":"01","name":"Kitty"}}
XML:
<Cat><id>01</id><name>Kitty</name></Cat>
My service class is:
#OpenAPIDefinition(info = #Info(title = "CatSwagger", description = "MyDescr01", version = "1.2"))
#Path("/public/v1")
public interface CatService {
#Operation(summary = "getCat", description = "OpDescr")
#GET
#Path("/cat")
#Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
public Cat getCat();
}
The problem:
The generated YAML file, uploaded to swagger editor.
I see a correct JSON representation (fine and wrapped)
I can't see the XML representation (broken).
->XML example cannot be generated; root element name is undefined
For me it looks, the JSON/WRAP instruction will break the XML definition.
YAML:
openapi: 3.0.1
info:
title: MySwagger01
description: MyDescr01
version: "1.2"
paths:
/public/v1/cat:
get:
summary: getCat
description: OpDescr
operationId: getCat
responses:
default:
description: default response
content:
application/json:
schema:
type: object
properties:
Cat:
$ref: '#/components/schemas/Cat'
application/xml:
schema:
type: object
properties:
Cat:
$ref: '#/components/schemas/Cat'
components:
schemas:
Cat:
type: object
properties:
id:
type: string
name:
type: string
xml:
name: Cat
I found, if I remove the object definition for operation /public/v1/cat from the XML defintion,
just have a reference:
application/xml:
schema:
type: object
properties:
Cat:
$ref: '#/components/schemas/Cat'
to
application/xml:
schema:
$ref: '#/components/schemas/Cat'
So swagger editor will show my documentation as expected. JSON and XML are looking fine.
Is there any way to control the YAML generation by swagger annotation and fix my problem?
Thanks in advance
Uwe

Related

OpenAPI V3 Maven Plugin generates incomplete interface code with multiple file upload using multipart/form-data

I am using Openapi V3 with Maven plugin openapi-generator-maven-plugin (5.3.0) for multiple files upload with multipart/form-data. However, the interface code generated is incomplete - missing List declaration in one of the default method (One method declares input parameter as List<MultipartFile> file and the other method declares it as MultipartFile file):
default ResponseEntity<UploadDocumentsResponse> _uploadDocuments(#Valid #RequestPart(value = "file", required = false) List<MultipartFile> file) {
return uploadDocuments(authorization, file);
}
// Override this method
default ResponseEntity<UploadDocumentsResponse> uploadDocuments(MultipartFile file) {
...
}
Here is the spec:
/api/uploadDocuments:
post:
tags:
- documents
summary: Upload documents
description: Upload documents
operationId: uploadDocuments
requestBody:
required: true
content:
multipart/form-data:
schema:
type: object
properties:
file:
type: array
items:
type: string
format: binary

How to add JAX-RS Response in OpenAPI YAML file

I have a REST service and I have defined the API signature using OpenAPI's YAML file.
Something like,
title: Sample Pet Store App
description: This is a sample server for a pet store.
termsOfService: http://example.com/terms/
contact:
name: API Support
url: http://www.example.com/support
email: support#example.com
paths:
v1/employees/{employeeId}:
get:
responses:
'200':
content:
....
From the YAML file, I generate the API requests using something like OpenAPI generator.
But how do I specify a https://docs.oracle.com/javaee/7/api/javax/ws/rs/core/Response.html, in my YAML file?
This is how I want to send a response from my Java code. I want to know how I can add this Response object to OpenAPI's YAML?
import javax.ws.rs.core.Response;
#Path("/v1/employees")
public Response getEmployee(String employeeId) {
// ...
return Response
.status(Response.Status.OK)
.entity(employee)
.build();
}
I am new to REST API development. I checked the documentation, but couldn't find details in OpenAPI's on how to add a Javax Response.
Depends on the template you use, by default it is not there, but you can create a custom template to use.
here is the list of available templates.
you must specify type of response you return on OpenAPI spec. like this:
v1/employees/{employeeId}:
get:
operationId: getUser
responses:
200:
description: Return user
content:
application/json:
schema:
$ref: '#/components/schemas/UsertResponseDTO'
after that, if you use default template, add manually typo response like this:
import javax.ws.rs.core.Response;
#Path("/v1/employees")
public Response getEmployee(String employeeId) {
// ...
return Response.ok(employee).build();
}
To solve my problem, instead of returning a Response object, I went with throwing a javax.ws.rs.WebApplicationException and adding an ExceptionTranslator code to convert all my exceptions into a WebApplicationException.
Here is a sample code on the exception translation.
// Actual code to convert Java exception into javax.ws.rs.WebApplicationException.
catch (Exception e) {
throw new WebApplicationException(getResponse(e));
}
// Exception translation code sample. This can be made into a nice generic function to handle different types of exceptions.
Response getResponse(Throwable t) {
if (throwable instanceof NotFoundException) {
Error error = new Error();
error.setType("404");
error.setMessage("Requested entry could not be found");
Response.status(Status.NOT_FOUND)
.entity(error)
.build();
}
}

Typescript - Get uninitialized properties after compilation

I am currently writing a wrapper around socket.io. Comming from a very object-oriented background, I want to implement the concept of Models in my framework/wrapper.
If you happen to know socket.io you might know that you get the data that is associated with an event as a parameter, now I have implemented a custom routing system where the handler of the route gets the data in an express.js like request object.
The idea is to have model classes that look something like this:
class XRequestModel
#v.String({ message: 'The username must be a string!' })
public userName: string;
}
And the route event might look something like this:
#RouteConfig({ route: '/something', model: XRequestModel })
class XEvent extends Route {
public on(req: Request<XRequestModel>, res: Response) {
// Handle Event
}
}
And to complete the example here is how the request object might look like:
class Request<T> {
public data: T;
}
Now generics in typescript are very limited since the type information is removed after compilation, I can not use the generic Request parameter ( which is the type of the model ) to get metadata from the model - Metadata, in this case, is the validation decorator. To overcome this issue I give a reference of the Model class to the RouteConfig of the RouteEvent, which is internally used and would allow me to create instances of the model, get the properties and so on...
The idea here is to give the handler of a route, a request object with pre-validated, typesafe data.
The thing holding me back from this, is the fact that unused properties, get removed after compilation by typescript, So I cannot get the metadata of the model. Initializing the class-property would solve this:
class XRequestModel
#v.String({ message: 'The username must be a string!' })
public userName: string = '';
}
But I think this makes for some very verbose syntax, and I dont want to force the user of this wrapper to init all the model properties.
An implementation side-note:
The user of the framework has to register the classes to a 'main' class and from there I can get the Route-class via decorator reflection.
When I try to get the properties of the model without initialized properties - First model example.
// Here the route.config.model refers to the model from the RouteConfig
Object.getOwnPropertyNames(new route.config.model());
>>> []
Here is what I get with initialized properties:
Object.getOwnPropertyNames(new route.config.model());
>>> [ 'userName' ]
Here a link to the GitHub repository: https://github.com/FetzenRndy/SRocket
Note that models are not implemented in this repo yet.
Basically, my question is: How can I get the properties of a class that has uninitialized properties after compilation.
The problem is that if no initialization happens, no code is emitted for the fields, so at runtime the field does not exist on the object until a value is assigned to it.
The simplest solution would be to initialize all fields even if you do so with just null :
class XRequestModel {
public userName: string = null;
public name: string = null;
}
var keys = Object.getOwnPropertyNames(new XRequestModel())
console.log(keys); // [ 'userName', 'name' ]
If this is not a workable solution for you, you can create a decorator that adds to a static field on the class and the walk up the prototype chain to get all fields:
function Prop(): PropertyDecorator {
return (target: Object, propertyKey: string): void => {
let props: string[]
if (target.hasOwnProperty("__props__")) {
props = (target as any)["__props__"];
} else {
props = (target as any)["__props__"] = [];
}
props.push(propertyKey);
};
}
class XRequestModelBase {
#Prop()
public baseName: string;
}
class XRequestModel extends XRequestModelBase {
#Prop()
public userName: string;
#Prop()
public name: string;
}
function getAllProps(cls: new (...args: any[]) => any) : string[] {
let result: string[] = [];
let prototype = cls.prototype;
while(prototype != null) {
let props: string[] = prototype["__props__"];
if(props){
result.push(...props);
}
prototype = Object.getPrototypeOf(prototype);
}
return result;
}
var keys = getAllProps(XRequestModel);
console.log(keys);

grails 3 mongodb: Method on class [] was used outside of a Grails application

I am using the following:
Grails Version: 3.0.1
Groovy Version: 2.4.3
JVM Version: 1.8.0_05
mongodb: 3.0.3
I have two domain objects that look like this:
class PhoneNumber {
String country
String numberString
static constraints = {
country nullable: false, size: 2..2
numberString nullable: false, blank: false, size: 1..16
}
}
and
class Contact {
String name
static hasMany = [phoneNumber: PhoneNumber]
static embedded = ['phoneNumber']
static constraints = { }
}
I have a controller that looks like this:
class ContactController extends RestfulController {
static responseFormats = ['json', 'xml']
ContactController() { super(Contact) }
#Transactional
def save(Contact contact) {
println contact
response.status = 201
def result = [:]
result.id = 1
render result as JSON
}
}
When I POST to the controller via:
curl -XPOST "http://localhost:8080/contact" -d "#contact.json"
I get a response of {"id":1}. However if I add the following line to my Contact and PhoneNumber domain objects:
static mapWith = 'mongo'
I get the following error:
ERROR org.grails.web.errors.GrailsExceptionResolver - IllegalStateException occurred when processing request: [POST] /contact - parameters:
{"id":null,"name":"Full Name","phoneNumber":[{"country":"ca","numberString":"18095551212"},{"country":"ca","numberString":"16135551212"}]}:
Method on class [xxx.Contact] was used outside of a Grails application. If running in the context of a test using the mocking API or bootstrap Grails correctly.. Stacktrace follows:
java.lang.IllegalStateException: Method on class [demo.Contact] was used outside of a Grails application. If running in the context of a test using the mocking API or bootstrap Grails correctly.
at grails.transaction.GrailsTransactionTemplate$2.doInTransaction(GrailsTransactionTemplate.groovy:93) ~[grails-core-3.0.1.jar:3.0.1]
at grails.transaction.GrailsTransactionTemplate.execute(GrailsTransactionTemplate.groovy:90) ~[grails-core-3.0.1.jar:3.0.1]
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) ~[na:1.8.0_05]
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) ~[na:1.8.0_05]
at java.lang.Thread.run(Thread.java:745) [na:1.8.0_05]
What else needs to be done to get mongodb domain objects marshalled on a POST?
Explicitly define the id field as an ObjectId when using MongoDB.
domain/com/example/Book.groovy
package com.example
import org.bson.types.ObjectId
class Book {
ObjectId id
String title
String author
static mapWith = "mongo"
}
BSON IDs are not simple long numbers — they contain four parts, including a timestamp. When converted to a String (example: book.id as String), the ID will be 24 characters long and look something like: "556a7299aa2437211f8e4e73"
See:
https://docs.mongodb.com/manual/reference/method/ObjectId

broken relations when loading from fixture / yaml in play framework

I have found a problem with the play framework. I could also reproduce it, so here I will show a simplified reproduction szenario.
When starting the play application, I want to read sample data from a yaml file. Therefore, i use the class Fixtures. In the yaml file I have prepared a data structure of objects that stand in relation to each other.
The model classes of the data structure look like this:
#Entity
public class Album extends Model{
public String name;
#ManyToOne
public Artist artist;
}
#Entity
public class Artist extends Model{
public String name;
#OneToMany(mappedBy="artist")
public List<Album> albums = new ArrayList<Album>();
}
The Job that i use to load the yaml file and control the result looks like this:
#OnApplicationStart
public class Bootstrap extends Job {
#Override
public void doJob(){
Fixtures.deleteAllModels();
Fixtures.loadModels("sample.yml");
List<Artist> artists = Artist.findAll();
for (Artist artist : artists) {
play.Logger.info(artist.name + " has " + artist.albums.size() + " albums");
}
}
}
if i use the following structure in my yml file, then it works:
Artist(b1):
name: Nirvana
Artist(b2):
name: ACDC
Album(a1):
name: Back in Black
artist: b2
Album(a2):
name: Highway to Hell
artist: b2
Album(a3):
name: Nevermind
artist: b1
Album(a4):
name: Bleach
artist: b1
But if i do it like this, then it will NOT work:
Album(a1):
name: Back in Black
Album(a2):
name: Highway to Hell
Album(a3):
name: Nevermind
Album(a4):
name: Bleach
Artist(b1):
name: Nirvana
albums: [a3,a4]
Artist(b2):
name: ACDC
albums: [a1,a2]
However, the documentation right here tells us, that the second way should work.
Did I make a mistake in my example code, or is this really a problem with the play framework or JPA?
No, according documentation your second try should not work. Problem lies in concept of relationship owner. Only owner side (one referenced by mappedby) is consulted when bidirectional relationship is persisted.
In your case
Artist(b1):
name: Nirvana
albums: [a3,a4]
Operates to following list, which is not owner of relationship:
//owner of this relationship if attribute artist in Album entity.
#OneToMany(mappedBy="artist")
public List<Album> albums = new ArrayList<Album>();
Your first try uses artist field in Album. It works, because artist is owner of bidirectional relationship between Album and Artist. Because of same reason also example in documentation that you linked works as well.