I am using laravel 9 and swagger and I installed the DarkaOnLine/L5-Swagger package.
In swagger config I added:
'securityDefinitions' => [
'securitySchemes' => [
'api_key_security' => [ // Unique name of security
'type' => 'apiKey', // The type of the security scheme. Valid values are "basic", "apiKey" or "oauth2".
'description' => 'A short description for security scheme',
'name' => 'x-session-id', // The name of the header or query parameter to be used.
'in' => 'header', // The location of the API key. Valid values are "query" or "header".
],
],
'security' => [
[
'api_key_security' => []
],
],
],
and in function definition inside the controller I need to use the x-session-id header:
/**
* Create TEST
* #OA\Get (
* path="/api/test",
* tags={"test"},
* summary="Endpoint de prueba",
* security={
* {"api_key_security":{}}
* },
* #OA\RequestBody(
* #OA\MediaType(
* mediaType="application/json"
* )
* ),
* #OA\Response(
* response=200,
* description="success",
* ),
* )
*/
public function index(): ?string
I saw in internet that adding the security key it should works but when I send the request in swagger the header 'x-session-id' is not added.
You can use headers parameter in the response body like this
#OA\Header(
header="X-Rate-Limit",
description="calls per hour allowed by the user",
#OA\Schema(
type="integer",
format="int32"
)
),
Related
I use Laravel v8 with spatie/laravel-feed v4 with this codes:
routes/web.php
Route::get('feed', '\App\Models\Blog\Post#getFeedItems')->name("feeds.main");
config/feed.php
<?php
return [
'feeds' => [
'main' => [
/*
* Here you can specify which class and method will return
* the items that should appear in the feed. For example:
* [App\Model::class, 'getAllFeedItems']
*
* You can also pass an argument to that method. Note that their key must be the name of the parameter: *
* [App\Model::class, 'getAllFeedItems', 'parameterName' => 'argument']
*/
'items' => ['App\Models\Blog\Post', 'getFeedItems'],
/*
* The feed will be available on this url.
*/
'url' => '/feed',
'title' => 'News',
'description' => 'The description of the feed.',
'language' => 'hu-HU',
/*
* The image to display for the feed. For Atom feeds, this is displayed as
* a banner/logo; for RSS and JSON feeds, it's displayed as an icon.
* An empty value omits the image attribute from the feed.
*/
'image' => '',
/*
* The format of the feed. Acceptable values are 'rss', 'atom', or 'json'.
*/
'format' => 'atom',
/*
* The view that will render the feed.
*/
'view' => 'feed::atom',
/*
* The mime type to be used in the <link> tag. Set to an empty string to automatically
* determine the correct value.
*/
'type' => 'application/atom+xml',
/*
* The content type for the feed response. Set to an empty string to automatically
* determine the correct value.
*/
'contentType' => '',
],
],
];
The result in webpage's HTML code:
<link rel="alternate" type="application/atom+xml" href="http://mydomain.test/feed" title="News">
But if I open the link in browser the result contains JSON code.
How can I set up correctly / force the application/atom+xml content type?
The error is in route
Route::get('feed', '\App\Models\Blog\Post#getFeedItems')->name("feeds.main");
So it should be
Route::get('/feed',FeedController::class)->name("feeds.main");
or
Route::feeds();
I use Laravel 8 with spatie/laravel-feed 4.0 package with this codes:
routes/web.php
Route::feeds();
config/feed.php
<?php
return [
'feeds' => [
'main' => [
/*
* Here you can specify which class and method will return
* the items that should appear in the feed. For example:
* [App\Model::class, 'getAllFeedItems']
*
* You can also pass an argument to that method. Note that their key must be the name of the parameter: *
* [App\Model::class, 'getAllFeedItems', 'parameterName' => 'argument']
*/
'items' => ['App\Models\Blog\Post', 'getFeedItems'],
/*
* The feed will be available on this url.
*/
'url' => '/feed',
'title' => 'News',
'description' => 'The description of the feed.',
'language' => 'en-US',
/*
* The image to display for the feed. For Atom feeds, this is displayed as
* a banner/logo; for RSS and JSON feeds, it's displayed as an icon.
* An empty value omits the image attribute from the feed.
*/
'image' => '',
/*
* The format of the feed. Acceptable values are 'rss', 'atom', or 'json'.
*/
'format' => 'atom',
/*
* The view that will render the feed.
*/
'view' => 'feed::atom',
/*
* The mime type to be used in the <link> tag. Set to an empty string to automatically
* determine the correct value.
*/
'type' => '',
/*
* The content type for the feed response. Set to an empty string to auatomatically
* determine the correct value.
*/
'contentType' => '',
],
],
];
app/Models/Blog/Post.php
class Post extends Model implements Feedable
{
// ...
public function toFeedItem(): FeedItem
{
return FeedItem::create([
'id' => $this->id,
'title' => $this->title,
'summary' => $this->summary,
'updated' => $this->updated_at,
'link' => $this->link,
'author' => $this->user->name,
]);
}
public static function getFeedItems()
{
return Post::orderBy('publish_date', 'desc')
->limit(10)
->get();
}
}
resources/views/layout.blade.php
<!DOCTYPE html>
<html lang="#yield('lang')">
<head>
<!-- ... -->
#include('feed::links')
</head>
<body>
<!-- ... -->
I tried to run these commands:
php artisan route:clear
php artisan optimize
...but noting changed.
I get this error message:
Route [feeds.main] not defined. (View:
/var/www/html/vendor/spatie/laravel-feed/resources/views/links.blade.php)
Any idea what I missed?
If you looking for immediate solution then you can remove
Route::feeds();
and add route like below
Route::get('feed',FeedController::class)->name("feeds.main");
and make sure name of url should match in config file
<?php
return [
'feeds' => [
'main' => [
/*
* Here you can specify which class and method will return
* the items that should appear in the feed. For example:
* [App\Model::class, 'getAllFeedItems']
*
* You can also pass an argument to that method. Note that their key must be the name of the parameter: *
* [App\Model::class, 'getAllFeedItems', 'parameterName' => 'argument']
*/
'items' => ['App\Models\Order', 'getFeedItems'],
/*
* The feed will be available on this url.
*/
'url' => '/feed',
'title' => 'My feed',
'description' => 'The description of the feed.',
'language' => 'en-US',
/*
* The image to display for the feed. For Atom feeds, this is displayed as
* a banner/logo; for RSS and JSON feeds, it's displayed as an icon.
* An empty value omits the image attribute from the feed.
*/
'image' => '',
/*
* The format of the feed. Acceptable values are 'rss', 'atom', or 'json'.
*/
'format' => 'atom',
/*
* The view that will render the feed.
*/
'view' => 'feed::atom',
/*
* The mime type to be used in the <link> tag. Set to an empty string to automatically
* determine the correct value.
*/
'type' => '',
/*
* The content type for the feed response. Set to an empty string to automatically
* determine the correct value.
*/
'contentType' => '',
],
],
];
and run following command
php artisan route:clear
php artisan optimize
Updates
Easy solution is if you specify url in config file then it wont throw error.For example in config/feed.php
'url' => '/feed',
so no need to change anythink
Really sorry if I've missed something here, but I have searched the issues and docs high and low, and I cannot find why this is not working. Perhaps I need another tutorial on using the internet ;)
Consider the following entity annotation, in the openapi_context section the body.description and responses.200.description have no effect at all, and its driving me slightly mad... You should know that I am using de/normalization contexts but that doesn't seem related so I have left it out.
/**
* User entity
*
* #API\ApiResource(
* collectionOperations={
* "authenticate"={
* "method"="POST",
* "status"=200,
* "path"="/authenticate",
* "controller"=UserLoginController::class,
* "openapi_context"={
* "responses"={"200"={"description"="API token and secret"}},
* "body"={"description"="Login details"},
* "summary"="User authentication",
* "description"="Provides auth tokens on success",
* },
* },
* }
* )
*
*/
class User implements UserInterface
{
...
}
The result (blue blocks as expected, red blocks not working):
I have spent way too much time on this issue as it is, I would really appreciate it if someone could help me put this to bed. I have checked/tried the following to no avail;
Creating Custom Operations and Controllers
Custom Symfony Action with API Platform bundle
[Question] Documentation API with Swagger #143
Changing Operations in the OpenAPI Documentation
Composer versions (relevant parts):
{
"require": {
"php": ">=7.2.5",
"api-platform/core": "^2.6",
"symfony/framework-bundle": "5.2.*"
}
}
After some experimenting, I found that the openapi_context annotation attribute does indeed seems to ignore the response documentation. It does however allow you to provide the request body description that you are missing:
#[ApiResource(
collectionOperations: [
'test' => [
'method' => 'POST',
'path' => '/test',
'openapi_context' => [
'summary' => 'The endpoint summary',
'description' => 'The endpoint description',
'requestBody' => [
'description' => 'The endpoint request body description', // This one
'content' => [
'application/json' => [
'schema' => [
'$ref' => '#/components/schemas/MyResource-some.group'
],
],
],
],
],
]
],
)]
I'm using PHP 8.0.3 and API Platform 2.6.3 while writing this, changing the annotations to docblocks should result in the same outcome for you though.
In order to document the endpoint response specification however, I had to implement a custom OpenApiFactoryInterface:
<?php declare(strict_types = 1);
namespace App;
use ApiPlatform\Core\OpenApi\Factory\OpenApiFactoryInterface;
use ApiPlatform\Core\OpenApi\Model\Operation;
use ApiPlatform\Core\OpenApi\Model\PathItem;
use ApiPlatform\Core\OpenApi\Model\RequestBody;
use ApiPlatform\Core\OpenApi\OpenApi;
use ArrayObject;
use UnexpectedValueException;
class MyResourceOpenApiFactory implements OpenApiFactoryInterface
{
private OpenApiFactoryInterface $openApiFactory;
public function __construct(OpenApiFactoryInterface $openApiFactory)
{
$this->openApiFactory = $openApiFactory;
}
public function __invoke(array $context = []): OpenApi
{
$openApi = ($this->openApiFactory)($context);
$components = $openApi->getComponents();
$schemas = $components->getSchemas();
if (null === $schemas) {
throw new UnexpectedValueException('Failed to obtain OpenApi schemas');
}
$pathItem = new PathItem(
'MyResource test endpoint',
'A test summary',
'A test description',
null,
null,
// Your custom post operation
new Operation(
'testMyResourceCollection', // the operation route name
[
'MyResource' // your resource name
],
[
// response specifications
'201' => [
'description' => 'test endpoint 201 response description',
'content' => [
'application/json' => [
'schema' => [
'$ref' => '#/components/schemas/MyResource-read', // your resource (read) schema
],
],
],
],
],
'A test endpoint summary',
'A test endpoint description',
null,
[],
new RequestBody(
'A test request body description',
new ArrayObject([
'application/json' => [
'schema' => [
'$ref' => '#/components/schemas/MyResource-write', // your resource (write) schema
],
],
]),
),
),
);
$paths = $openApi->getPaths();
$paths->addPath('/my_resources/test', $pathItem);
return $openApi;
}
}
Have this OpenApiFactoryInterface implementation wired by the service container as a decorator to api_platform.openapi.factory:
App\MyResourceOpenApiFactory:
decorates: 'api_platform.openapi.factory'
autoconfigure: false
Change the references to the example MyResource name to a resource name of your choice (like User).
Sidenote:
This whole process of customizing OpenApi endpoint documentation in API Platform is currently quite convoluted in my opinion. Use the implementation I provided as a reference to your own implementation, as you most likely need to make a few adjustments to it in order to make it satisfy your specific use case.
I'm generating rest api documentation and the tool I'm using is swagger. I've configured l5-swagger package for this. unfortunately i'm not getting the authorize option from the ui. my l5-swagger.php file
'passport' => [
'type' => 'oauth2',
'description' => 'Laravel passport oauth2 security.',
'in' => 'header',
'scheme' => 'https',
'flows' => [
"password" => [
"authorizationUrl" => config('app.url') . '/oauth/authorize',
"tokenUrl" => config('app.url') . '/oauth/token',
"refreshUrl" => config('app.url') . 'oauth/token/refresh',
"scopes" => []
],
],
],
My Controller looks like this
/**
* #OA\GET(
** path="/api/product/{product_id}",
* tags={"Product"},
* security={
* {
* "passport": {}},
* },
* summary="Product Detail",
* operationId="productdetails",
* #OA\Parameter(
* name="product_id",
* in="path",
* required=true,
* #OA\Schema(
* type="integer"
* )
* ),
* #OA\Response(
* response=200,
* description="Success",
* #OA\MediaType(
* mediaType="application/json",
* )
* ),
*)
public function get_product($product_id){
}
i'm not sure what I'm missing that's causing the disappearing of authorize lock button
Instead of writing your own security There is 'securitySchemes' => [] find passport array just uncomment that and replace the dummy oauth url with the real one's and all set.
How do I add custom options to a product like you can in the backend, using the API.
Im using C# but if you know how do to this in Php, that would be helpful too.
I noticed that product has this:
var product = new catalogProductCreateEntity();
product.options_container = "blah";
And there is this:
catalogAttributeOptionEntity optionEntity = new catalogAttributeOptionEntity();
optionEntity.value = "sds";
optionEntity.label = "ere";
But I cant see a way of utilizing them, im not sure how to make a container, and the catalogAttributeOptionEntity does not have all the properties needed to make a custom option.
Look at the admin product controller. Yes it is possible.
/**
* Initialize data for configurable product
*/
if (($data = $this->getRequest()->getPost('configurable_products_data')) && !$product->getConfigurableReadonly()) {
$product->setConfigurableProductsData(Zend_Json::decode($data));
}
if (($data = $this->getRequest()->getPost('configurable_attributes_data')) && !$product->getConfigurableReadonly()) {
$product->setConfigurableAttributesData(Zend_Json::decode($data));
}
$product->setCanSaveConfigurableAttributes((bool)$this->getRequest()->getPost('affect_configurable_product_attributes') && !$product->getConfigurableReadonly());
/**
* Initialize product options
*/
if (isset($productData['options']) && !$product->getOptionsReadonly()) {
$product->setProductOptions($productData['options']);
}
This is not documented anywhere (else), but at least in Magento 1.6 one can find the appropriate API methods for product options in the source code. (I don 't know since which version that feature exists.)
The API itself is defined in: app/code/core/Mage/Catalog/etc/api.xml
<catalog_product_custom_option translate="title" module="catalog">
<title>Catalog product custom options API</title>
<model>catalog/product_option_api</model>
<acl>catalog/product/option</acl>
<methods>
<add translate="title" module="catalog">
<title>Add new custom option into product</title>
<acl>catalog/product/option/add</acl>
</add>
<update translate="title" module="catalog">
<title>Update custom option of product</title>
<acl>catalog/product/option/update</acl>
</update>
<types translate="title" module="catalog">
<title>Get list of available custom option types</title>
<acl>catalog/product/option/types</acl>
</types>
<info translate="title" module="catalog">
<title>Get full information about custom option in product</title>
<acl>catalog/product/option/info</acl>
</info>
<list translate="title" module="catalog">
<title>Retrieve list of product custom options</title>
<acl>catalog/product/option/list</acl>
<method>items</method>
</list>
<remove translate="title" module="catalog">
<title>Remove custom option</title>
<acl>catalog/product/option/remove</acl>
</remove>
</methods>
</catalog_product_custom_option>
The called functions are defined in: app/code/core/Mage/Catalog/Model/Product/Option/Api.php
class Mage_Catalog_Model_Product_Option_Api extends Mage_Catalog_Model_Api_Resource
{
/**
* Add custom option to product
*
* #param string $productId
* #param array $data
* #param int|string|null $store
* #return bool $isAdded
*/
public function add( $productId, $data, $store = null )
/**
* Update product custom option data
*
* #param string $optionId
* #param array $data
* #param int|string|null $store
* #return bool
*/
public function update( $optionId, $data, $store = null )
/**
* Read list of possible custom option types from module config
*
* #return array
*/
public function types()
/**
* Get full information about custom option in product
*
* #param int|string $optionId
* #param int|string|null $store
* #return array
*/
public function info( $optionId, $store = null )
/**
* Retrieve list of product custom options
*
* #param string $productId
* #param int|string|null $store
* #return array
*/
public function items( $productId, $store = null )
/**
* Remove product custom option
*
* #param string $optionId
* #return boolean
*/
public function remove( $optionId )
/**
* Check is type in allowed set
*
* #param string $type
* #return bool
*/
protected function _isTypeAllowed( $type )
}
The $data-array also is a bit tricky, since it's keys partly depend on the selected option type. The basic $data-array looks like:
$data = array (
'is_delete' => 0,
'title' => 'Custom Option Label',
'type' => 'text',
'is_require' => 0,
'sort_order' => 1,
'additional_fields' => array (
0 => array (
'price' => '10.0000',
'price_type' => 'fixed', // 'fixed' or 'percent'
'sku' => '',
),
),
);
The additional_fields always conatin at least one row with at least the columns price, price_type and sku. More additional fields (maf: …) may be added depending on the type. The types in the group select may have more than one row specified in additional_fields. The custom option types/type-groups are:
text (maf: 'max_characters')
field
area
file (maf: 'file_extension', 'image_size_x', 'image_size_y')
file
select (maf: 'value_id', 'title', 'sort_order')
drop_down
radio
checkbox
multiple
date
date
date_time
time
Examples for complete option data arrays:
// type-group: select, type: checkbox
$data = array (
'is_delete' => 0,
'title' => 'extra Option for that product',
'type' => 'checkbox',
'is_require' => 0,
'sort_order' => 1,
'additional_fields' => array (
0 => array (
'value_id' => '3',
'title' => 'Yes',
'price' => 10.00,
'price_type' => 'fixed',
'sku' => NULL,
'sort_order' => 1,
),
1 => array (
'value_id' => 3,
'title' => 'No',
'price' => 0.00,
'price_type' => 'fixed',
'sku' => NULL,
'sort_order' => 2,
),
),
);
// type-group: text, type: field
$data = array (
'is_delete' => 0,
'title' => 'Custom Option Label',
'type' => 'text',
'is_require' => 0,
'sort_order' => 1,
'additional_fields' => array (
0 => array (
'price' => 10.00,
'price_type' => 'fixed',
'sku' => NULL,
'max_characters' => 150,
),
),
);
In the end I decided it cant be done via the API and went to the database directly.