Swagger-PHP attribute paths is missing - swagger-php

In the project I use swagger-php and on production server I get the validation error "object has missing required properties ([\"paths\"])".
Despite this error swagger UI working fine.
Annotations in my php files:
/**<br />
* #SWG\Swagger(<br />
* #SWG\Info(<br />
* title="Заголовок",<br />
* description="Описпание",<br />
* contact={<br />
* "name": "Дмитрий Сабиров",<br />
* "email": "test#yandex.ru"<br />
* },<br />
* license={<br />
* "name": "MIT",<br />
* "url": "https://opensource.org/licenses/MIT"<br />
* },<br />
* version="1.0.0"<br />
* ),<br />
* schemes={"http"},<br />
* host=API_HOST,<br />
* basePath="/api"<br />
* )<br />
*/<br />
and
/**<br />
* #SWG\Get(<br />
* description="Проверка существования пользователя с данным емайлом.",<br />
* path="/users/check-email",<br />
* produces={"application/json"},<br />
* #SWG\Parameter(<br />
* name="email",<br />
* in="query",<br />
* description="проверяемый емайл",<br />
* required=true,<br />
* type="string"<br />
* ),<br />
* #SWG\Response(<br />
* response=200,<br />
* description="Если пользователь не существует - создаётся и ысылается письмо.",<br />
* #SWG\Schema(<br />
* type="object",<br />
* #SWG\Items(<br />
* #SWG\Property(<br />
* property="emailExist",<br />
* type="boolean"<br />
* ),<br />
* #SWG\Property(<br />
* property="userCreated",<br />
* type="boolean"<br />
* ),<br />
* #SWG\Property(<br />
* property="sentActivationEmail",<br />
* type="boolean"<br />
* )<br />
* ),<br />
* example={<br />
* "application/json": {<br />
* "emailExist": true,<br />
* "userCreated": false,<br />
* "sentActivationEmail": false<br />
* }<br />
* }<br />
* )<br />
* ),<br />
* #SWG\Response(<br />
* response=500,<br />
* description="внутренняя ошибка сервера",<br />
* )<br />
* )<br />
*/<br />
swagger api response is :
{
"swagger": "2.0",
"info": {
"title": "Заголовок",
"description": "Описпание",
"contact": {
"name": "Дмитрий Сабиров",
"email": "test#yandex.ru"
},
"license": {
"name": "MIT",
"url": "https://opensource.org/licenses/MIT"
},
"version": "1.0.0"
},
"host": "46.36.218.161",
"basePath": "/api",
"schemes": ["http"],
"paths": {
"/users/check-email": {
"get": {
"description": "Проверка существования пользователя с данным емайлом.",
"produces": ["application/json"],
"parameters": [{
"name": "email",
"in": "query",
"description": "проверяемый емайл",
"required": true,
"type": "string"
}],
"responses": {
"200": {
"description": "Если пользователь не существует - создаётся и высылается письмо.",
"schema": {
"type": "object",
"items": {
"properties": {
"emailExist": {
"type": "boolean"
},
"userCreated": {
"type": "boolean"
},
"sentActivationEmail": {
"type": "boolean"
}
}
},
"example": {
"application/json": {
"emailExist": true,
"userCreated": false,
"sentActivationEmail": false
}
}
}
},
"500": {
"description": "внутренняя ошибка сервера"
}
}
}
}
},
"definitions": {}
}
but swagger UI doc return error :
{
"messages": ["attribute paths is missing"],
"schemaValidationMessages": [{
"level": "error",
"domain": "validation",
"keyword": "required",
"message": "object has missing required properties ([\"paths\"])",
"schema": {
"loadingURI": "#",
"pointer": ""
},
"instance": {
"pointer": ""
}
}]
}
how fix this error ?

The validator badge uses the online Swagger Validator at swagger.io to verify your spec. The validator makes the request to:
http://online.swagger.io/validator?url=http://46.36.218.161/site/api
The validation server at online.swagger.io loads your spec directly from URL and does not use any authorizations (no API keys, Basic auth, etc.) Without authorization, your spec URL (/site/api) returns the following:
{
"securityDefinitions": {
"api_key": {
"in": "header",
"type": "apiKey",
"name": "api_key"
}
},
"swagger": "2.0",
"schemes": [
"http"
],
"info": {
"title": "Please take authentication firstly."
}
}
This is not a valid OpenAPI/Swagger spec, because it does not include the paths key. That's why the validator returns the error.
To avoid the error, you can do one of the following.
1) Modify your spec generator so that the initial (unauthenticated) spec includes "paths": {}:
{
"swagger": "2.0",
...
"paths": {}
}
This will make the spec valid.
2) Hide the validator badge. To do this, add validatorUrl: null in the HTML code of your Swagger UI page:
window.swaggerUi = new SwaggerUi({
url: url,
...
validatorUrl: null
});

Related

How to specify additionalProperties with l5-swagger

I am trying to write OpenAPI documentation for a project (written with Laravel) and struggling with several API points.
One returns
{
"active_options": {
"1": {
"name": "name1",
"type": "type1"
},
"2": {
"name": "name2",
"type": "type2"
},
"3": {
"name": "name3",
"type": "type3"
}
},
"server": {
"url": "URL...",
"settings1": "value"
},
"message": "Server settings retrieved."
}
I am struggling with how to write this annotation with l5-swagger plugin.
The "1", "2", "3" are optional and any combination of them is valid.
I wanted to use optionalProperties, but I don't know how to combine them together.
This is the closest I got:
* #OA\Response(
* response=200,
* description="Settings",
* #OA\JsonContent(
* #OA\Property(property="options",
* #OA\Items(
* #OA\Property(property="name", type="string"),
* #OA\Property(property="type", type="string")
* )
* ),
* )
* ),
But the sample generates this:
{
"options": [
{
"name": "string",
"type": "string"
}
]
}
Which obviously is missing the "1": ... part.
Maybe a better question would be how to do unnamed properties?
This is my solution:
/**
*
* #OA\Schema(
* schema="ExampleResponse",
* #OA\Xml(name="ExampleResponse"),
* #OA\Property(property="message", type="string", example="My solution."),
* #OA\Property(property="errors", type="object",
* #OA\AdditionalProperties(type="array",
* #OA\Items(
* #OA\Property(property="name", type="string"),
* #OA\Property(property="type", type="string")
* )
* )
* ),
* )
*
*/

Excecuting an Azure API Management Operation to save data in Azure Blob Storage fails in PowerShell-Script, but not in Postman or DeveloperPortal

In Azure API Management different results for different clients happen.
This is the API Management Policy to store a JSON-Document in Azure Blob Storage:
<base />
<!-- ########## put to storage ########## -->
<set-variable name="resource" value="#{
string prefix = "/payloads/" + context.Request.MatchedParameters["business-object"] + "/";
string fileName = string.empty;
return prefix + fileName;
}" />
<set-variable name="storageUrl" value="{{STORAGE_URL}}" />
<set-variable name="blobUrl" value="#((string)context.Variables["storageUrl"] + (string)context.Variables["resource"])" />
<set-variable name="storageKey" value="{{STORAGE_KEY}}" />
<set-variable name="storageAccountName" value="#(context.Variables.GetValueOrDefault<string>("storageUrl").Split('.')[0].Split('/')[2])" />
<set-variable name="date" value="#(DateTime.UtcNow.ToString("R"))" />
<set-variable name="version" value="2018-03-28" />
<trace source="keyInput">#{
string body = context.Request.Body.As<string>(preserveContent: true);
string contentType = "text/plain";
string contentLength = context.Request.Headers["Content-Length"][0];
var hmacSha256 = new System.Security.Cryptography.HMACSHA256 { Key = Convert.FromBase64String(context.Variables.GetValueOrDefault<string>("storageKey")) };
var payLoad = string.Format("{0}\n\n\n{1}\n\n{2}\n\n\n\n\n\n\nx-ms-blob-type:BlockBlob\nx-ms-date:{3}\nx-ms-version:{4}\n{5}",
"PUT",
contentLength,
contentType,
context.Variables["date"],
context.Variables["version"],
"/" + context.Variables.GetValueOrDefault<string>("storageAccountName") + context.Variables.GetValueOrDefault<string>("resource"));
return payLoad;
}</trace>
<send-request mode="new" response-variable-name="putStorageRequest" timeout="5" ignore-error="true">
<set-url>#((string)context.Variables["blobUrl"])</set-url>
<set-method>PUT</set-method>
<set-header name="x-ms-date" exists-action="override">
<value>#((string) context.Variables["date"] )</value>
</set-header>
<set-header name="x-ms-version" exists-action="override">
<value>#((string) context.Variables["version"] )</value>
</set-header>
<set-header name="x-ms-blob-type" exists-action="override">
<value>BlockBlob</value>
</set-header>
<set-header name="Content-Type" exists-action="override">
<value>application/json</value>
</set-header>
<set-header name="Authorization" exists-action="override">
<value>#{
string body = context.Request.Body.As<string>(preserveContent: true);
string contentType = "application/json";
string contentLength = context.Request.Headers["Content-Length"][0];
var hmacSha256 = new System.Security.Cryptography.HMACSHA256 { Key = Convert.FromBase64String(context.Variables.GetValueOrDefault<string>("storageKey")) };
var payLoad = string.Format("{0}\n\n\n{1}\n\n{2}\n\n\n\n\n\n\nx-ms-blob-type:BlockBlob\nx-ms-date:{3}\nx-ms-version:{4}\n{5}",
"PUT",
contentLength,
contentType,
context.Variables["date"],
context.Variables["version"],
"/" + context.Variables.GetValueOrDefault<string>("storageAccountName") + context.Variables.GetValueOrDefault<string>("resource"));
return "SharedKey "+ context.Variables.GetValueOrDefault<string>("storageAccountName") + ":" + Convert.ToBase64String(hmacSha256.ComputeHash(System.Text.Encoding.UTF8.GetBytes(payLoad)));
}</value>
</set-header>
<set-body>#( context.Request.Body.As<string>(true) )</set-body>
</send-request>
<choose>
<when condition="#(context.Variables["putStorageRequest"] == null)">
<return-response>
<set-status code="500" reason="Storage failure" />
<set-body />
</return-response>
</when>
<when condition="#(((IResponse)context.Variables["putStorageRequest"]).StatusCode != 201)">
<return-response>
<set-status code="500" reason="Storage failure" />
<set-body>#(((IResponse)context.Variables["putStorageRequest"]).Body.As<string>())</set-body>
</return-response>
</when>
</choose>
</inbound>
Ocp-Apim-Subscription-Key is used as HTTP Header for Authentication.
Executing it in API Management Developer Portal and Postman works as expected and the document is stored in Azure Blob Storage.
By executing a PowerShell-Script, it fails:
Invoke-RestMethod -Method POST -Uri $url -Headers $authHeaders -Body $body -ContentType "application/json"
Exception:
code: 403
reason: "Server failed to authenticate the request. Make sure the value of Authorization header is formed correctly including the signature."
The problem is, that the Content-Length changes during the inbound flow.
Please find an excerpt of the OCP-Trace below:
{
"traceEntries": {
"inbound": [
{
"source": "api-inspector",
"timestamp": "2019-10-22T13:52:47.4545895Z",
"elapsed": "00:00:00.0019930",
"data": {
"request": {
"method": "POST",
"url": "https://lorem.ipsum/private/api/store",
"headers": [
{
"name": "Ocp-Apim-Subscription-Key",
"value": "secret"
},
{
"name": "Connection",
"value": "Keep-Alive"
},
{
"name": "Content-Length",
"value": "13782"
},
{
"name": "Content-Type",
"value": "application/json"
},
{
"name": "Host",
"value": "lorem.ipsum"
},
{
"name": "User-Agent",
"value": "Mozilla/5.0 (Windows NT; Windows NT 10.0; en-US) WindowsPowerShell/5.1.14393.3053"
}
]
}
}
},
{
"source": "keyInput",
"timestamp": "2019-10-22T13:52:47.4545895Z",
"elapsed": "00:00:00.0036425",
"data": "PUT 13782 text/plain x-ms-blob-type:BlockBlob x-ms-date:Tue, 22 Oct 2019 13:52:47 GMT x-ms-version:2018-03-28 --CUTTED--"
},
{
source: "send-request",
timestamp: "2019-10-22T13:52:47.4545895Z",
elapsed: "00:00:00.0040858",
data: {
message: "Request is being forwarded to the backend service. Timeout set to 5 seconds",
request: {
method: "PUT",
url: "https://lorem.ipsum.blob.core.windows.net/payloads/stuff/b812a1b4-decd-45a1-bf00-f7792fb3789a",
headers: [
{
name: "Content-Length",
value: 13784
}
]
}
}
},
{
source: "send-request",
timestamp: "2019-10-22T13:52:47.5639587Z",
elapsed: "00:00:00.1123550",
data: {
response: {
status: {
code: 403,
reason: "Server failed to authenticate the request. Make sure the value of Authorization header is formed correctly including the signature."
}
}
}
}
]
}
}
The Content-Length has does not change if the API is called with Postman.
Why does the Content-Length change and causes me an authentication issue?
--- UPDATE ---
It also depends on the content:
- vs. –
Good:
{
"value": "te-st"
}
Bad:
"value": "te–st"
}
The other thing is the file-encoding of the JSON-Document used in PowerShell.
Bad works as ANSI
Bad does not work as UTF-8
This makes sense, it's also documented:
https://learn.microsoft.com/en-us/powershell/scripting/components/vscode/understanding-file-encoding?view=powershell-6#common-causes-of-encoding-issues
This problem occurs because VSCode encodes the character – in UTF-8 as the bytes 0xE2 0x80 0x93. When these bytes are decoded as Windows-1252, they are interpreted as the characters –.
Some strange character sequences that you might see include:
– instead of –
— instead of —
But this does not explain, why the Content-Length changes in API Management.
And how do I handle wrong encoding in API Management?

<ReferenceArrayInput /> not working with my custom data provider (prisma)

I'm currently building a data provider for prisma based on graphcool's one, and failed at succeeding to make <ReferenceArrayInput /> working.
The component successfully queries the values of the item and all the possibles values available, but it looks like it's failing when trying to match both. As a result, I get a label saying At least one of the associated references no longer appears to be available., with no values selected.
Here's the data returned by my data provider:
Returned by the GET_MANY request to grab actual choices:
[
{
"id": "cji4xk7ly00k3085444gszh6e",
"name": "value1",
"option.id": "cji4xk7lx00k20854ns2bersv",
"option": {
"id": "cji4xk7lx00k20854ns2bersv"
}
},
{
"id": "cji4xk7lz00k40854gp876vgn",
"name": "value2",
"option.id": "cji4xk7lx00k20854ns2bersv",
"option": {
"id": "cji4xk7lx00k20854ns2bersv"
}
}
]
And the data returned by the GET_LIST request to grab all possible values:
[
{
"id": "cji4xk7lz00k40854gp876vgn",
"name": "value2",
"option.id": "cji4xk7lx00k20854ns2bersv",
"option": {
"id": "cji4xk7lx00k20854ns2bersv"
}
},
{
"id": "cji4xk7ly00k3085444gszh6e",
"name": "value1",
"option.id": "cji4xk7lx00k20854ns2bersv",
"option": {
"id": "cji4xk7lx00k20854ns2bersv"
}
},
{
"id": "cjit6nvot00j80954n53vj6vt",
"name": "1x100ml",
"option.id": "cjit6dejm00bt0954ts5g2f5g",
"option": {
"id": "cjit6dejm00bt0954ts5g2f5g"
}
},
{
"id": "cjit6gu5o00d00954vzfuda0l",
"name": "19mg",
"option.id": "cjit6e66i00cb0954u1zlg1i3",
"option": {
"id": "cjit6e66i00cb0954u1zlg1i3"
}
}
]
On the JSX part, here's my code:
export const OptionEdit = props => (
<Edit title="Edit an option" {...props}>
<SimpleForm>
<DisabledInput source="id" />
<TextInput source="name" />
<ReferenceArrayInput source="values" reference="OptionValue" perPage={100}>
<SelectArrayInput optionText="name" />
</ReferenceArrayInput>
</SimpleForm>
</Edit>
);
I can try to setup something for you to reproduce my issue if needed, I'm hoping that this would be enough for you to help me. If it can help though, here's the repository containing my (very wip) data provider and the dashboard ra-data-prisma
Thanks a lot for you help.
Update:
Here's a codesandbox if you want to try: https://codesandbox.io/s/xvqm6mnyxz?expanddevtools=1&initialpath=App.js&module=%2Fsrc%2FApp.js
Just try to edit a User, and see the responses in the console along with the SelectArrayInput not being loaded with choices.
And here's the datamodel used for generating the Prisma API used in the codesanbox example:
type User {
id: ID! #unique
name: String!
addresses: [Address!]!
}
type Address {
id: ID! #unique
city: String!
user: User!
}
Got it. react-admin expects an array of ids to match the resources. All I needed to do was to set the <ReferenceArrayInput /> source prop to <resource>Ids. That fixed the issue.

react-admin: custom user filters at list component

My backend work with filters is like this (for example) :
filters = {
"groupOp": "AND",
"rules": [
{
"field": "id",
"op": "equal",
"data": 6
},
{
"field": "partnerId",
"op": "equal",
"data": 446
}
],
"groups": [
{
"groupOp": "AND",
"rules": [
{
"field": "username",
"op": "startswith",
"data": "Alex"
}
],
"groups": []
}
]
}
It's working fine with persistence filters, but it's not working with user filters that are passed to the list component. For example:
export const OrdersFilter = (props) => (
<Filter {...props}>
<TextInput label="username" source="username" />
<TextInput label="partnerId" source="partnerId" />
</Filter>
);
Because it is a key-value filter and I can't understand how I can add additional fields to the user filter field.
Or how I can wrap url changes ('##router/LOCATION_CHANGE') after action ##redux-form/CHANGE to modify the original filter which is passed to the url with the filtred field name :
filter=%7B%22partnerId%22%3A%226%22%7D&order=DESC&page=1&perPage=10'
to
filter={"field": "partnerId","op": "equal","data": 6}&order=DESC&page=1&perPage=10
You should simplify (flatten) the client side filters for react-admin and "translate" them in the shape expected by your backend in your dataProvider
You can choose a custom attribute in the filter and through the backend you can apply the filter.
const UserFilter = (props) => (
<Filter {...props}>
<TextInput label="Username" source="q" alwaysOn />
</Filter>
);
Backend: Loopback4 for example:
async find(#param.filter(User) filter?: Filter<User>): Promise<User[]> {
if(filter && filter.where){
const where = filter.where as any;
if(where["q"]){
where.username = { ilike: '%'+where["q"]+'%' };
filter.where = where;
}
}
const response = await this.userRepository.find(filter);
this.response.set("X-Total-Count", ""+response.length);
this.response.set("access-control-expose-headers", "X-Total-Count");
return response;
}

Aglio builder does not put object to schema if this object was defined as an array option

Here I'm defining a property in object Page Index called chartControllers which must be an array of objects called chartController.
# Page Index (Page Base)
- bundle: `site-index` (string, required) - название страницы на фронтенде
- nav (object, required) - навигационное меню
- settings: `/settings` (string, required) - юрл по которому будет осуществляться переход на страницу настроек
- signOut: `/signOut` (string, required) - юрл по которому будет отправляться POST запрос для выхода из аккаунта
- chart (object, required) - данные для первоначальной отрисовки графика, до выбора каких либо опций
- title (object, required)
- text: `выберите модуль` (string, required) - надпись в заголовке графика
- chartControllers (array[chartController], required) - массив всевозможных опций меню
# chartController
- title: `выбор инстанса` (string, required) - названии типа настройки в меню
- action: `/page` (string, required) - url по которому совершает данная настройка запрос при выборе ее опции(опций)
- name: `page` (string, required) - уникальное человекочитаемое имя настройки на английском языке
- method: `POST` (string, optional) - метод совершения запроса
aglio builder defines object Page Index correctly, with only one peculiar moment - it does not describe options for object `chartController`.
It provides me with the following body, which is fine:
{
"title": "главная",
"bundle": "site-index",
"nav": {
"settings": "/settings",
"signOut": "/signOut"
},
"chart": {
"title": {
"text": "выберите модуль"
}
},
"chartControllers": [
{
"title": "выбор инстанса",
"action": "/page",
"name": "page",
"method": "POST"
}
]
}
BUT! It provides me with not complete schema! It does not describe chartController.
The schema is the following:
{
"$schema": "http://json-schema.org/draft-04/schema#",
"type": "object",
"properties": {
"title": {
"type": "string",
"description": "Название страницы в табе браузера."
},
"bundle": {
"type": "string",
"description": "название страницы на фронтенде"
},
"nav": {
"type": "object",
"properties": {
"settings": {
"type": "string",
"description": "юрл по которому будет осуществляться переход на страницу настроек"
},
"signOut": {
"type": "string",
"description": "юрл по которому будет отправляться POST запрос для выхода из аккаунта"
}
},
"required": [
"settings",
"signOut"
],
"description": "навигационное меню"
},
"chart": {
"type": "object",
"properties": {
"title": {
"type": "object",
"properties": {
"text": {
"type": "string",
"description": "надпись в заголовке графика"
}
},
"required": [
"text"
]
}
},
"required": [
"title"
],
"description": "данные для первоначальной отрисовки графика, до выбора каких либо опций"
},
"chartControllers": {
"type": "array",
"description": "массив всевозможных опций меню"
}
},
"required": [
"title",
"bundle",
"nav",
"chart",
"chartControllers"
]
}
How can I fix that and see chartController to be defined in schema?
I had the same question as you, after searching the web for a long time, finally I found the solution:
Attributes with array of objects produce incomplete schema #328
Here is the fix for your particular question as an example:
(just add 'fixed', although I have know idea where this 'fixed' came from.)
# Page Index (Page Base)
......
- chartControllers (array[chartController], required, fixed) - массив всевозможных опций меню

Resources