Are declarations of entire responses reusable in swagger-php? - annotations

I would love a way to declare some responses that are the same for every endpoint; for example every endpoint will output a 401 if you don't have a proper token, and then there can be another group of endpoints that will all output 404 if you don't have a valid id of something in the beginning of your path.
So for almost every method I find myself copy-pasting things such as this:
* #OA\Response(response=401, description="If no token..."),
* #OA\Response(response=404, description="If ID of thing invalid..."),
The getting started section says that reusable responses are supported, but covering only the properties (of successful responses).
There are no examples for the kind of thing i'm looking for, and I also can't get myself to guess anything that would compile.
Something like #OA\Response(ref="#/components/schemas/Unauthorized") seems like it should be the way (and the compilation doesn't complain about the presence of the ref attribute), but how do i then declare what the Unauthorized schema looks like? Because declaring a #OA\Response says it only expects the fields: "ref", "response", "description", "headers", "content", "links", "x", none of which can serve as an identifier.
I am using this in conjunction with L5-swagger for Laravel support.

Declare a response outside of an operation:
/**
* #OA\Response(response="Unauthorized", description="If no token...")
*/
This will add it to the #/components/responses/ using the response= as value as key.
Then you're able to reuse it later inside an operation:
/**
* #OA\Get(
* path="/example",
* #OA\Response(response=401, ref="#/components/responses/Unauthorized")
* )
*/

Related

Returning Array in USER_INT userFunc leads to <!--INT_SCRIPT output

I have a userFunc which I call via
lib.random = USER_INT
lib.random {
userFunc = My\Plugin\UserFunc\Functions->random
}
when I return a Array and try to access it is fails.
<v:variable.set name="random" value="{f:cObject(typoscriptObjectPath: 'lib.random')}" />
{random.max}
When I try to debug out it I get some <!--INT_SCRIPT string
Did anyone know the problem and a Solution?
/e:
I would like to make the problem a little clearer by describing the Szenario.
I have a Plugin with a Login form. When the User logs in I set a JWT with various basic informations (name, email).
This Informations have to be displayed on various places around the Website, not only on one page (for example profile page). Some cases are prefilled forms or just silly "Hello, Paul" stuff.
So when I first log in (Fresh browser, no cache) then I read "Hello, Paul" after I log out and log in with a another Account (Lets call it "Peter") then It still is written "Hello, Paul" , nor "Hello, Peter". When I clear my browser Cache then everything is fine.
Maybe this helps maybe to solve my dilemma. :)
TL;DR: uncached parts in TYPO3 are replaced in the generated page output string using markers and cannot communicate in the direction intended here. Selectively caching, disabling cache or detaching the data from the main request (with XHR or other) are the only possible methods.
It should be clear that USER_INT achieves its functionality by string replacement in the generated page body. This means, among other things, that:
You can never pass the output of a USER_INT to anything in Fluid, not even if the entire page is uncached. You will effectively be passing a string containing <!---INT_SCRIPT... (the entire marker).
You can however generate USER_INT from Fluid, which ends up in the generated page, which is then replaced with the rendered object (use f:cObject to render a USER_INT or COA_INT).
Then there are the use case context considerations. First of all, a cookie (in practice) changes the request parameters and should be part of the cache identifier that your page uses (it is not this way by default). Second, if said cookie changes the way the page renders (and it does, given your use case) this will cause trouble when the page is cached. Third, the page output changing based on a cookie indicates perhaps sensitive information or at the very least user-specific information.
Taking the above into account your use case should do one of the following things:
Either render the entire chunk of output that changes based on cookie, as USER_INT. That means wrapping the entire Fluid output and rendering it all without caching. Note that template compiling still happens (and you can use f:cache.static to hard-cache certain parts if they never change based on request parameters).
Or add the cookie value to the cHash (page hash value) so that having the cookie set means you request a specific cached version that matches the cookie. This is the preferred way if your cookie's values is generally the same for many users (e.g. it contains a selected contact person from a limited list and stores that in a cookie).
Or, in the case that your output contains actually sensitive information, require that the content element or page is only available when logged in with a specific group. This has two purposes: first, it protects the page from being viewed without authentication - but second, it also makes the page content not cache or be cached with the frontend user group ID as part of the cache identity.
Refactor to XHR request and make whichever endpoint it uses, a USER_INT or manually disabled cache context, then load the data. Or set the actual data in the cookie, then use JS to insert the values where needed.
Hopefully that clarifies the different contexts and why they can't communicate in the direction you're attempting; even if they had been exchanging strings instead of arrays.
See also: .cache sub-object in TypoScript which is where you would be able to craft a unique cache identifier for use case 2 described above.
USER_INT are not Cached, so the values for this are replaced after the cache is build up.
I think f:cObject is the wrong way. Implement an own ViewHelper to get the same data should be an better way.
<?php
namespace My\Plugin\ViewHelpers;
use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface;
use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper;
use TYPO3Fluid\Fluid\Core\ViewHelper\Traits\CompileWithRenderStatic;
class RandomViewHelper extends AbstractViewHelper
{
use CompileWithRenderStatic;
/**
* #var boolean
*/
protected $escapeOutput = false;
/**
* #param array $arguments
* #param \Closure $renderChildrenClosure
* #param RenderingContextInterface $renderingContext
* #return string
*/
public static function renderStatic(
array $arguments,
\Closure $renderChildrenClosure,
RenderingContextInterface $renderingContext
) {
return rand();
}
}
Now you can use it like following:
{my:random()} or <my:random />

Implementation of Event-Sourcing / CQRS approach in api-platform

on official Api-Platform website there is a General Design Considerations page.
Last but not least, to create Event Sourcing-based systems, a convenient approach is:
to persist data in an event store using a custom data persister
to create projections in standard RDBMS (Postgres, MariaDB...) tables or views
to map those projections with read-only Doctrine entity classes and to mark those classes with #ApiResource
You can then benefit from the built-in Doctrine filters, sorting, pagination, auto-joins, etc provided by API Platform.
So, I tried to implement this approach with one simplification (one DB is used, but with separated reads and writes).
But failed... there is a problem, which I don't know how to resolve, so kindly asking you for a help!
I created a User Doctrine entity and annotated fields I want to expose with #Serializer\Groups({"Read"}). I will omit it here as it's very generic.
User resource in yaml format for api-platform:
# config/api_platform/entities/user.yaml
App\Entity\User\User:
attributes:
normalization_context:
groups: ["Read"]
itemOperations:
get: ~
collectionOperations:
get:
access_control: "is_granted('ROLE_ADMIN')"
So, as it's shown above User Doctrine entity is read-only, as only GET methods are defined.
Then I created a CreateUser DTO:
# src/Dto/User/CreateUser.php
namespace App\Dto\User;
use App\Validator as AppAssert;
use Symfony\Component\Validator\Constraints as Assert;
final class CreateUser
{
/**
* #var string
* #Assert\NotBlank()
* #Assert\Email()
* #AppAssert\FakeEmailChecker()
*/
public $email;
/**
* #var string
* #Assert\NotBlank()
* #AppAssert\PlainPassword()
*/
public $plainPassword;
}
CreateUser resource in yaml format for api-platform:
# config/api_platform/dtos/create_user.yaml
App\Dto\User\CreateUser:
itemOperations: {}
collectionOperations:
post:
access_control: "is_anonymous()"
path: "/users"
swagger_context:
tags: ["User"]
summary: "Create new User resource"
So, here you can see that only one POST method is defined, exactly for creation of a new User.
And here what router shows:
$ bin/console debug:router
---------------------------------- -------- -------- ------ -----------------------
Name Method Scheme Host Path
---------------------------------- -------- -------- ------ -----------------------
api_create_users_post_collection POST ANY ANY /users
api_users_get_collection GET ANY ANY /users.{_format}
api_users_get_item GET ANY ANY /users/{id}.{_format}
I also added a custom DataPersister to handle POST to /users. In CreateUserDataPersister::persist I used Doctrine entity to write data, but for this case it doesn't matter as Api-platform do not know anything about how DataPersister will write it.
So, from the concept - it's a separation of reads and writes.
Reads are performed by Doctrine's DataProvider shipped with Api-platform, and writes are performed by custom DataPersister.
# src/DataPersister/CreateUserDataPersister.php
namespace App\DataPersister;
use ApiPlatform\Core\DataPersister\DataPersisterInterface;
use App\Dto\User\CreateUser;
use App\Entity\User\User;
use Doctrine\ORM\EntityManagerInterface;
class CreateUserDataPersister implements DataPersisterInterface
{
private $manager;
public function __construct(EntityManagerInterface $manager)
{
$this->manager = $manager;
}
public function supports($data): bool
{
return $data instanceof CreateUser;
}
public function persist($data)
{
$user = new User();
$user
->setEmail($data->email)
->setPlainPassword($data->plainPassword);
$this->manager->persist($user);
$this->flush();
return $user;
}
public function remove($data)
{
}
}
When I perform a request to create new User:
POST https://{{host}}/users
Content-Type: application/json
{
"email": "test#custom.domain",
"plainPassword": "123qweQWE"
}
Problem!
I'm getting a 400 response ... "hydra:description": "No item route associated with the type "App\Dto\User\CreateUser"." ...
However, a new record is added to database, so custom DataPersister works ;)
According to General Design Considerations separations of writes and reads are implemented, but not working as expected.
I'm pretty sure, that I could be missing something to configure or implement. So, that's why it's not working.
Would be happy to get any help!
Update 1:
The problem is in \ApiPlatform\Core\Bridge\Symfony\Routing\RouteNameResolver::getRouteName(). At lines 48-59 it iterates through all routes trying to find appropriate route for:
$operationType = 'item'
$resourceClass = 'App\Dto\User\CreateUser'
But $operationType = 'item' is defined only for $resourceClass = 'App\Entity\User\User', so it fails to find the route and throws an exception.
Update 2:
So, the question could sound like this:
How it's possible to implement separation of reads and writes (CQS?) using Doctrine entity for reads and DTO for writes, both residing on the same route, but with different methods?
Update 3:
Data Persisters
store data to other persistence layers (ElasticSearch, MongoDB, external web services...)
not publicly expose the internal model mapped with the database through the API
use a separate model for read operations and for updates by implementing patterns such as CQRS
Yes! I want that... but how to achieve it in my example?
Short Answer
The problem is that the Dto\User\CreateUser object is getting serialized for the response, when in fact, you actually want the Entity\User to be returned and serialized.
Long Answer
When API Platform serializes a resource, they will generate an IRI for the resource. The IRI generation is where the code is puking. The default IRI generator uses the Symfony Router to actually build the route based on the API routes created by API Platform.
So for generating an IRI on an entity, it will need to have a GET item operation defined because that is the route that will be the IRI for the resource.
In your case, the DTO doesn't have a GET item operation (and shouldn't have one), but when API Platform tries to serialize your DTO, it throws that error.
Steps to Fix
From your code sample, it looks like the User is being returned, however, it's clear from the error that the User entity is not the one being serialized.
One thing to do would be to install the debug-pack, start the dump server with bin/console server:dump, and add a few dump statements in the API Platform WriteListener: ApiPlatform\Core\EventListener\WriteListener near line 53:
dump(["Controller Result: ", $controllerResult]);
$persistResult = $this->dataPersister->persist($controllerResult);
dump(["Persist Result: ", $persistResult]);
The Controller Result should be an instance of your DTO, the Persist Result should be an instance of your User entity, but I'm guessing it's returning your DTO.
If it is returning your DTO, you need to just debug and figure out why the DTO is being returned from the dataPersister->persist instead of the User entity. Maybe you have other data persisters or things in your system that can be causing conflict.
Hopefully this helps!
You need to send the "id" in your answer.
If User is Doctrine entity, use:
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
If User isn't Doctrine entity, use:
/**
* #Assert\Type(type="integer")
* #ApiProperty(identifier=true)
*/
private $id;
Anyway, your answer would be like this:
{
"id": 1, // Your unique id of User
"email": "test#custom.domain",
"plainPassword": "123qweQWE"
}
P.S.: sorry for my english :)
Only Work in the 2.4 version but really helpful.
Just add
output_class=false for the CreateUserDTO and everything will be fine for POST|PUT|PATCH
output_class to false allow you to bypass the get item operation. You can see that in the ApiPlatform\Core\EventListener#L68.

CodeIgniter Form Validation callback to model from config rules?

My User_model have a public method called is_unique_email($email). This method checks if a user has a uniqe mail adress with some status flag checks. This is also the reason why I can't use the standard is_unique validation rule from CodeIgniter.
I'm using a form_validation.php with config array for my validation rule groups. My question is: How can I call the model method for checking the new user's e-mail address? I searched and tried so many things, but nothing work. My preferred call would be with | pipe separator.
Like: trim|required|max_length[70]|valid_email|<~ here comes the model callback ~>
Is there any solution for this callback or is there no way and I have to extend the Form_validation system library?
I'm using CodeIgniter 3.1.7.
Thanks in advance!
UPDATE:
Because I've always done things via extending the form_validation library I forgot about this:
https://codeigniter.com/user_guide/libraries/form_validation.html#callbacks-your-own-validation-methods
and this (anonymous functions):
https://codeigniter.com/user_guide/libraries/form_validation.html#callable-use-anything-as-a-rule
Might be better for you. When in doubt, always read the docs ;)
Yes you can extend the form_validation library. In application/library make a MY_Form_validation.php and have it extend CI's as such:
class MY_Form_validation extends CI_Form_validation {
then in it you can do something like this:
/**
* Checks to see if bot sum is valid
* e.g. equals the session stored values
*
* #param int $sum
* #return boolean
*/
public function valid_bot_sum($sum) {
$generated = $this->CI->session->bot_first_number + $this->CI->session->bot_second_number;
if ($generated !== intval($sum)) {
$this->set_message('valid_bot_sum', 'Invalid bot sum.');
return false;
} else {
return true;
}
}
and now your function can be accessed via pipe separators as any native form_validation validation function. Just be sure to set the message on false as I've done, otherwise you will get an error. You can access the CI instance like so $this->CI.
In your case you can either migrate the function from the model into this file, or you can call it by loading the model in the function and calling the function and just testing to see if it evaluates to true/false and handling as above.

Sails.js: Sending a 409 response if a duplicate record is posted

Not sure how to do this in sails.js, but I'd like to be able to, when creating a new object on the API, check to see if that object's id exists and if it does, send a 409 conflict response, and if it doesn't, create the object like normal.
For the sake of discussion, I've created a Brand model.
I'm assuming that I would override the create function in the BrandController, search for the brand based on req.param('id') and if it exists, send the error response. But I'm not sure if I'm doing this correctly, as I can't seem to get anything to work.
Anyone have ideas?
I ended up using a policy for this particular use case.
Under config/policies, I created a isRecordUnique policy:
/**
* recordIsUnique
*
* #module :: Policy
* #description :: Simple policy to check that a record is unique
*
* #docs :: http://sailsjs.org/#!documentation/policies
*
*/
module.exports = function(req, res, next) {
Brand.findOne({ id: req.body.id}).done(function (err, brand) {
if (brand) {
res.send(409);
} else {
next();
}
});
};
This allowed me to avoid overriding any CRUD functions and it seemed to fit the definition of a policy, in that only checks one thing.
To tie my policy to my create function, I modified config/policies by adding:
BrandController: {
create: 'isRecordUnique'
}
That's it. Took me way too long to figure this out, but I think it's a good approach.
Well since this is MVC you are thinking correctly that the Control should be enforcing this logic. However, as this is basic uniqueness by the primary id the model should know/understand and help enforce this.
Model should identity the conflict.
In sails the coder is responsible for the defining uniqueness, but I would have the model object do it not the controller.
The controller should route/respond by sending the view which is effectively http 409.
Yes the controller create method should be used in this case, as sails wants to provide CRUD routes for you. Assuming it is a logical create not some resultant or odd non-restful side effect.
I think of Sails.js by default providing a model controller, so use their perspective since you are using their framework. There are many approaches to Control/Model relationships.
res.view([view, options[, fn]])
Ideally the view would control the http response code, the message, any special additional headers. The view just happens to be extremely basic, but could vary in the future.
You could always set headers and response with JSON from the controller but views offer you flexibility in the future, like decoupling, the reason the MVC pattern exists. However, sails also seems to value convenience, so if it is a small app maybe directly from the controller.

Recess Framework GET send sentence

The following is a Recess Framework code in which I am facing problem.
/** !Route GET, /abc/$text */
function xyz($text) {
If $text is a sentence. How do we get a sentence through HTTP GET. I am able to send only single word right now unlike normal URL GET where I can type + to add more words.
IN RECESS its a REST API call and GET is used to get a response for a query.
mostly we pass an ID through the URL which is the primary key of the db.