Spring MVC Converter and Swagger doc: how to? - spring-boot

I use converters in my Spring MCV controllers. In this example, the String from the path variable is mapped into a UserId:
#GetMapping(path = "/user/{user-id}")
public User get(#Parameter(description = "User id", required = true, example = "3fa85f64-5717-4562-b3fc-2c963f66afa6")
#PathVariable("user-id")
UserId userId) {
return userService.get(userId)
}
It seems to annoy Swagger as the generated doc requires an object as parameter and not a plain string:
...
"/api/v1/user/{user-id}": {
"get": {
"operationId": "get",
"parameters": [
{
"name": "user-id",
"in": "path",
"schema": {
"$ref": "#/components/schemas/UserId"
},
}
],
...
with the UserId schema:
"UserId": {
"type": "object",
"properties": {
"value": {
"type": "string",
"format": "uuid"
}
}
}
And thus the Swagger UI cannot be used because either the parameter is considered as invalid when a single string is provided, either the data is actually invalid when the object format is used.
What is an option to fix that?

To achieve that, the schema parameter of the #Parameter annotation is the answer.
The above example becomes:
#Parameter(description = "User id",
required = true,
schema = #Schema(implementation = String.class), // this is new
example = "3fa85f64-5717-4562-b3fc-2c963f66afa6")

Related

Customizing the GraphQL schema of a Gatsby project with nested types breaks functionality of transformer plugins

In a project where I'm sourcing data from NetlifyCMS I need to extend the GraphQL schema of Gatsby because there are optional fields in my CMS, which would cause errors trying to query non-existing data from GraphQL.
The below code extends the GraphQL types to always include the optional fields
// gatsby-node.js
// ADD OPTIONAL TYPES
// note: I have also tried exports.sourceNodes
exports.createSchemaCustomization = ({ actions }) => {
const { createTypes } = actions
const typeDefs = `
type SettingsJson implements Node {
tags: String
name: String
phone: String
email: String
additional_info: [SettingsJsonAdditional_info]
}
type SettingsJsonAdditional_info {
type: String
text: String
}
`
createTypes(typeDefs)
}
Sourcing and transforming the tags, name, phone and email work as expected. Queries return null if an optional field has not been filled out, and the query returns the correct value after being set. However, querying for additional_info always returns null even when containing data.
This is the json-file generated by NetlifyCMS:
{
"name": "Name Nameson",
"phone": "+41 1234 5678",
"email": "mail#example.com",
"additional-info": [
{
"type": "Booking",
"text": "Booker McBookerson <book#book.com>"
}
]
}
The following GraphQL query shows that the data is not being transformed properly when extending the GraphQL schema myself.
Query
query {
file(relativePath: {eq: "settings/contacts.json"}) {
internal {
content
}
childSettingsJson {
name
phone
email
additional_info {
type
text
}
}
}
}
Response
{
"data": {
"file": {
"internal": {
"content": "{\n \"name\": \"Name Nameson\",\n \"phone\": \"+41 1234 5678\",\n \"email\": \"mail#example.com\",\n \"additional-info\": [\n {\n \"type\": \"Booking\",\n \"text\": \"Booker McBookerson <book#book.com>\"\n },\n {\n \"type\": \"Booking2\",\n \"text\": \"Booker2 McBookerson <book#book.com>\"\n }\n ]\n}"
},
"childSettingsJson": {
"name": "Name Nameson",
"phone": "+41 1234 5678",
"email": "mail#example.com",
"additional_info": null
}
}
},
"extensions": {}
}
When the types are inferred by the transformer plugin itself I get the expected data when querying
// ...
"additional_info": [
{
"type": "Booking",
"text": "Booker McBookerson <book#book.com>"
}
]
// ...
This example uses json files with gatsby-transformer-json. I have tried with gatsby-transformer-yaml too with the same results.
Is it possible to add my array of SettingsJsonAdditional_info to the schema to get the "optional field" functionality I'm looking for?
One kind of hacky solution I have found is to make the transformer plugin infer the types by adding a dummy-file that will be sourced and transformed along with "real" files.
// dummy.json
{
"name": "dummy",
"phone": "dummy",
"email": "dummy",
"tags": "dummy",
"additional-info": [
{
"type": "dummy",
"text": "dummy"
}
]
}
This file can be hidden from NetlifyCMS (by simply not including a UI entry for it in the config.yml file of NetlifyCMS. It will guarantee that you can always query for the fields included in this file without getting GraphQL "field doesn't exist" errors.

springdoc-openapi-ui add JWT header parameter to generated swagger

In my spring boot app I have endpoints which are validated by header parameter in my springboot app.
Current swagger json looks like this:
// part of current swagger.json
...
"paths": {
"/path1/{param1}": {
"get": {
"parameters": [
{
"name": "param1",
"in": "path",
"type": "string",
"required": true
}
]
}
}
}
...
I want to add missing parameter using springdoc-openapi-ui configuration so it would look like this:
// I want to achieve swagger.json which contains additional parameter
...
"paths": {
"/path1/{param1}": {
"get": {
"parameters": [
{
"name": "param1",
"in": "path",
"type": "string",
"required": true
},
{
"name": "missingParam",
"in": "header",
"type": "string",
"required": true
}
]
}
}
}
...
I tried achieving that by adding to my appplication.yml solution from Common Parameters for Various Paths
#application.yml
...
components:
parameters:
hiddenParam:
in: header
name: missingParam
required: true
schema:
type: string
paths:
/path1:
get:
parameters:
- $ref: '#/components/parameters/hiddenParam'
But it doesn't work.
My questions:
Is there a way to modify my swagger result using application configuration?
I want to define parameter template and add it to all endpoints, how can I achieve that?
You can add the global parameters like header using OperationCustomizer as shown below. This will add your parameter to every service
#Configuration
public class SwaggerConfiguration {
#Bean
public OperationCustomizer customGlobalHeaders() {
return (Operation operation, HandlerMethod handlerMethod) -> {
Parameter missingParam1 = new Parameter()
.in(ParameterIn.HEADER.toString())
.schema(new StringSchema())
.name("missingParam1")
.description("header description2")
.required(true);
Parameter missingParam2 = new Parameter()
.in(ParameterIn.HEADER.toString())
.schema(new StringSchema())
.name("missingParam2")
.description("header description2")
.required(true);
operation.addParametersItem(missingParam1);
operation.addParametersItem(missingParam2);
return operation;
};
}
}
Finally I decided to use different approach.
I defined security scheme and applied it globally as authorization header.
#Bean
public OpenAPI customOpenAPI() {
return new OpenAPI()
.info(new Info().title("My App").version("1.0.0"))
// Components section defines Security Scheme "mySecretHeader"
.components(new Components()
.addSecuritySchemes("mySecretHeader", new SecurityScheme()
.type(SecurityScheme.Type.APIKEY)
.in(SecurityScheme.In.HEADER)
.name("missingParam")))
// AddSecurityItem section applies created scheme globally
.addSecurityItem(new SecurityRequirement().addList("mySecretHeader"));
}
Now swagger-ui.html allows testing endpoints with authorization header or without it according to tester requirements.
Cheers!

Spring Boot ConfigurationProperties metadata generation - wrong default values

So I'm trying to generate my own configuration metadata from #ConfigurationProperties items, following documentation available at https://docs.spring.io/spring-boot/docs/current/reference/html/configuration-metadata.html#configuration-metadata-annotation-processor. Note that I'm using Spring Boot 2.0 and Kotlin.
It works fine, except for one thing: the defaultValue fields are not getting filled with my default values, but instead just contain standard values like 0 or false.
My #ConfigurationProperties file looks like this:
#Component
#ConfigurationProperties("flowr.epg")
class EpgProperties {
/** Whether or not schedules should be computed/refreshed on (re)start */
var refreshOnRestart = true
/** Number of threads used to store new images */
var nImagesUploadThreads = 10
}
The result looks like this:
{
"hints": [],
"groups": [
{
"sourceType": "com.taktik.flowr.epg.properties.EpgProperties",
"name": "flowr.epg",
"type": "com.taktik.flowr.epg.properties.EpgProperties"
}
],
"properties": [
{
"sourceType": "com.taktik.flowr.epg.properties.EpgProperties",
"defaultValue": false,
"name": "flowr.epg.refresh-on-restart",
"description": "Whether or not schedules should be computed\/refreshed on (re)start",
"type": "java.lang.Boolean"
},
{
"sourceType": "com.taktik.flowr.epg.properties.EpgProperties",
"defaultValue": 0,
"name": "flowr.epg.n-images-upload-threads",
"description": "Number of threads used to store new images in Ozone",
"type": "java.lang.Integer"
}
}
The documentation is kind of poor about that defaultValue field. Am I doing something wrong? Is it possible to fill that defaultValue field automatically? If yes, what am I doing wrong?

How to have nested configurations in config json file and read it using bit?

so far I've seen some sample code in which some configuration is inserted in a file named environment.json like this:
[
{
"Name": "Default",
"AppInfo": {
"Name": "blahblah",
"Version": "1"
},
"Configs": [
{
"Key": "someconfiguration",
"Value": "some value"
},
{
"Key": "another configuration ",
"Value": "blah blah"
},
]
}
]
and then when needed, data can be read from configuration file like this:
var value = DefaultAppEnvironmentsProvider.Current
.GetActiveAppEnvironment()
.GetConfig<string>("SomeConfiguration");
The Question is:
what if I want to have some configuration whose value is a nested json list or json object. I want some thing like this:
"key": "Address",
"value": {
"street": "some street name",
"postal code": "blah blah",
...
}
how can I read such configurations using bit?
thanks for your time in advance.
First of all, create a class which defines your configuration contract:
public class MailServerConfig
{
public Uri ServerUrl { get; set; }
public string AnotherConfig { get; set; }
}
Then add followings to your environments.json file:
,
{
"Key": "MailServerConfig",
"Value": {
"$type": "SampleApp.Configurations.MailServerConfig, SampleApp",
"ServerUrl": "https://google.com/",
"AnotherConfig": "!"
}
}
In your controllers (Or wherever you want to read your configs), inject AppEnv
public AppEnvironment AppEnv { get; set; }
Then read your config as below:
MailServerConfig mailServerConfig = AppEnv.GetConfig<MailServerConfig>("MailServerConfig");
Note that in environments.json, $type is mandatory, and it should be the first line of your nested json object.

Spring Data ElasticSearch Build In IN query returning partial match

I am new to elastic search spring data, Today I was trying to get In query working with Spring data ES repository.
I have to do a lookup for list of user names, and if its exactly match in the index, need to get those users back as result.
I tried to use the built in repository 'In' method to do so, but it returns partial matches, please help me to make this working like SQL IN query.
Here is my repository code:
public interface UserRepository extends ElasticsearchRepository<EsUser, String>
{
public List<EsUser> findByUserAccountUserNameIn(Collection<String> terms);
}
REQUEST:
{"terms":["vijay", "arun"], "type":"NAME"}
RESPONSE:
[
{
"userId": "236000",
"fbId": "",
"userAccount": {
"userName": "arun",
"urlFriendlyName": "arun",
},
"userProfile": {
},
"userStats": {
}
},
{
"userId": "6228",
"userAccount": {
"userName": "vijay",
"urlFriendlyName": "vijay",
},
"userProfile": {
},
"userStats": {
}
},
{
"userId": "236000",
"fbId": "",
"userAccount": {
"userName": "arun singh",
"urlFriendlyName": "arun-singh",
},
"userProfile": {
},
"userStats": {
}
}
{
"userId": "236000",
"fbId": "",
"userAccount": {
"userName": "vijay mohan",
"urlFriendlyName": "vijay-mohan",
},
"userProfile": {
},
"userStats": {
}
}
]
This is because your userAccount.userName field is an analyzed string, and thus, the two tokens arun and singh have been indexed. Your query then matches the first token, which is normal.
In order to prevent this and guarantee an exact match you need to declare your field as not_analyzed, like this:
#Field(index = FieldIndex.not_analyzed)
private String userName;
Then you'll need to delete your index and the associated template in /_template, restart your application so a new template and index are created with the proper field mapping.
Then your query will work.

Resources