Validate URL Parameters in an Openapi 3.0 Spring Boot RESTful API? - spring-boot

In my openapi spec I specify URL parameters for a specific resource path like so:
paths:
/some/path:
get:
summary: some summary
parameters:
name: usefulParameter
description:
schema:
type: string
maxLength: 15
Using openapi 3.0 codegen I generate a Spring Boot RESTful API which automatically generates an interface with a name like xxxxDelegate, which has methods that must be implemented like:
default ResponseEntity<Object> somePathGet(String usefulParameter) {
getRequest().ifPresent(request -> {
for (MediaType mediaType: MediaType.parseMediaTypes(request.getHeader("Accept"))) {
if (mediaType.isCompatibleWith(MediaType.valueOf("application/json"))) {
String exampleString = "null";
ApiUtil.setExampleResponse(request, "application/json", exampleString);
break;
}
}
});
return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);
}
Which has a comment at the top of the interface stating:
A delegate to be called by the {#link YourApiController}}.
Implement this interface with a {#link org.springframework.stereotype.Service} annotated class.
So I do exactly that, which results in my own class which looks like this:
#Service
public class MyCustomClass implements xxxxDelegate {
#Override
public ResponseEntity<Object> somePathGet(String usefulParameter) {
}
}
So far this works perfectly. I can invoke it and log the input parameter. The issue that I'm having is that I can't seem to find documentation anywhere on how to assign validation to that input parameter - the Openapi 'maxLength' is not being applied, and Spring/Spring Boot doesn't seem to provide a way to apply validation to these parameters unless you use Model type classes and annotate the members. I would prefer to avoid that and instead simply tell the API that this URL parameter requires specific validation, such as min/max length, or a certain character set.
Is this possible?

You can use
x-field-extra-annotation: "#Email"
in field definitions of the api-defining yaml. Self-defined validators are possible if you put them into the org.openapitools.model package.
In the server-side API-implementation you need to put the #Valid annotation in front of any parameter you want to have validated.
Here is an example for an UUID scheme with custom validation (note that the validation rules defined in the yaml have no effect on the generated Spring-Java-Code):
schemas:
UUID:
description: "Format: UUID"
type: "string"
pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$'
minLength: 36
maxLength: 36
example: "097c9a34-ec4c-4766-b29b-f528db4a3ef8"
x-field-extra-annotation: "#UUID"

Related

Translations with Lighthouse GraphQL

I'm using lighthouse on laravel to create APIs for a portal for which I only deal with the development of the backend.
Basically I have to extract a list from a table in the db and so far everything is ok: in the schema file I define the type and the query itself
type StandardLibrary #guard{
id: ID!
code: String!
title: String!
....
}
type Query{
...
standardLibraries: [StandardLibrary!] #all
...
}
At this point, however, I need to get the translations of the title field from the dedicated json files, and I should have solved it by making a #translate directive that I call next to the field that interests me and implemented as follows
type StandardLibrary #guard{
id: ID!
code: String!
title: String! #translate
....
}
namespace App\GraphQL\Directives;
use Closure;
use GraphQL\Type\Definition\ResolveInfo;
use Nuwave\Lighthouse\Schema\Directives\BaseDirective;
use Nuwave\Lighthouse\Schema\Values\FieldValue;
use Nuwave\Lighthouse\Support\Contracts\FieldMiddleware;
use Nuwave\Lighthouse\Support\Contracts\GraphQLContext;
class TranslateDirective extends BaseDirective implements FieldMiddleware
{
public static function definition(): string
{
return /** #lang GraphQL */ <<<'GRAPHQL'
directive #example on FIELD_DEFINITION
GRAPHQL;
}
public function handleField(FieldValue $fieldValue, Closure $next): FieldValue
{
$resolver = $fieldValue->getResolver();
// If you have any work to do that does not require the resolver arguments, do it here.
// This code is executed only once per field, whereas the resolver can be called often.
$fieldValue->setResolver(function ($root, array $args, GraphQLContext $context, ResolveInfo $resolveInfo) use ($resolver) {
// Do something before the resolver, e.g. validate $args, check authentication
// Call the actual resolver
$result = $resolver($root, $args, $context, $resolveInfo);
// Do something with the result, e.g. transform some fields
return __($result,[],'en');
});
// Keep the chain of adding field middleware going by calling the next handler.
// Calling this before or after ->setResolver() allows you to control the
// order in which middleware is wrapped around the field.
return $next($fieldValue);
}
}
It works and if it doesn't find the corresponding translation it returns the contents of the field in the db.
But my problem is: how do I dynamically give the language in the query? I've tried declaring a custom client directive but I can't figure out how to implement it, could someone help me out? At some point I'll also have to fetch from the json other fields (such as the description) not present in the db, so I'd need to fetch the translations via the record id and not directly looking for the column content.
You can either do it based on client locale (using the Accept-Language header), or ask API client to explicitly specify expected locales, or even mix both a choose the first-one as a fallback of the second one.
For the header part, I would recommend a Laravel middleware that would simply set the app()->setLocale() based on available locales, and header values.
For the argument, schema would look like this :
title(locale: string): String! #translate
(Yes, argument can exist at any level, not only Query/Mutation)
Value is retrieved as following on directive class:
return __($result,[],$args['locale'] ?? app()->getLocale());

Kotlin Spring Request Blank Validation Best Practice

I'm new to kotlin and so far I have 2 options to do not blank validation on incoming HTTP request since a blank string considered as valid value in kotlin null checking.
Validation on class init function
data class Foo(val key: String) {
init {if (this.key.isEmpty()) throw Exception("Invalid Request")}
}
Validation with javax annotation
data class Foo(#field.NotBlank val key: String)
Both ways are working as expected, I just curious on what's the best way to do this in kotlin. I'm afraid that my kotlin code is to java, at that point no use I work with kotlin
If you want to validate the incoming request, go for 2nd option.
Consider having more than one attribute, lets say you have class such as:
data class User(val name: String, val surname:String)
Then you have to write validation for each field. What if both name and surname are blank? The name validation throws an error, you add name to request and BAM, surname validation throws an error.
data class User(val name: String, val surname:String) {
init {
if (this.name.isEmpty()) throw Exception("Name is missing")
if (this.surname.isEmpty()) throw Exception("Surname is missing)
}
}
You can validate all at once, using OR for example, but then you would lose explicit error - what was wrong? Blank name? Blank surname? Both were blank...?
data class User(val name: String, val surname:String) {
init {
if (this.name.isEmpty() || this.surname.isEmpty()) throw Exception("Name or surname is missing")
}
}
Now think about three, four, five fields.
If you use javax.validation properly, you have to write just the annotations and the rest is done by the framework - it will explictly say, what is wrong, on which field, because it checks all the constraints and if there are any violations, it throws an error with all violation details.

Decompose incoming JSON to Objects by fields with Spring MVC

I need to decompose my incoming JSON by fields in me REST Controller with Spring Boot.
My request body:
{
"text": "my text",
"myEnum": "VALUE1"
}
And my controller:
#PatchMapping("/{id}")
Object updateEntity(#PathVariable Long id, String text, MyEnum myEnum) {
/* ... */
}
#RequestParam doesn't work because it's just for query string params, #RequestBody doesn't work too because it handle whole body. But I need decompose incoming body by fields and inject into controller. I know what I can use Map <String, String> for this, but I would like validate my incoming fields, and I have the fields with difference types. And I don't want to create one class by incoming body for each controller.
If I haven't misunderstood your requirement, the usual way to deal with incoming JSON is to define a class that reflects your expected input, and make that the controller method parameter annotated as RequestBody.
Spring Boot, by default, uses Jackson to deserialize to your class, and so if you use matching property names then you won't need any special annotations or setup. I think enums will be handled by default, as are other types (though you may need to provide some guidance for strings representing dates or timestamps). Any bad value will fail deserialisation, which I think you can handle in ControllerAdvice (though you'll want to double check that)

Deserialize nested Kotlin object in springMVC

I'm trying to deserialize a complex GET request into a structure of nested objects.
The GET requests looks like:
curl 'localhost:8080/?id=1&inner.id=1'
and the code should look like this:
class RootObj(val id: Int, inner: InnerObject)
class InnerObject(val id: Int)
#RestController
class SearchController {
#GetMapping(path = ["/"])
fun getRoot(rootObj: RootObj): String {
return "ok"
}
}
This doesn't work out of the box I guess because spring doesn't know how properly create this nested structure.
Parameter specified as non-null is null: [...] parameter inner","path":"/"}%
Is there a way to overcome this problem? Maybe providing a totally custom deserializer code?
As alternative solution, I guess I could flatten the object hierarchy but for doing so I must be able to map a query parameter like inner.id to a field named innerId
Spring can actually map the query params to the custom object directly, but you need to provide defaults to the params of the custom object constructor.
So you need to define your classes as below for it to work
class RootObj(val id: Int = 0, val inner: InnerObject = InnerObject(0))
class InnerObject(var id: Int = 0)
Do note that the id field of InnerObject would have to be declared as var for Spring to be able to map it. Then curl 'localhost:8080/?id=1&inner.id=1' would work fine.

Unable to set header as optional in OpenApi using Spring with annotations

I'm using Java 11.0.2, Spring Boot 2.2.6 and Spring OpenApi Core 1.1.49 to create an OpenApi documentation using annotations.
During the request for creating a merchant in the Controller I need to have a custom header property, but which needs to be optional. Based on Swagger documentation for Parameter Object, the field "required" (Determines whether this parameter is mandatory. If the parameter location is "path", this property is REQUIRED and its value MUST be true. Otherwise, the property MAY be included and its default value is false.) by default for header is false, but below you can see that for some reason is true (nevertheless that I configured this option to "false").
Java - part ot Controller method
public ResponseDto create(#Parameter(in = ParameterIn.HEADER, required = false, schema = #Schema(type = "string", format = "uuid"), name = "X-Request-Correlation-Id", #RequestHeader("X-Request-Correlation-Id") #Nullable String headerXRequestId, ... <
This result in OpenApi yaml file - autogenerated with the information from the annotations
parameters:
- name: X-Request-Correlation-Id
in: header
required: true
schema:
type: string
format: uuid
Can you point out the problem, because I can't find a solution in the documentation or anywhere else?!
Found the solution - the problem wasn't in OpenApi annotation #Parameter, but in Spring binding annotation #RequestHeader, which binds the header field to the method parameter. #RequestHeader has also field "required" and by default, it's set on "true" and it overrides the one in #Parameter. So the solution was the following syntax - #RequestHeader(name = "X-Request-Correlation-Id", required = false).

Resources