I have been setting up the Nelmio API Doc Bundle with Swagger-PHP. All is working as expected the only thing I cannot seem to figure out/understand is the schemas.
In the User controller I have the following annotation:
* #OA\RequestBody(
* description="Updated user object",
* required=true,
* #OA\MediaType(
* mediaType="multipart/form-data",
* #OA\Schema(ref="#/components/schemas/User")
* )
* )
In my Entity/User class I defined the schema as follows:
/**
* User
*
* #OA\Schema(schema="User")
*
* #ORM\Table(schema="app", name="users")
* #ORM\Entity
*/
class User implements UserInterface
In the User controller I have the use App\Entity\User; defined as well.
In my mind this would be enough for the schema to be found but it doesn't work as I would otherwise not be posting here :)
The only way I was able to make it work is to run vendor/bin/openapi --format yaml src and copy/paste the schema output into the nelmio_api_doc.yaml file. This is the schema part I copy/pasted:
User:
properties:
first_name:
type: string
middle_name:
type: string
last_name:
type: string
initials:
type: string
username:
type: string
password:
type: string
status:
type: integer
email:
type: string
id:
type: integer
customer_id:
type: integer
locked:
type: boolean
type: object
So my question is, is this the way to about it or should the schema section be created automatically?
Thanks for any insights.
NelmioApiDocBundle does not load all files to fetch annotations in opposition to swagger-php, to load a schema, you should use the #Model annotation, see https://symfony.com/doc/current/bundles/NelmioApiDocBundle/index.html#use-models.
In your case, that would give the following:
use Nelmio\ApiDocBundle\Annotation\Model;
/**
* #OA\RequestBody(
* description="Updated user object",
* required=true,
* #OA\MediaType(
* mediaType="multipart/form-data",
* #OA\Schema(ref=#Model(type=User::class))
* )
* )
*/
Related
I'm using an OpenApi generator setup on Maven + Spring Boot (2.7.7) to generate API interfaces to implement in my software. This includes Validation.
I'm trying to understand if there is a way to specify validation in an OpenApi (3.0.1 atm but I'm flexible) yaml in such a way that I can have an object not be mandatory, but if ANY fields are included, then all of it must be included.
For example:
paths:
/api/complexobject:
get:
tags:
- complexobject
summary: generic search
operationId: findAll
parameters:
- in: query
name: complexobject
schema:
$ref: '#/components/schemas/ComplexObject'
explode: true
- in: query
name: sort
schema:
$ref: '#/components/schemas/Sorting'
- in: query
name: page
schema:
$ref: '#/components/schemas/Pagination'
responses:
"200":
description: successful operation
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/ComplexObject'
components:
schemas:
ComplexObject:
type: object
<cut>
Pagination:
type: object
properties:
page:
type: integer
minimum: 0
description: 0-indexed page number for pagination
size:
type: integer
minimum: 1
description: Number of returned records (page size). Suggested default is 20
required:
- page
- size
Sorting:
type: object
properties:
dir:
$ref: '#/components/schemas/SortDir'
sorted:
type: array
items:
type: string
required:
- dir
- sorted
SortDir:
type: string
enum: ["asc", "desc"]
The intention here is that the Sorting and the Pagination objects can be omitted from the query, however IF they're included, they must be wholly included (and with valid inputs too).
However it seems that at runtime the Pagination and Sorting Java Objects are instanced even without any parameters being sent to the Controller, which makes them fail the validation.
Is there some different validation setup I can use in the yaml, or do I need to change something in Java code?
Or, just remove the requirements altogether from the OpenAPI field declation and do programmatic validation directly instead?
As far as I can tell the specification I wrote is correct, however the generator (the most recent one available at the time of writing) doesn't support this kind of behaviour.
However what it does support is the use of defaults:
Pagination:
type: object
nullable: true
properties:
page:
type: integer
minimum: 0
default: 0 # Added default
description: 0-indexed page number for pagination
size:
type: integer
minimum: 1
default: 20 # Added default
description: Number of returned records (page size).
required:
- page
- size
Sorting:
type: object
nullable: true
properties:
dir:
$ref: '#/components/schemas/SortDir'
sorted:
type: array # No default needed, see below!
items:
type: string
required:
- dir
- sorted
SortDir:
type: string
default: "asc" # Added default
enum: ["asc", "desc"]
In this case you'll actually be bypassing the problem a bit, in that you'll never end up in a situation when any of the required fields will ever be null, see the generated code for reference:
#Generated(value = "org.openapitools.codegen.languages.SpringCodegen", date = "2023-01-30T16:25:58.375+01:00[Europe/Berlin]")
public class Pagination {
#JsonProperty("page")
private Integer page = 0;
#JsonProperty("size")
private Integer size = 20;
As a side note, the validation for the "sorted" property actually works as expected, even though the way the codegen attains that is weird. It'll work the same way whether you specify it as required or not AND without specifying defaults.
However we have the two following codegens:
Without required:
#JsonProperty("sorted")
#Valid
private List<String> sorted = null;
// Cut for brevity
/**
* Get sorted
* #return sorted
*/
#NotNull
#Schema(name = "sorted", required = false)
public List<String> getSorted() {
return sorted;
}
WITH required:
#JsonProperty("sorted")
#Valid
private List<String> sorted = new ArrayList<>();
// Cut for brevity
/**
* Get sorted
* #return sorted
*/
#NotNull
#Schema(name = "sorted", required = true)
public List<String> getSorted() {
return sorted;
}
This also means that the validation in the second case WILL be active... but never fail.
I have a GraphQL object type Student. And each student may or may not have Phone data.
Phone is another GraphQL type object (A child object in Student).
I have GraphQL dataloader registered on Phone. Things work fine as long as each student record has a corresponding Phone record. But if there is any student record who does not have a corresponding phone record, Dataloader fails with error:
"message": "Exception while fetching data (/students[0]/phone) : The size of the promised values MUST be the same size as the key list",
I believe this is because, dataloader is trying to assert that size-of-keys-in-list should be same as size-of-values-resolved-in-list.
Is there anyway to accept null as values which is a valid case in my requirement.
Found that the backend server is not even sending 'null', it just ignores the missing record.
From official source:
https://github.com/graphql-java/java-dataloader/blob/7bf46ea182b1f8ab255c3107b1b61d4afd36ad88/src/main/java/org/dataloader/BatchLoader.java
Dataloader does support null values:
*
* <pre>
* [
* { id: 2, name: 'San Francisco' },
* { id: 9, name: 'Chicago' },
* null,
* { id: 1, name: 'New York' }
* ]
* </pre>
I am almost done with my Swagger yaml file but there is a particular piece that I cannot seem to write out correctly:
You see where it says invitationCompletedOn and afterwards I need to add createdOn and completedOn:
When I add createdOn like so:
invitationCompletedOn:
type: string
example: 2021-09-22T08:27:49.622Z
createdOn:
type: string
example: 2021-09-22T08:27:49.622Z
But that breaks the output. Here is the entire yaml file:
/**
* #openapi
* /api/v2/organizations/:organizationId/invitations:
* get:
* description: Get a list of Data Access Object invitations.
* responses:
* 200:
* description: Get a list of Data Access Object invitations.
* content:
* application/json:
* schema:
* type: object
* properties:
* data:
* type: array
* items:
* type: object
* properties:
* _type:
* type: string
* description: Data Transfer Object of new invitation.
* example: ViewInvitationDto
* email:
* type: string
* description: Invitation email.
* example: dolittle#qa.co
* pui:
* type: string
* description: The patients user id.
* example: spyrt_p10102acc
* groupId:
* type: string
* description: The organizations identification.
* example: poi_5002
* roleDescription:
* type: string
* description: The role of the organization.
* example: admin
* viewHealthWorkerDto:
* type: object
* properties:
* _type:
* type: string
* description: Data Access Object for HealthWorker
* example: ViewHealthWorkerDto
* _id:
* type: string
* description: Identification for Healthworker
* example: 613ef0964b525196cf8599bf
* assignedRoleCode:
* type: string
* description: Assigned code role for organizations and healthworkers.
* example: armada.organization.doctor
* pui:
* type: string
* description: Assigned code role for organizations and healthworkers.
* example: spyrt_p10102acc
* firstName:
* type: string
* description: The first name of the healthworker.
* example: John
* lastName:
* type: string
* description: The last name of the healthworker.
* example: Dolittle
* healthWorkerTags:
* example:
* - key: migratedOn
* value: 2021-11-19T00:00:43.722Z
* - key: exporterVersion
* value: 2
* - key: _oldAccountId
* value: 10102
* - key: _oldPatientIds
* value: 10729
* schemaVersion:
* type: string
* example: 1
* createdOn:
* type: string
* example: 2020-05-08T07:43:43.000Z
* updatedOn:
* type: string
* example: 2020-05-08T07:43:43.000Z
* roleDescription:
* type: string
* example: admin
* email:
* type: string
* example: dolitle#qa.co
* userTags:
* example:
* - key: migratedOn
* value: 2021-11-19T00:00:43.722Z
* - key: exporterVersion
* value: 2
* - key: _oldAccountId
* value: 10102
* - key: _oldPatientIds
* value: 10729
* invitationCompletedOn:
* type: string
* example: 2021-09-22T08:27:49.622Z
*
*
*/
createdOn and completedOn should probably be sibling properties of viewHealthWorkerDto rather than of invitationCompletedOn. Try this:
* viewHealthWorkerDto:
* type: object
* properties:
* ...
* createdOn:
* type: string
* example: 2021-09-22T08:27:49.622Z
* completedOn:
* type: string
* example: 2021-09-22T08:27:49.622Z
Also, I see that you've defined an example value for userTags but no actual schema. From your screenshot, it looks like userTags (and similar arrays of key-value objects) can be properly defined like so:
* userTags:
* type: array
* items:
* type: object
* properties:
* key:
* type: string
* value:
* type: string
* example:
* - key: migratedOn
* value: 2021-11-19T00:00:43.722Z
* - ...
I just need to get a string from this json, for example: https://filesamples.com/samples/code/json/sample1.json
I took the Chainlink example and just change the URL and path. The example works fine, but if you use a different json o jobid it do not work.
Link to chainlink example: https://docs.chain.link/docs/large-responses/
Is there any way to solve this?
Code used:
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;
import "#chainlink/contracts/src/v0.8/ChainlinkClient.sol";
/**
* #notice DO NOT USE THIS CODE IN PRODUCTION. This is an example contract.
*/
contract GenericLargeResponse is ChainlinkClient {
using Chainlink for Chainlink.Request;
// variable bytes returned in a signle oracle response
bytes public data;
string public dataS;
/**
* #notice Initialize the link token and target oracle
* #dev The oracle address must be an Operator contract for multiword response
*
*
* Kovan Testnet details:
* Link Token: 0xa36085F69e2889c224210F603D836748e7dC0088
* Oracle: 0xc57B33452b4F7BB189bB5AfaE9cc4aBa1f7a4FD8 (Chainlink DevRel)
*
*/
constructor(
) {
setChainlinkToken(0xa36085F69e2889c224210F603D836748e7dC0088);
setChainlinkOracle(0xc57B33452b4F7BB189bB5AfaE9cc4aBa1f7a4FD8);
}
/**
* #notice Request variable bytes from the oracle
*/
function requestBytes(
)
public
{
bytes32 specId = "7a97ff8493ec406d90621b2531f9251a";
uint256 payment = 100000000000000000;
Chainlink.Request memory req = buildChainlinkRequest(specId, address(this), this.fulfillBytes.selector);
req.add("get","https://filesamples.com/samples/code/json/sample1.json");
req.add("path", "fruit");
requestOracleData(req, payment);
}
event RequestFulfilled(
bytes32 indexed requestId,
bytes indexed data
);
/**
* #notice Fulfillment function for variable bytes
* #dev This is called by the oracle. recordChainlinkFulfillment must be used.
*/
function fulfillBytes(
bytes32 requestId,
bytes memory bytesData
)
public
recordChainlinkFulfillment(requestId)
{
emit RequestFulfilled(requestId, bytesData);
data = bytesData;
dataS = string(data);
}
}
The response of that API is:
{
"fruit": "Apple",
"size": "Large",
"color": "Red"
}
The fruit has a value of Apple. You'll need to have the API return the bytes edition of Apple instead.
You'll notice the example returns JSON that looks like:
{
"image": "0x68747470733a2f2f697066732e696f2f697066732f516d5358416257356b716e3259777435444c336857354d736a654b4a4839724c654c6b51733362527579547871313f66696c656e616d653d73756e2d636861696e6c696e6b2e676966"
}
Which is the hex edition of the URL of the image.
I tried to generate a API document by apidoc
If my response is a array like
[
{"id" : 1, "name" : "John"},
{"id" : 2, "name" : "Mary"}
]
How could I set in #apiSuccess?
I had try Object[] but did not know how to set the field name.
Thanks.
Lets say in the above example the id and name are of a user-profile, and you have an array of user-profile objects, then the #apiSuccess will look like:
/**
* #api {get} /users
* #apiSuccess {Object[]} profiles List of user profiles.
* #apiSuccess {Number} profiles.Id Users id.
* #apiSuccess {String} profiles.Name Users Name.
*/