Produce a schema-valid OpenAPI specification (closes #1457)

This commit is contained in:
Bernd Bestel 2021-07-05 23:23:59 +02:00
parent 6462dd8af6
commit 54bf7ed659
No known key found for this signature in database
GPG Key ID: 71BD34C0D4891300
10 changed files with 136 additions and 135 deletions

View File

@ -65,7 +65,7 @@ _RTL languages are unfortunately not yet supported._
## Things worth to know ## Things worth to know
### REST API & data model documentation ### REST API
See the integrated Swagger UI instance on [/api](https://demo.grocy.info/api). See the integrated Swagger UI instance on [/api](https://demo.grocy.info/api).
### Barcode readers & camera scanning ### Barcode readers & camera scanning

View File

@ -11,7 +11,7 @@ class FilesApiController extends BaseApiController
{ {
try try
{ {
if (!in_array($args['group'], $this->getOpenApiSpec()->components->internalSchemas->FileGroups->enum)) if (!in_array($args['group'], $this->getOpenApiSpec()->components->schemas->FileGroups->enum))
{ {
throw new \Exception('Invalid file group'); throw new \Exception('Invalid file group');
} }
@ -39,7 +39,7 @@ class FilesApiController extends BaseApiController
{ {
try try
{ {
if (!in_array($args['group'], $this->getOpenApiSpec()->components->internalSchemas->FileGroups->enum)) if (!in_array($args['group'], $this->getOpenApiSpec()->components->schemas->FileGroups->enum))
{ {
throw new \Exception('Invalid file group'); throw new \Exception('Invalid file group');
} }
@ -69,7 +69,7 @@ class FilesApiController extends BaseApiController
{ {
try try
{ {
if (!in_array($args['group'], $this->getOpenApiSpec()->components->internalSchemas->FileGroups->enum)) if (!in_array($args['group'], $this->getOpenApiSpec()->components->schemas->FileGroups->enum))
{ {
throw new \Exception('Invalid file group'); throw new \Exception('Invalid file group');
} }
@ -100,7 +100,7 @@ class FilesApiController extends BaseApiController
{ {
try try
{ {
if (!in_array($args['group'], $this->getOpenApiSpec()->components->internalSchemas->FileGroups->enum)) if (!in_array($args['group'], $this->getOpenApiSpec()->components->schemas->FileGroups->enum))
{ {
throw new \Exception('Invalid file group'); throw new \Exception('Invalid file group');
} }

View File

@ -216,26 +216,26 @@ class GenericEntityApiController extends BaseApiController
private function IsEntityWithEditRequiresAdmin($entity) private function IsEntityWithEditRequiresAdmin($entity)
{ {
return in_array($entity, $this->getOpenApiSpec()->components->internalSchemas->ExposedEntityEditRequiresAdmin->enum); return in_array($entity, $this->getOpenApiSpec()->components->schemas->ExposedEntityEditRequiresAdmin->enum);
} }
private function IsEntityWithNoListing($entity) private function IsEntityWithNoListing($entity)
{ {
return in_array($entity, $this->getOpenApiSpec()->components->internalSchemas->ExposedEntityNoListing->enum); return in_array($entity, $this->getOpenApiSpec()->components->schemas->ExposedEntityNoListing->enum);
} }
private function IsEntityWithNoEdit($entity) private function IsEntityWithNoEdit($entity)
{ {
return in_array($entity, $this->getOpenApiSpec()->components->internalSchemas->ExposedEntityNoEdit->enum); return in_array($entity, $this->getOpenApiSpec()->components->schemas->ExposedEntityNoEdit->enum);
} }
private function IsEntityWithNoDelete($entity) private function IsEntityWithNoDelete($entity)
{ {
return in_array($entity, $this->getOpenApiSpec()->components->internalSchemas->ExposedEntityNoDelete->enum); return in_array($entity, $this->getOpenApiSpec()->components->schemas->ExposedEntityNoDelete->enum);
} }
private function IsValidExposedEntity($entity) private function IsValidExposedEntity($entity)
{ {
return in_array($entity, $this->getOpenApiSpec()->components->internalSchemas->ExposedEntity->enum); return in_array($entity, $this->getOpenApiSpec()->components->schemas->ExposedEntity->enum);
} }
} }

View File

@ -36,45 +36,45 @@ class OpenApiController extends BaseApiController
$spec->info->description = str_replace('PlaceHolderManageApiKeysUrl', $this->AppContainer->get('UrlManager')->ConstructUrl('/manageapikeys'), $spec->info->description); $spec->info->description = str_replace('PlaceHolderManageApiKeysUrl', $this->AppContainer->get('UrlManager')->ConstructUrl('/manageapikeys'), $spec->info->description);
$spec->servers[0]->url = $this->AppContainer->get('UrlManager')->ConstructUrl('/api'); $spec->servers[0]->url = $this->AppContainer->get('UrlManager')->ConstructUrl('/api');
$spec->components->internalSchemas->ExposedEntity_IncludingUserEntities = clone $spec->components->internalSchemas->ExposedEntity; $spec->components->schemas->ExposedEntity_IncludingUserEntities = clone $spec->components->schemas->ExposedEntity;
foreach ($this->getUserfieldsService()->GetEntities() as $userEntity) foreach ($this->getUserfieldsService()->GetEntities() as $userEntity)
{ {
array_push($spec->components->internalSchemas->ExposedEntity_IncludingUserEntities->enum, $userEntity); array_push($spec->components->schemas->ExposedEntity_IncludingUserEntities->enum, $userEntity);
} }
$spec->components->internalSchemas->ExposedEntity_NotIncludingNotEditable = clone $spec->components->internalSchemas->StringEnumTemplate; $spec->components->schemas->ExposedEntity_NotIncludingNotEditable = clone $spec->components->schemas->StringEnumTemplate;
foreach ($spec->components->internalSchemas->ExposedEntity->enum as $value) foreach ($spec->components->schemas->ExposedEntity->enum as $value)
{ {
if (!in_array($value, $spec->components->internalSchemas->ExposedEntityNoEdit->enum)) if (!in_array($value, $spec->components->schemas->ExposedEntityNoEdit->enum))
{ {
array_push($spec->components->internalSchemas->ExposedEntity_NotIncludingNotEditable->enum, $value); array_push($spec->components->schemas->ExposedEntity_NotIncludingNotEditable->enum, $value);
} }
} }
$spec->components->internalSchemas->ExposedEntity_IncludingUserEntities_NotIncludingNotEditable = clone $spec->components->internalSchemas->StringEnumTemplate; $spec->components->schemas->ExposedEntity_IncludingUserEntities_NotIncludingNotEditable = clone $spec->components->schemas->StringEnumTemplate;
foreach ($spec->components->internalSchemas->ExposedEntity_IncludingUserEntities->enum as $value) foreach ($spec->components->schemas->ExposedEntity_IncludingUserEntities->enum as $value)
{ {
if (!in_array($value, $spec->components->internalSchemas->ExposedEntityNoEdit->enum)) if (!in_array($value, $spec->components->schemas->ExposedEntityNoEdit->enum))
{ {
array_push($spec->components->internalSchemas->ExposedEntity_IncludingUserEntities_NotIncludingNotEditable->enum, $value); array_push($spec->components->schemas->ExposedEntity_IncludingUserEntities_NotIncludingNotEditable->enum, $value);
} }
} }
$spec->components->internalSchemas->ExposedEntity_NotIncludingNotDeletable = clone $spec->components->internalSchemas->StringEnumTemplate; $spec->components->schemas->ExposedEntity_NotIncludingNotDeletable = clone $spec->components->schemas->StringEnumTemplate;
foreach ($spec->components->internalSchemas->ExposedEntity->enum as $value) foreach ($spec->components->schemas->ExposedEntity->enum as $value)
{ {
if (!in_array($value, $spec->components->internalSchemas->ExposedEntityNoDelete->enum)) if (!in_array($value, $spec->components->schemas->ExposedEntityNoDelete->enum))
{ {
array_push($spec->components->internalSchemas->ExposedEntity_NotIncludingNotDeletable->enum, $value); array_push($spec->components->schemas->ExposedEntity_NotIncludingNotDeletable->enum, $value);
} }
} }
$spec->components->internalSchemas->ExposedEntity_NotIncludingNotListable = clone $spec->components->internalSchemas->StringEnumTemplate; $spec->components->schemas->ExposedEntity_NotIncludingNotListable = clone $spec->components->schemas->StringEnumTemplate;
foreach ($spec->components->internalSchemas->ExposedEntity->enum as $value) foreach ($spec->components->schemas->ExposedEntity->enum as $value)
{ {
if (!in_array($value, $spec->components->internalSchemas->ExposedEntityNoListing->enum)) if (!in_array($value, $spec->components->schemas->ExposedEntityNoListing->enum))
{ {
array_push($spec->components->internalSchemas->ExposedEntity_NotIncludingNotListable->enum, $value); array_push($spec->components->schemas->ExposedEntity_NotIncludingNotListable->enum, $value);
} }
} }

View File

@ -253,7 +253,7 @@
"required": true, "required": true,
"description": "A valid entity name", "description": "A valid entity name",
"schema": { "schema": {
"$ref": "#/components/internalSchemas/ExposedEntity_NotIncludingNotListable" "$ref": "#/components/schemas/ExposedEntity_NotIncludingNotListable"
} }
}, },
{ {
@ -342,7 +342,7 @@
"required": true, "required": true,
"description": "A valid entity name", "description": "A valid entity name",
"schema": { "schema": {
"$ref": "#/components/internalSchemas/ExposedEntity_NotIncludingNotEditable" "$ref": "#/components/schemas/ExposedEntity_NotIncludingNotEditable"
} }
} }
], ],
@ -426,7 +426,7 @@
"required": true, "required": true,
"description": "A valid entity name", "description": "A valid entity name",
"schema": { "schema": {
"$ref": "#/components/internalSchemas/ExposedEntity_NotIncludingNotListable" "$ref": "#/components/schemas/ExposedEntity_NotIncludingNotListable"
} }
}, },
{ {
@ -510,7 +510,7 @@
"required": true, "required": true,
"description": "A valid entity name", "description": "A valid entity name",
"schema": { "schema": {
"$ref": "#/components/internalSchemas/ExposedEntity_NotIncludingNotEditable" "$ref": "#/components/schemas/ExposedEntity_NotIncludingNotEditable"
} }
}, },
{ {
@ -587,7 +587,7 @@
"required": true, "required": true,
"description": "A valid entity name", "description": "A valid entity name",
"schema": { "schema": {
"$ref": "#/components/internalSchemas/ExposedEntity_NotIncludingNotDeletable" "$ref": "#/components/schemas/ExposedEntity_NotIncludingNotDeletable"
} }
}, },
{ {
@ -630,7 +630,7 @@
"required": true, "required": true,
"description": "A valid entity name", "description": "A valid entity name",
"schema": { "schema": {
"$ref": "#/components/internalSchemas/ExposedEntity_IncludingUserEntities" "$ref": "#/components/schemas/ExposedEntity_IncludingUserEntities"
} }
}, },
{ {
@ -679,7 +679,7 @@
"required": true, "required": true,
"description": "A valid entity name", "description": "A valid entity name",
"schema": { "schema": {
"$ref": "#/components/internalSchemas/ExposedEntity_IncludingUserEntities_NotIncludingNotEditable" "$ref": "#/components/schemas/ExposedEntity_IncludingUserEntities_NotIncludingNotEditable"
} }
}, },
{ {
@ -734,7 +734,7 @@
"required": true, "required": true,
"description": "The file group", "description": "The file group",
"schema": { "schema": {
"$ref": "#/components/internalSchemas/FileGroups" "$ref": "#/components/schemas/FileGroups"
} }
}, },
{ {
@ -814,7 +814,7 @@
"required": true, "required": true,
"description": "The file group", "description": "The file group",
"schema": { "schema": {
"$ref": "#/components/internalSchemas/FileGroups" "$ref": "#/components/schemas/FileGroups"
} }
}, },
{ {
@ -865,7 +865,7 @@
"required": true, "required": true,
"description": "The file group", "description": "The file group",
"schema": { "schema": {
"$ref": "#/components/internalSchemas/FileGroups" "$ref": "#/components/schemas/FileGroups"
} }
}, },
{ {
@ -1900,7 +1900,7 @@
"description": "The due date of the product to add, when omitted, the current date is used" "description": "The due date of the product to add, when omitted, the current date is used"
}, },
"transaction_type": { "transaction_type": {
"$ref": "#/components/internalSchemas/StockTransactionType" "$ref": "#/components/schemas/StockTransactionType"
}, },
"price": { "price": {
"type": "number", "type": "number",
@ -1988,7 +1988,7 @@
"description": "The amount to remove - please note that when tare weight handling for the product is enabled, this needs to be the amount including the container weight (gross), the amount to be posted will be automatically calculated based on what is in stock and the defined tare weight" "description": "The amount to remove - please note that when tare weight handling for the product is enabled, this needs to be the amount including the container weight (gross), the amount to be posted will be automatically calculated based on what is in stock and the defined tare weight"
}, },
"transaction_type": { "transaction_type": {
"$ref": "#/components/internalSchemas/StockTransactionType" "$ref": "#/components/schemas/StockTransactionType"
}, },
"spoiled": { "spoiled": {
"type": "boolean", "type": "boolean",
@ -2444,7 +2444,7 @@
"description": "The due date of the product to add, when omitted, the current date is used" "description": "The due date of the product to add, when omitted, the current date is used"
}, },
"transaction_type": { "transaction_type": {
"$ref": "#/components/internalSchemas/StockTransactionType" "$ref": "#/components/schemas/StockTransactionType"
}, },
"price": { "price": {
"type": "number", "type": "number",
@ -2523,7 +2523,7 @@
"description": "The amount to remove - please note that when tare weight handling for the product is enabled, this needs to be the amount including the container weight (gross), the amount to be posted will be automatically calculated based on what is in stock and the defined tare weight" "description": "The amount to remove - please note that when tare weight handling for the product is enabled, this needs to be the amount including the container weight (gross), the amount to be posted will be automatically calculated based on what is in stock and the defined tare weight"
}, },
"transaction_type": { "transaction_type": {
"$ref": "#/components/internalSchemas/StockTransactionType" "$ref": "#/components/schemas/StockTransactionType"
}, },
"spoiled": { "spoiled": {
"type": "boolean", "type": "boolean",
@ -4114,91 +4114,6 @@
} }
}, },
"components": { "components": {
"internalSchemas": {
"ExposedEntity": {
"type": "string",
"enum": [
"products",
"chores",
"product_barcodes",
"batteries",
"locations",
"quantity_units",
"quantity_unit_conversions",
"shopping_list",
"shopping_lists",
"shopping_locations",
"recipes",
"recipes_pos",
"recipes_nestings",
"tasks",
"task_categories",
"product_groups",
"equipment",
"api_keys",
"userfields",
"userentities",
"userobjects",
"meal_plan",
"stock_log",
"stock",
"stock_current_locations",
"api_keys"
]
},
"ExposedEntityNoListing": {
"type": "string",
"enum": [
"api_keys"
]
},
"ExposedEntityNoEdit": {
"type": "string",
"enum": [
"stock_log",
"api_keys",
"stock",
"stock_current_locations"
]
},
"ExposedEntityNoDelete": {
"type": "string",
"enum": [
"stock_log",
"stock",
"stock_current_locations"
]
},
"ExposedEntityEditRequiresAdmin": {
"type": "string",
"enum": [
"api_keys"
]
},
"StockTransactionType": {
"type": "string",
"enum": [
"purchase",
"consume",
"inventory-correction",
"product-opened"
]
},
"FileGroups": {
"type": "string",
"enum": [
"equipmentmanuals",
"recipepictures",
"productpictures",
"userfiles",
"userpictures"
]
},
"StringEnumTemplate": {
"type": "string",
"enum": []
}
},
"schemas": { "schemas": {
"Product": { "Product": {
"type": "object", "type": "object",
@ -5082,7 +4997,7 @@
"type": "string" "type": "string"
}, },
"transaction_type": { "transaction_type": {
"$ref": "#/components/internalSchemas/StockTransactionType" "$ref": "#/components/schemas/StockTransactionType"
}, },
"row_created_timestamp": { "row_created_timestamp": {
"type": "string", "type": "string",
@ -5130,7 +5045,7 @@
"default": false "default": false
}, },
"transaction_type": { "transaction_type": {
"$ref": "#/components/internalSchemas/StockTransactionType" "$ref": "#/components/schemas/StockTransactionType"
}, },
"row_created_timestamp": { "row_created_timestamp": {
"type": "string", "type": "string",
@ -5180,7 +5095,7 @@
"type": "string" "type": "string"
}, },
"transaction_type": { "transaction_type": {
"$ref": "#/components/internalSchemas/StockTransactionType" "$ref": "#/components/schemas/StockTransactionType"
} }
}, },
"example": { "example": {
@ -5428,6 +5343,91 @@
"type": "string" "type": "string"
} }
} }
},
"ExposedEntity": {
"type": "string",
"enum": [
"products",
"chores",
"product_barcodes",
"batteries",
"locations",
"quantity_units",
"quantity_unit_conversions",
"shopping_list",
"shopping_lists",
"shopping_locations",
"recipes",
"recipes_pos",
"recipes_nestings",
"tasks",
"task_categories",
"product_groups",
"equipment",
"api_keys",
"userfields",
"userentities",
"userobjects",
"meal_plan",
"stock_log",
"stock",
"stock_current_locations",
"api_keys"
]
},
"ExposedEntityNoListing": {
"type": "string",
"enum": [
"api_keys"
]
},
"ExposedEntityNoEdit": {
"type": "string",
"enum": [
"stock_log",
"api_keys",
"stock",
"stock_current_locations"
]
},
"ExposedEntityNoDelete": {
"type": "string",
"enum": [
"stock_log",
"stock",
"stock_current_locations"
]
},
"ExposedEntityEditRequiresAdmin": {
"type": "string",
"enum": [
"api_keys"
]
},
"StockTransactionType": {
"type": "string",
"enum": [
"purchase",
"consume",
"inventory-correction",
"product-opened"
]
},
"FileGroups": {
"type": "string",
"enum": [
"equipmentmanuals",
"recipepictures",
"productpictures",
"userfiles",
"userpictures"
]
},
"StringEnumTemplate": {
"type": "string",
"enum": [
""
]
} }
}, },
"securitySchemes": { "securitySchemes": {

View File

@ -324,7 +324,7 @@ msgstr ""
msgid "Manage API keys" msgid "Manage API keys"
msgstr "" msgstr ""
msgid "REST API & data model documentation" msgid "REST API browser"
msgstr "" msgstr ""
msgid "API keys" msgid "API keys"

View File

@ -20,7 +20,8 @@ const swaggerUi = SwaggerUIBundle({
HideTopbarPlugin HideTopbarPlugin
], ],
layout: 'StandaloneLayout', layout: 'StandaloneLayout',
docExpansion: "list" docExpansion: "list",
defaultModelsExpandDepth: -1
}); });
window.ui = swaggerUi; window.ui = swaggerUi;

View File

@ -49,7 +49,7 @@ class UserfieldsService extends BaseService
public function GetEntities() public function GetEntities()
{ {
$exposedDefaultEntities = $this->getOpenApiSpec()->components->internalSchemas->ExposedEntity->enum; $exposedDefaultEntities = $this->getOpenApiSpec()->components->schemas->ExposedEntity->enum;
$userEntities = []; $userEntities = [];
$specialEntities = ['users']; $specialEntities = ['users'];

View File

@ -652,7 +652,7 @@
@endif @endif
<a class="dropdown-item discrete-link" <a class="dropdown-item discrete-link"
target="_blank" target="_blank"
href="{{ $U('/api') }}"><i class="fas fa-book"></i>&nbsp;{{ $__t('REST API & data model documentation') }}</a> href="{{ $U('/api') }}"><i class="fas fa-book"></i>&nbsp;{{ $__t('REST API browser') }}</a>
<a class="dropdown-item discrete-link" <a class="dropdown-item discrete-link"
href="{{ $U('/barcodescannertesting') }}"><i class="fas fa-barcode"></i>&nbsp;{{ $__t('Barcode scanner testing') }}</a> href="{{ $U('/barcodescannertesting') }}"><i class="fas fa-barcode"></i>&nbsp;{{ $__t('Barcode scanner testing') }}</a>
<div class="dropdown-divider"></div> <div class="dropdown-divider"></div>

View File

@ -18,7 +18,7 @@
<link rel="icon" <link rel="icon"
href="{{ $U('/img/grocy_icon.svg?v=', true) }}{{ $version }}"> href="{{ $U('/img/grocy_icon.svg?v=', true) }}{{ $version }}">
<title>{{ $__t('REST API & data model documentation') }} | grocy</title> <title>{{ $__t('REST API browser') }} | grocy</title>
<link href="{{ $U('/node_modules/swagger-ui-dist/swagger-ui.css?v=', true) }}{{ $version }}" <link href="{{ $U('/node_modules/swagger-ui-dist/swagger-ui.css?v=', true) }}{{ $version }}"
rel="stylesheet"> rel="stylesheet">