How can I get OpenApi-Generator to produce nullable DateTimeOffset? - swagger

My API exposes some endpoints with DateTime? and others with DateTimeOffset?.
I want to generate an API Client using OpenApi-Generator, that will create client code for each endpoint, respecting the differences between the types.
OpenApi-Generator offers the option to useDateTimeOffset=true, which will generate the client using DateTimeOffset everywhere regardless of whether the API was exposing DateTime or DateTimeOffset. As such, I don't want to use that.
So to resolve the requirement without that, I'm attempting to create a Filter in which I will specify that in the case of DateTimeOffset? the generator should use the type DateTimeOffset and make it nullable.
This is the Filter:
public class FixDateTimeOffsetTypeSchemaFilter : ISchemaFilter
{
public void Apply(OpenApiSchema model, SchemaFilterContext context)
{
if (context.Type == typeof(DateTimeOffset?))
{
model.Format = "date-time-offset-nullable";
model.Type = "DateTimeOffset";
model.Nullable = true;
}
}
}
and this is an example of the output from that:
"/Api/MyApiEndpoint": {
"get": {
"tags": [
"General"
],
"operationId": "General_MyApiEndpoint",
"parameters": [
{
"name": "startDatetime",
"in": "query",
"schema": {
"type": "DateTimeOffset",
"format": "date-time-offset-nullable",
"nullable": true
}
},
When I run this and generate the client from that JSON, the the DateTimeOffset objects are never nullable. It doesn't seem to respect that instruction.
What do I need to change to make it work, so that when I specify DateTimeOffset should be nullable, it appears as nullable in the code?

It looks like this is a known problem:
https://github.com/OpenAPITools/openapi-generator/pull/3530
And here is the pull request that solves it: https://github.com/OpenAPITools/openapi-generator/pull/3530/commits/89fd5d70e9f15a5be9ffe26c2beddc07770043c0
Also check this link: https://jane.readthedocs.io/en/latest/tips/nullable.html

Related

Displaying enum values together with a referenced class in Swagger using Swashbuckle

I am using Swashbuckle to generate Swagger output. On my model I the following property:
public FieldValue? MyProperty { get; set; }
FieldValue is a simple class that can hold an ID and a name:
public class FieldValue
{
/// <summary>
/// The numerical represenation of the value.
/// </summary>
/// <example>1</example>
public string Id { get; set; }
/// <summary>
/// The textual represenation of the value.
/// </summary>
/// <example>SomeValue</example>
public string Name { get; set; }
}
Via some business logic in our code, a set of possible key-value pairs (ID and name) are mapped to this property.
Now, I would like to show all the possible values for MyProperty in the enum tag in my Swagger output. Using an implementation of ISchemaFilter, a custom attribute on the MyProperty property and using allOf instead of a simple ref, I have managed to add the values in my JSON output, see below. The reason for using allOf instead of just ref is that ref will overwrite all properties on the referencing schema. So, when I use allOf it keeps my description and enum fields in the document.
{
"components": {
"schemas": {
"FieldValue": {
"type": "object",
"properties": {
"Id": {
"type": "string",
"description": "The numerical represenation of the value.",
"nullable": true,
},
"Name": {
"type": "string",
"description": "The textual represenation of the value.",
"nullable": true,
}
}
},
"MyProperty": {
"enum": [
"1: EnumValue1",
"2: EnumValue2",
"3: EnumValue3"
],
"allOf": [{
"$ref": "#/components/schemas/FieldValue"
}
],
"description": "Some description",
"nullable": true
}
}
}
This does not show the enum-values in the Swagger UI, though. It does show the description.
Is this Swagger doc valid? If not, how can I make it valid and display the enums for MyProperty?
The correct representation of your use case is:
"MyProperty": {
"enum": [
{"Id": "1", "Name": "EnumValue1"},
{"Id": "2", "Name": "EnumValue2"},
{"Id": "3", "Name": "EnumValue3"}
],
"allOf": [
{ "$ref": "#/components/schemas/FieldValue" }
],
"description": "Some description",
"nullable": true
}
However, there's little to no tooling support for enums containing object literals. I suggest mentioning the possible Id and Name values in the description of the MyProperty property.

Why ModelState returns different result and how to fix it?

Asp.net core 3.1 WebApi.
I have a model with required properties.
1.If model is not valid, then the response contains data
like :
{
"errors": {
"Name": [
"Update model can't have all properties as null."
],
"Color": [
"Update model can't have all properties as null."
]
},
"type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
"title": "One or more validation errors occurred.",
"status": 400,
"traceId": "|f032f1c9-4c36d1e62aa60ead."
}
And this looks good for me.
But if I add some custom validation to modelState.AddModelError("statusId", "Invalid order status id.") then it returns different structure:
[
{
"childNodes": null,
"children": null,
"key": "statusId",
"subKey": {
"buffer": "statusId",
"offset": 0,
"length": 8,
"value": "statusId",
"hasValue": true
},
"isContainerNode": false,
"rawValue": "11202",
"attemptedValue": "11202",
"errors": [
{
"exception": null,
"errorMessage": "Invalid order status id."
}
],
"validationState": 1
}
]
Also looks like ModelState.IsValid is actual no more for controller, because the bad request is returned before it even enters the controller with invalid mode. Or is there some flag with global validation via ModelSate?
Why the structure is different?
How to make it the same?
How to force to get to ModelState.IsValid method inside of api controller as it worked in MVC?
Update:
[Route("....")]
[Authorize]
[ApiController]
public class StatusesController : ApiControllerBase
{
[HttpPut, Route("{statusId}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status409Conflict)]
[Produces("application/json")]
public async Task<ObjectResult> UpdateStatusAsync(int statusId, [FromBody] StatusUpdateDto orderStatusUpdateDto)
{
int companyId = User.Identity.GetClaimValue<int>(ClaimTypes.CompanyId);
const string errorNotFound = "There is not order status with this id for such company";
if (statusId <= 0)
{
Logger.LogError(errorNotFound);
ModelState.AddErrorModel(nameof(statusId), "Invalid order status id")
throw new NotFound(ModelState);
}
if (orderStatusUpdateDto == null)
{
const string error = "Invalid (null) order status can't be added";
Logger.LogError(error);
throw new ArgumentNullException(error);
}
if (ModelState.IsValid == false) // also this code is always true or returns 400 before this line
{
return BadRequest(ModelState);
}
....
return result;
}
}
The ApiController attribute adds some specific opinionated behaviors to a controller. One of them is to return a 400 error if the model is not valid.
This behavior can be disabled, but only on a global level.
services.AddControllers()
.ConfigureApiBehaviorOptions(options =>
{
options.SuppressModelStateInvalidFilter = true;
});
I think you have the following options:
Disable this behavior and check ModelState.IsValid yourself. Use ValidationProblem method to produce the same response
Add this check to the validator of the model
Keep everything as it is. But use ValidationProblem inside the controller method to return the validation errors.
See https://learn.microsoft.com/en-us/aspnet/core/web-api/?view=aspnetcore-3.1#automatic-http-400-responses for information

Adding Example RequestBody in swagger micronaut

I generated swagger for micronaut using the instructions provided in https://micronaut-projects.github.io/micronaut-openapi/latest/guide/index.html
So I have a controller method like:
#Consumes("application/vnd.api+json")
#Produces("application/vnd.api+json")
#Post("/{id}/users")
#RequestBody
public HttpResponse addAndAssignTarget(#PathVariable("id") Long projectId, #Body #Parameter() JsonNode user) {
I am not using a POJO for adding users for another reason which is out of context for this question. Thus, the generated swagger ui shows {} as example for request body. I would like to change this to something like. How can I do this?
{
"data" : {
"type": "projects",
"attributes": {
"name": "some-name1",
"description": "some-description",
"partner_company": "some-compnay"
}
}
}
It looks much better if you use POJO. However, you can add an example as string, but the problem with that if you have any nested objects the quotation marks won't look nice (will be escaped "):
#Consumes("application/vnd.api+json")
#Produces("application/vnd.api+json")
#Post("/{id}/users")
#RequestBody
public HttpResponse addAndAssignTarget(#PathVariable("id") Long projectId,
#Body #RequestBody(content = #Content(schema = #Schema(example = "[0, 2, 3]"))) JsonNode user){
}
Also it is not a Paramater, but a #Body and #RequestBody for annotation purposes.
Output will be:
"[0, 2, 3]"

Using Java objects as request parameters breaks down to primitives in swagger ui springfox

I have the following controller code
#GetMapping("/users")
public ResponseEntity<UserDto> getUsers(Filter filter) {
return ResponseEntity.ok(userService.findUsers(filter));
}
Filter.java:
public class Filter {
private Integer page;
private Integer size;
// Contains 2 fields: "propertyName" and "order"
private Sort sort;
... getters and setters
}
The URL is following: /users?page=1&size=10&sort=+firstName. So I have a custom converter from String to Sort and it works perfectly.
However, the generated swagger documentation is incorrect:
"parameters":[
{
"name":"sort.propertyName",
"in":"query",
"required":false,
"type":"string"
},
{
"name":"sort.order",
"in":"query",
"required":false,
"type":"string",
"enum":[
"+",
"-"
]
},
{
"name":"page",
"in":"query",
"required":false,
"type":"integer",
"format":"int32"
},
{
"name":"size",
"in":"query",
"required":false,
"type":"integer",
"format":"int32"
}
]
As you can see, it has broken down the Sort field of Filter and has generated 2 parameters: sort.propertyName and sort.order. Instead, I want to have one parameter sort with type string.
Is there any way to achieve that? I have tried annotating the sort field with #ApiParam(name = "sort", value = "Sort", type = "string"), but it doesn't work.
You could create an alternate type rule that treats Sort as a String
import static springfox.documentation.schema.AlternateTypeRules.*;
...
// Configure your docket bean
new Docket(...)
...
.alternateTypeRules(newRule(Sort.class, String.class)
...

Custom Web API Formatting

I've been playing around with asp.net web api, and I noticed that default generated returned json doesn't include the object level key. I was also wondering how to customize the output of the json string. For example:
Getting a User usually returns
{
"Name": "ChaoticLoki",
"Age": 22,
"Sex": "Male"
}
I was hoping I could return something like:
{
"data": {
"Name": "ChaoticLoki",
"Age": 22,
"Sex": "Male",
},
"success": true
}
You can then create a class wrapping the data and status like this
public class JsonResult{
public object data { get; set;}
public boolean success { get; set;}
}
And then in your Web Api methods, you can do
var data = ... //getting from repo
return Json(new JsonResult{data = data, success = true});

Resources