RAML support for a resource PATCH (RFC 7396) - rest

Problem
I'm trying to describe an API which is supposed to have resources PATCH methods work like described in RFC 7396.
Basically a POST method have a set of properties in it's body, some required some not.
PATCH (by some standards) has no fields required - requests body contains only the fields you want to update, additionally the API allows a client to "clean" a field by sending a null.
Example:
#%RAML 1.0
title: test
mediaType: [application/json]
types:
Create:
type: object
properties:
description: string
Retrieve:
type: Create
properties:
id: string
Update:
type: object
properties:
description?: nil | string
/resource:
get:
responses:
200:
body:
type: Retrieve[]
post:
body:
type: Create
/{id}:
uriParameters:
id: string
get:
responses:
200:
body:
type: Retrieve
patch:
body:
type: Update
responses:
200:
body:
type: Retrieve
in the example I have a Update type specified just for that task, because... Create has a required description, Retrieve inherits from Create as it should return all it's fields and the additional id, Update can't inherit from Create as RAML does not allow to override a required field with one that is not required.
Questions:
Is there a better way to describe that API?
If not is nil | string the proper way to go when writing that Update type "from scratch"?

I don't think there's a better way. Only thing is, if you have not required properties in both the create and the update, then you could create a base type.
Yes, nil | [type] is the way to go. You need to be able to "clean" the property by sending null.

Related

Openapi discriminator without propertyname

I need inheritance only with one field in super class, so propertyName is no need. Is anyway to ignore propertyName?
Base:
type: object
discriminator:
propertyName: useless
required:
- id
properties:
id:
type: integer
format: int64
example: 1
Response like
"useless":"subtype", "id":1
And throws error like
Unrecognized field "useless"
I need inheritance only with id, without field propertyName, is anyway to do it?
Or make propertyName as integer id?

OpenAPI discriminator oneOf default

Given the following OpenAPI YAML, do we always need to define petType while creating Pet?
Can I by default create a pet of type Dog without specifying petType? Is there some way to do this with OpenAPI?
Pet:
oneOf:
- $ref: '#/components/schemas/Cat'
- $ref: '#/components/schemas/Dog'
- $ref: '#/components/schemas/Lizard'
discriminator:
propertyName: petType
do we always need to define petType while creating Pet
Yes. As explained in the Discriminator Object section of the OpenAPI Specification (emphasis mine):
a discriminator MAY act as a "hint" to ... selection of the matching schema ... We can then describe exactly which field tells us which schema to use:
...
The expectation now is that a property with name petType MUST be present in ... payload, and the value will correspond to the name of a schema defined in the OAS document.
I had a similar problem myself, and I was able to work out the following solution:
Pet:
x-field-extra-annotation: '#com.fasterxml.jackson.annotation.JsonTypeInfo(
use = com.fasterxml.jackson.annotation.JsonTypeInfo.Id.NAME,
include = com.fasterxml.jackson.annotation.JsonTypeInfo.As.PROPERTY,
property = "petType", visible = true,
defaultImpl = Dog.class)'
oneOf:
- $ref: '#/components/schemas/Cat'
- $ref: '#/components/schemas/Dog'
- $ref: '#/components/schemas/Lizard'
discriminator:
propertyName: petType
I used 6.0.1 version of the OpenAPI generator.
Just in case, here is the link to the documentation where I found many other helpful solutions:
https://openapi-generator.tech/docs/generators/spring

In OpenAPI, how to make some attributes required only in the PATCH?

Imagine you have the object User (see below) which is used to specify the body param in both the calls Post /users and Patch /users/:id.
I want to use it for both API calls, but I need to specify that firstname and lastname are not required in the Patch call.
I know I could do that by duplicating the file user.yaml into another file (maybe called patch_user.yml) and removing the required section:
required:
- firstname
- lastname
but I was wondering if there is a better way. I have the same issue in many endpoints, and I would end up with lots of duplication.
How would you tackle this problem?
user.yaml:
User
type: object
required:
- type
- attributes
properties:
id:
type: string
type:
type: string
attributes:
type: object
required:
- firstname
- lastname
properties:
firstname:
type: string
example: John
lastname:
type: string
example: Doe

Abstract class or interface in RAML modeling

Is it possible to model abstract class or interface using RAML? If not, how can we impose constraints in supertypes that the subtypes must define?
You can model super types and inheritance in types like so:
types:
ResponseNoId:
properties:
something1:
something2?:
ResponseId:
type: ResponseNoId
properties:
id:
Response:
ResponseNoId|ResponseId
/test:
get:
responses:
200:
body:
application/json:
type: ResponseId
In this example ResponseId inherits something1 and something2 from ResponseNoId but adds a new property called id.
Also Response allows you to use either in your resource.
In you resources you can now define type: Response and it only allow one of the child types.

Defining an API with swagger: GET call that uses JSON in parameters

I am trying to create a proper, REST API, and document it with Swagger (2.0).
So, I have an API call that is a query, ie, it makes no changes and doesn't create anything (idempotent and safe). But it requires passing in a complex JSON parameter (list of items, 2 or 3 sets of addresses, etc). So I'm doing a GET with a parameter thats URL encoded JSON. That seems like the proper way to do it.
I see so often API's like this where they do it as a POST for this reason, but that's an incorrect use of the POST verb.
I'm seeing lots of swagger API's that do this...
I can't figure out if there's a way to do a proper rest API with Swagger, using a JSON parameter. You can define the parameter as a string, of course, and pass your encoded JSON into it, but then the swagger tooling doesn't understand that there's a schema/definition for it.
Is swagger not able to properly document this kind of call?
OpenAPI 2.0 (Swagger 2.0)
OpenAPI 2.0 does not support objects in query strings, it only supports primitive values and arrays of primitives. The most you can do is define your parameter as type: string, add an example of a JSON value, and use description to document the JSON object structure.
swagger: '2.0'
...
paths:
/something:
get:
parameters:
- in: query
name: params
required: true
description: A JSON object with the `id` and `name` properties
type: string
example: '{"id":4,"name":"foo"}'
OpenAPI 3.x
JSON in query string can be described using OpenAPI 3.x. In OAS 3, query parameters can be primitives, arrays as well as objects, and you can specify how these parameters should be serialized – flattened into key=value pairs, encoded as a JSON string, and so on.
For query parameters that contain a JSON string, use the content keyword to define a schema for the JSON data:
openapi: 3.0.1
...
paths:
/something:
get:
parameters:
- in: query
name: params
required: true
# Parameter is an object that should be serialized as JSON
content:
application/json:
schema:
type: object
properties:
id:
type: integer
name:
type: string
This corresponds to the following GET request (before URL encoding):
GET /something?params={"id":4,"name":"foo"}
or after URL encoding:
GET /something?params=%7B%22id%3A4%2C%22name%22%3A%22foo%22%7D
Note for Swagger UI users:
Parameters with content are supported in Swagger UI 3.23.8+ and Swagger Editor 3.6.34+.
Workaround for earlier versions of UI/Editor:
Define the parameter as just type: string and add an example of the JSON data. You lose the ability to describe the JSON schema for the query string, but "try it out" will work.
parameters:
- in: query
name: params
required: true
schema:
type: string # <-------
example: '{"id":4,"name":"foo"}' # <-------
For .Net and Swashbuckle (tested on 3.0)
I have a generic class JsonModelBinder that implements IModelBinder interface. The class is used like this:
public IActionResult SomeAction(
[FromRoute] int id,
[FromQuery][ModelBinder(BinderType = typeof(JsonModelBinder<SomeModel>))] SomeModelquery query) => {}
I have created Operation filter that does the following:
Removes parameters created by Swashbuckle from properties of my model
Add query parameter of type string
As a result in Swagger I have a text field where I can insert json and test requests
public class JsonModelBinderOperationFilter : IOperationFilter
{
public void Apply(Operation operation, OperationFilterContext context)
{
if (operation.Parameters == null || context.ApiDescription.HttpMethod != HttpMethod.Get.ToString())
return;
//Find json parameters
var jsonGetParameters = context.ApiDescription.ActionDescriptor.Parameters.Cast<ControllerParameterDescriptor>()
.Where(p => p.ParameterInfo.CustomAttributes.Any(c => c.AttributeType == typeof(ModelBinderAttribute) && c.NamedArguments.Any(IsJsonModelBinderType))).ToArray();
if (jsonGetParameters.Length > 0)
{
//Select parameters names created by Swagger from json parameters
var removeParamNames = new HashSet<string>(context.ApiDescription.ParameterDescriptions.Where(d => jsonGetParameters.Any(p => p.Name == d.ParameterDescriptor.Name)).Select(p => p.Name));
//Create new Swagger parameters from json parameters
var newParams = jsonGetParameters.Select(p => new NonBodyParameter()
{
In = "query",
Name = p.Name,
Type = "string",
Description = "Json representation of " + p.ParameterType.Name
});
//Remove wrong parameters and add new parameters
operation.Parameters = operation.Parameters.Where(p => p.In != "query" || !removeParamNames.Contains(p.Name)).Concat(newParams).ToList();
}
}
private static bool IsJsonModelBinderType(CustomAttributeNamedArgument arg)
{
var t = arg.TypedValue.Value as Type;
return t != null && t.GetGenericTypeDefinition().IsAssignableFrom(typeof(JsonModelBinder<>));
}
}
Notes:
I use IsAssignableFrom because I have classes derived from JsonModelBinder. You can omit it if you don't inherit
You can also omit GetGenericTypeDefinition if your binder is not generic
This solution doesn't check for parameter name collision, though you should never have it if the API made with common sense