I'd like to ask if there is any support for POST parameters in RAML. And if there is - what is the syntax. I've browsed spec 0.8 and spec 1.0 roughly (actually I'm bound to 0.8, since many tools don't support 1.0 yet). I didn't find POST parameters support, but maybe I just missed something.
So what do I mean by POST parameters? These can be either of the two (sorry, I don't know their formal names, if there are any):
HTTP plain parameters, key=value, each parameter in one line, such as
name=John Doe
amount=5
which is not really handy (e.g. no nesting)
parameters as JSON object, just a JSON with all its syntax allowed (server-side needs to parse this json); such as:
{"name":"John Doe","amount":"5"}
Different server-side API implementations use either 1st or 2nd one. Anyway, how does RAML support these?
As it is shown in this reference https://github.com/raml-org/raml-spec/wiki/Breaking-Changes:
For raml 0.8:
body:
application/x-www-form-urlencoded:
formParameters:
name:
description: name on account
type: string
example: Naruto Uzumaki
gender:
enum: ["male", "female"]
Is the equivalent in raml 1.0 to:
body:
application/x-www-form-urlencoded:
properties:
name:
description: name on account
type: string
example: Naruto Uzumaki
gender:
enum: ["male", "female"]
So what it changes is the formParameters attribute to the properties one.
#Pedro has covered option 2, so here is option 1. Based on the discussion in the comments, it seems the encoding used is application/x-www-form-urlencoded.
You need to use formParameters.
Example:
post:
description: The POST operation adds an object to a specified bucket using HTML forms.
body:
application/x-www-form-urlencoded:
formParameters:
AWSAccessKeyId:
description: The AWS Access Key ID of the owner of the bucket who grants an Anonymous user access for a request that satisfies the set of constraints in the Policy.
type: string
acl:
description: Specifies an Amazon S3 access control list. If an invalid access control list is specified, an error is generated.
type: string
Reference: https://github.com/raml-org/raml-spec/blob/master/raml-0.8.md#web-forms
Post parameters can be expressed using JSON Schema
A simple RAML 0.8 example:
#%RAML 0.8
title: Api
baseUri: /
schemas:
- Invoice: |
{
"$schema": "http://json-schema.org/draft-03/schema",
"type": "object",
"properties": {
"Id": { "type": "integer"},
"Name": { "type": "string"},
"Total": { "type": "number"}
}
}
/invoices:
post:
body:
application/json:
schema: Invoice
Related
I am working on providing a GET REST API where I would like to conditionally include the total_documents field (its an integer count of the total number of records present in the DB table).
The API signature and response payload will be something like:
GET /endpoint/?total_documents&.....
Response Payload:
{
documents: [....],
total_documents: 100
}
Now I would like the total_documents field to be appeared in the response payload if and only if the total_documents query parameter exists in the URL.
This is what I tried, based on my schema:
fastify.addSchema({
$id: 'persistence-query-params',
title: "PersistenceQueryParams",
type: 'object',
description: 'Persistence Service GET API URL query specification. Applicable for GET API only.',
properties: {
'total_documents': {
description: 'Total number of documents present in the collection, after applying filters, if any. This query paramater does not take any value, just pass it as the name (e.g. &total_documents).',
nullable: true,
},
},
}
querystring: {
description: 'Persistence Service GET API URL query specification. Applicable for GET API only.',
$ref: 'persistence-query-params#',
},
response: {
200: {
properties: {
'documents': {
description: 'All the retrieved document(s) from the specified collection for the specified service database and account.',
type: 'array',
items: {
$ref: 'persistence-response-doc#',
}
},
'total_documents': {
description: "If total_documents query paremeter is specified, gives the total number of documents present in the collection, after applying query paramaters, if any. If total_documents is not specified, this field will not be available in the response payload.",
type: 'number',
default: -1,
},
},
dependencies: {
'total_documents': { required: ['querystring/properties/total_documents'] },
},
},
'4xx': {
$ref: 'error-response#',
description: 'Error response.'
}
}
What is the way out here?
Thanks,
Pradip
JSON Schema has no notion of a request or response or HTTP.
What you have here is an OpenAPI specification document.
The OpenAPI specification defines a way to access dynamic values, but only within Link Objects or Callback Objects, which includes the query params.
Runtime expressions allow defining values based on information that
will only be available within the HTTP message in an actual API call.
This mechanism is used by Link Objects and Callback Objects.
https://spec.openapis.org/oas/v3.1.0#runtime-expressions
JSON Schem has no way to reference instance data, let alone data relating to contexts it is unaware of.
I am trying to create an issue via Jira API -
{
// other fields is here
description: {
type: "doc",
version: 1,
content: [
{
type: "text",
text: summary
}
}
}
but I get an error - "Operation value must be a string".
so how can I create an issue correctly?
Most likely you're using API version 2 - which accepts text for this field.
However, you're providing value as json (Atlassian Document Format) which is for API version3
I am learning OpenAPI recently, and would like to know the best practices.
Let's say I have a resource called Person, and it is defined in components/schemas as follows:
Person:
type: object
required:
- id
- name
- age
properties:
id:
readOnly: true
type: integer
name:
type: string
age:
type: integer
I've made id readOnly because when I do post or patch, the ID will be passed as part of the URL. See https://swagger.io/docs/specification/data-models/data-types/
name and age must present when the client tries to create a new person using post method, or get a person, therefore they are defined as required.
My question is about patch: what if I only want to update a person's age or name independently? Ideally I would like to do something like
PATCH /person/1
{"age": 40}
However, since I've defined name as required, I can't do it. I can think of several solutions, but they all have flaws:
Remove the required restriction. But if I do that, I lose the validation on post and get.
Use a separate schema for patch, e.g. PersonUpdate, with required removed. Apparently that leads to redundancy.
When I do patch, I do pass all the fields, but for the ones I don't want to update, I pass an invalid value, e.g.
PATCH /Person/1
{"age": 40, "name": null}
And make all the fields nullable, and let the server ignore these values. But what if I do want to set name to null in DB?
I use PUT for update, and always pass all the required fields. But what if my data is outdated? E.g. when I do
PUT /Person/1
{"age": 40, "name": "Old Name"}
Some other client has already changed name to "New Name", and I am overriding it.
Like method 3, but I pass additional fields when doing patch to indicate the fields the server should care, whether using query parameters like ?fields=age, or add it to the JSON body. So I can change the requestBody to something like
requestBody:
content:
application/json:
schema:
allOf:
- $ref: '#/components/schemas/Person'
- type: object
properties:
_fields:
type: array
items:
type: string
Then I can do this
PATCH /Person/1
{"age": 40, "name": null, _fields: ["age"]}
In this way, I can update name to null as well
PATCH /Person/1
{"age": 40, "name": null, _fields: ["age", "name"]}
This method seems can work, but is there a better or widely accepted practice?
I came up with the following solution:
Person:
type: object
allOf:
- $ref: '#/components/schemas/PersonProperties'
- required:
- id
- name
- age
UpdatePerson:
type: object
allOf:
- $ref: '#/components/schemas/PersonProperties'
PersonProperties:
type: object
properties:
id:
readOnly: true
type: integer
name:
type: string
age:
type: integer
PersonProperties acts as a simple collection of properties which make up a the person model. It doesn't specify any required fields.
Person re-uses all properties of PersonProperties and, in addition, specifies which one are required. PersonUpdate re-uses all properties of PersonProperties and allows for partial updates.
This solution still feels hacky. Also, it doesn't work for partial updates of nested objects and nullable properties.
(In fact, since the id is readOnly, you could also add required: [id] on PersonProperties and remove id from the required list on Person)
By splitting the schemas up into multiple, composable schemas, we can layer the schemas on top of one another without repeating ourselves. We can even support removal of properties via PATCH by making optional properties nullable.
Example OpenAPI definition allowing partial updates via PATCH endpoints (validated):
openapi: 3.0.0
info:
title: Sample API with reusable schemas and partial updates (PATCH)
version: 1.0.0
paths:
/customers:
post:
tags:
- Customer
requestBody:
$ref: '#/components/requestBodies/CreateCustomer'
responses:
201:
description: Created
content:
application/json:
schema:
$ref: '#/components/schemas/CustomerId'
get:
tags:
- Customer
responses:
200:
description: OK
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/Customer'
/customers/{CustomerId}:
get:
tags:
- Customer
parameters:
- $ref: '#/components/parameters/CustomerId'
responses:
200:
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/Customer'
put:
tags:
- Customer
requestBody:
$ref: '#/components/requestBodies/CreateCustomer'
parameters:
- $ref: '#/components/parameters/CustomerId'
responses:
204:
description: Updated
patch:
tags:
- Customer
requestBody:
description: Update customer with properties to be changed
content:
application/json:
schema:
allOf:
- $ref: '#/components/schemas/CustomerProperties'
- type: object
properties:
Segment:
nullable: true
parameters:
- $ref: '#/components/parameters/CustomerId'
responses:
204:
description: Updated
components:
schemas:
CustomerProperties:
type: object
properties:
FirstName:
type: string
LastName:
type: string
DOB:
type: string
format: date-time
Segment:
type: string
enum:
- Young
- MiddleAged
- Old
- Moribund
CustomerRequiredProperties:
type: object
required:
- FirstName
- LastName
- DOB
Id:
type: integer
CustomerId:
type: object
properties:
Id:
$ref: '#/components/schemas/Id'
Customer:
allOf:
- $ref: '#/components/schemas/CustomerId'
- $ref: '#/components/schemas/CustomerProperties'
- $ref: '#/components/schemas/CustomerRequiredProperties'
parameters:
CustomerId:
name: CustomerId
in: path
required: true
schema:
$ref: '#/components/schemas/Id'
requestBodies:
CreateCustomer:
description: Create a new customer
content:
application/json:
schema:
allOf:
- $ref: '#/components/schemas/CustomerProperties'
- $ref: '#/components/schemas/CustomerRequiredProperties'
Adapted from: https://stoplight.io/blog/reuse-openapi-descriptions/
It's weird to talk about the OpenAPI / Json schema structure because it tells you something about what you are sending, but not about the meaning of the properties.
To do what you want, you need to come up with a format with a set of rules that can make these kind of modifications.
One (more complex) example is JSON Patch.
Once you figured out what your rules around patching JSON is going to be, that's when a good time might be to define specific cases for this (like the case for Person).
I have a question related to RAML sending different response. In the below RAML, I want to send the response based on the query parameter. If my query parameter (flightId) is "F001", I should get a response only for the data F001. But I am getting the response with all the data specified in the response. May I know how to filter the unwanted data.
#%RAML 1.0
baseUri: https://mocksvc.mulesoft.com/mocks/9b7a0390-ecf4-4ff4-b307-0b7d87ed9495 # baseUri: https://mocksvc.mulesoft.com/mocks/b133e2e4-f0e3-49a0-b224-8f36358e04ca #
title: FlightApi-Rajesh
version: 1.0.1
/{flightId}:
description: Flight Id
get:
queryParameters:
flightId:
displayName: flightId
type: string
required: true
description: Flight name with its ID
example: F0001
description: Get the flight with `flightId = {flightId}`
responses:
200:
body:
application/json:
example: |
{
"F001":{
"flightName": "Ingido",
"Location": "Mumbai",
"flightId": "F001",
"Destination":"Delhi",
"timing":"19:55 HRS"
},
"F002":{
"flightName": "SpiceJet",
"Location": "Pune",
"flightId": "PNQ012",
"Destination":"Chennai",
"timing":"15:00 HRS"
}
}
404:
body:
application/json:
example: |
{"message": "Flight not found"}
RAML is a design specification for REST APIs. When you run the mock service based on RAML, it does not have the capability of generating dynamic responses, which is what you are trying to do. Please refer Mule forum which confirms this - https://forums.mulesoft.com/questions/60487/can-the-example-in-the-raml-be-dynamic.html
If your requirement is to create a mock service with dynamic responses, there are other ways to achieve implementing such mock services - SOAPUI, Dynatrace, etc.
http://blog.maheeka.me/2016/11/parasoft-virtualizer-tips-for.html
https://www.soapui.org/rest-testing-mocking/service-mocking-overview.html
If your requirement is to use RAML and try dynamic responses, you will have to create a Mule ESB project based off the RAML. You can then implement dynamic responses based on input received with Mule configurations.
http://workshop.tools.mulesoft.com/modules/module3_lab1#step-1-create-a-new-mule-project-and-import-raml-definition
You'll likely want to change your example to this. A mock endpoint will return your example response every time, so if you're only expecting one match, the example should reflect that. Keep this in mind, because you will not be able to modify the response of a mocked API based on user input:
/{flightId}:
description: Flight Id
get:
queryParameters:
flightId:
displayName: flightId
type: string
required: true
description: Flight name with its ID
example: F0001
responses:
200:
body:
application/json:
example: |
{
"F001":{
"flightName": "Ingido",
"Location": "Mumbai",
"flightId": "F001",
"Destination":"Delhi",
"timing":"19:55 HRS"
}
}
404:
body:
application/json:
example: |
{"message": "Flight not found"}
If you have received this data from the database connector, even with a single record returned, you'll get a list of results. You'll need to transform it like this:
%dw 1.0
%output application/json
%vars flight = (payload filter $.flightId == flowVars.flightId)[0]
---
{
(flowVars.flightId): flight
}
I'm writing some REST documentation with RAML but I'm stuck.
My problem:
- I have a GET request used for search that can take a parameter "id" or (exclusive or) "reference". Having only one of them is required.
I know how to say "this param is required" but I don't know how to say "having one of these params is required". Is it even possible?
The following example written in RAML 1.0 defines two object types in Url and File then creates another object Item which requires Url OR File in ext. If you change the included examples (which currently validate), you'll see that they fail if the property does not conform to one or the other definition. Hope that helps! LMK if you have any other questions and I'll do my best.
[EDIT: hmm I think I am seeing your problem now, the final example I've just added, named should_fail, (which has one of each type together in the example) still validates and you want a way to make it fail validation.]
[UPDATE: OK I figured a mildly hacky way to do this. Use maxProperties: 1 in the object which should have properties appear alone, see updated code below which fails the final example during validation.]
#%RAML 1.0
types:
Url:
properties:
url:
type: string
example: http://www.cats.com/kittens.jpg
description: |
The url to ingest.
File:
properties:
filename:
type: string
example: kittens.jpg
description: |
Name of the file that will be uploaded.
Item:
description: |
An example of a allowing multiple types yet requiring
one AND ONLY one of two possible types using RAML 1.0
properties:
ext:
maxProperties: 1
type: File | Url
examples:
file_example:
content:
ext:
filename: video.mp4
url_example:
content:
ext:
url: http://heres.a.url.com/asset.jpg
should_fail:
content:
ext:
url: http://heres.a.url.com/asset.jpg
filename: video.mp4
I had the same problem. User can provide either a textual input OR a file input, but not both.
Both have different fields and I detect the request type from the field names. i.e if the request has [files and parameters], it is a FileInput. If the request has [texts and parameters], it is a TextInput. It is not allowed to provide both text and file within the same request.
I used the union property. See CatAndDog example in
Raml 200 documentation for a small example.
You can define your types as follows.
types:
FileInput:
properties:
parameters:
type: Parameters
description: (...)
files:
type: ArchiveCollection | FileCollection
description: (...)
TextInput:
properties:
parameters:
type: Parameters
description: (...)
texts:
type: TextCollection
description: (...)
Then in my POST request body:
/your_route:
post:
body:
multipart/form-data:
type: TextInput | FileInput
The fields in the body are defined with either TextInput or FileInput type.
In RAML 0.8 you can not describe queryParameters with only one parameter.
In RAML 1.0 you can do this. You should use oneOf in jsonschema for describing Type. Your queryParameters should use this type. Example:
api.raml
#%RAML 1.0
title: AUTH microservice
mediaType: application/json
protocols: [HTTPS]
types:
- example: !include schemas/example.json
/example:
get:
queryParameters:
type: example
schemas/example.json
{
"$schema": "http://json-schema.org/draft-04/schema#",
"type": "object",
"id": "file://schemas/credentials.json",
"oneOf": [
{
"properties": {"key1": {"type": "string"}},
"additionalProperties": false
},
{
"properties": {"key2": {"type": "string"}},
"additionalProperties": false
}
]
}
Also you can use uriParameters. Maybe it will help in your case.
#%RAML 0.8
title: API Using media type in the URL
version: v1
/users{mediaTypeExtension}:
uriParameters:
mediaTypeExtension:
enum: [ .json, .xml ]
description: Use .json to specify application/json or .xml to specify text/xml