Interrupt FuelPHP REST controller flow and display response - fuelphp

I am using FuelPHP's rest controller.
I am trying to break the flow and display my response after encountering an error.
Here is my basic flow needed:
When any of the methods are called I run a "validate" function, which validates parameters and other business logic.
If the "validate" function determines something is off, I want to stop the entire script and display the errors I have complied so far.
I have tried the following in my "validate" function but it simply exits the validate function... then continues to the initial method being requested. How do I stop the script immediately and display the contents of this response?
return $this->response( array(
'error_count' => 2,
'error' => $this->data['errors'] //an array of error messages/codes
) );

That is very bad practice. If you exit you not only abort the current controller, but also the rest of the framework flow.
Just validate in the action:
// do your validation, set a response and return if it failed
if ( ! $valid)
{
$this->response( array(
'error_count' => 2,
'error' => $this->data['errors'] //an array of error messages/codes
), 400); //400 is an HTTP status code
return;
}
Or if you want to do central validation (as opposed to in the controller action), use the router() method:
public function router($resource, $arguments)
{
if ($this->valid_request($resource))
{
return parent::router($resource, $arguments);
}
}
protected function valid_request($resource)
{
// do your validation here, $resource tells you what was called
// set $this->response like above if validation failed, and return false
// if valid, return true
}

I am new to FuelPHP so if this method is bad practice, let me know.
If you want your REST controller to break the flow at some other point than when the requested method returns something, use this code. You can change the $this->response array to return whatever you want. The main part of the script is the $this->response->send() method and exit method.
$this->response( array(
'error_count' => 2,
'error' => $this->data['errors'] //an array of error messages/codes
), 400); //400 is an HTTP status code
//The send method sends the response body to the output buffer (i.e. it is echo'd out).
//pass it TRUE to send any defined HTTP headers before sending the response body.
$this->response->send(true);
//kill the entire script so nothing is processed past this point.
exit;
For more information on the send method, check out the FuelPHP documentation for the response class.

Related

check if api endpoint exits before send request - axios

I have axios request in my vue application:
async get_banner(id:number) : Promise<any> {
return global.axios.get(`${process.env.VUE_APP_DOMAIN}/banners/${id}`)
}
it works while banner/${id} response exits, but I have situation when I should disable banner in my admin panel so api endpoint becomes empty. (not exits) so I am getting Request failed with status code 404 in my vue app console.
question is how to prevent error and know if url exits or not? what is best practice to do this?
You can't tell whether an API exists or not without trying it (or relying on another API to get status of the former API)
It's usually just a manner of handling the response properly. Usually this would look something like this...
getTheBanner(id){
this.errorMessage = null; // reset message on request init
get_banner(id)
.then(r => {
// handle success
this.results = r;
})
.catch(e => {
// handle error
if (e.status === 404){
// set error message
this.errorMessage = "Invalid banner Id";
}
})
}
then in your template you could have something like this
<div v-if="errorMessage" class="alert danger">{errorMessage}</div>
Explaination:
Yes, you're absolutely right. This is the default behavior of strapi. Whenever the response is empty it throws a 404 error. This is basically because the findOne method in service returns null to the controller and when the controller sends this to the boom module it returns a 404 Not Found error to the front end.
Solution:
Just override the find one method in the controller to return an empty object {} when the response is null.
Implementation
// Path - yourproject/api/banner/controllers/banner.js
const { sanitizeEntity } = require('strapi-utils');
module.exports = {
/**
* Retrieve a record.
*
* #return {Object}
*/
async findOne(ctx) {
const { id } = ctx.params;
const entity = await strapi.services.restaurant.findOne({ id });
// in case no entity is found, just return emtpy object here.
if(!entity) return {};
return sanitizeEntity(entity, { model: strapi.models.restaurant });
},
};
Side Note:
There's no need to make any changes to the browser side axios implementation. You should always handle such cases in controller rather the client side.
Reference:
Backend Customizations

Workbox - NetworkOnly executes even if I return false

Using Workbox, I am trying to deal with graphql.
Anyone know why I am getting this error message from NetworkOnly? I am returning false in the route matcher, so I don't know why new NetworkOnly() is even executing. This only happens in offline mode (no network available).
The strategy could not generate a response for ... The underlying error is TypeError: Failed to fetch.
at NetworkOnly._handle (webpack://service-workers/./node_modules/workbox-strategies/NetworkOnly.js?:101:19)
at async NetworkOnly._getResponse (webpack://service-workers/./node_modules/workbox-strategies/Strategy.js?:155:24)
const bgSyncPlugin = new BackgroundSyncPlugin('myQueueName', {
maxRetentionTime: 24 * 60
});
const isGraphqlSubmitForm = async(event) => {
const clonedRequest = event.request.clone();
const body = await clonedRequest.json();
if (body?.operationName === 'submitForm') {
return true;
}
return false; // I MADE SURE THIS IS BEING RETURNED
};
registerRoute(
isGraphqlSubmitForm,
new NetworkOnly({
plugins: [bgSyncPlugin]
}),
'POST'
);
Sorry about that—you're seeing that behavior because the truthiness of the return value from this function is used to determine whether a route matches, and an async function always returns a Promise, which is a "truthy" value.
There's a log message that warns about this if you're in Workbox's development mode.
If it's possible for you to add in a header on the requests you want to match, that would probably be the easiest way to synchronously trigger your route, since any attempt to access a request body needs to be async. Failing that, the cleanest approach would be to just match all of your GraphQL requests with a given route, and then inside of that route's handler, use whatever async logic you need to trigger different strategies for different types of traffic.

Get the output of RestSetResponse without making HTTP request

I have a minimal (example) REST end-point test/people.cfc:
component
restpath = "test/people/"
rest = true
{
remote void function create(
required string first_name restargsource = "Form",
required string last_name restargsource = "Form"
)
httpmethod = "POST"
restpath = ""
produces = "application/json"
{
// Simulate adding person to database.
ArrayAppend(
Application.people,
{ "first_name" = first_name, "last_name" = last_name }
);
// Simulate getting people from database.
var people = Application.people;
restSetResponse( {
"status" = 201,
"content" = SerializeJSON( people )
} );
}
}
As noted here and in the ColdFusion documentation:
Note: ColdFusion ignores the function's return value and uses the response set using the RestSetResponse() function.
So the void return type for the function appears to be correct for the REST function.
Now, I know I can call it from a CFM page using:
httpService = new http(method = "POST", url = "https://localhost/rest/test/people");
httpService.addParam( name = "first_name", type = "formfield", value = "Alice" );
httpService.addParam( name = "last_name", type = "formfield", value = "Adams" );
result = httpService.send().getPrefix();
However, I would like to call the function without making a HTTP request.
Firstly, the REST CFCs do not appear to be accessible from within the REST directory. This can be solved simply by creating a mapping in the ColdFusion admin panel to the root path of the REST service.
I can then do:
<cfscript>
Application.people = [];
people = new restmapping.test.People();
people.create( "Alice", "Adams" );
WriteDump( application.people );
</cfscript>
This calls the function directly and the output shows it has added the person. However, the response from the REST function has disappeared into the aether. Does anyone know if it is possible to retrieve the response's HTTP status code and content (as a minimum - preferably all the HTTP headers)?
Update - Integration Testing Scenario:
This is one use-case (of several) where calling the REST end-point via a HTTP request has knock-on effects that can be mitigated by invoking the end-point directly as a method of a component.
<cfscript>
// Create an instance of the REST end-point component without
// calling it via HTTP request.
endPoint = new restfiles.test.TestRESTEndPoint();
transaction {
try {
// Call a method on the end-point without making a HTTP request.
endPoint.addValueToDatabase( 1, 'abcd' );
assert( getRESTStatusCode(), 201 );
assert( getRESTResponseText(), '{"id":1,"value":"abcd"}' );
// Call another method on the end-point without making a HTTP request.
endPoint.updateValueInDatabase( 1, 'dcba' );
assert( getRESTStatusCode(), 200 );
assert( getRESTResponseText(), '{"id":1,"value":"dcba"}' );
// Call a third method on the end-point without making a HTTP request.
endPoint.deleteValueInDatabase( 1 );
assert( getRESTStatusCode(), 204 );
assert( getRESTResponseText(), '' );
}
catch ( any e )
{
WriteDump( e );
}
finally
{
transaction action="rollback";
}
}
</cfscript>
Calling each REST function via a HTTP request will commit the data to the database after each request - cleaning up between tests where the data has been committed can get very complicated and often results in needing to flashback the database to a previous state (resulting in integration tests being unable to be run in parallel with any other tests and periods of unavailability during flashbacks). Being able to call the REST end-points without making lots of atomic HTTP requests and instead bundle them into a single transaction which can be rolled back means the testing can be performed in a single user's session.
So, how can I get the HTTP status code and response text which have been set by RestSetResponse() when I create an instance of the REST component and invoke the function representing the REST path directly (without using a HTTP request)?
#MT0,
The solution will* involve a few steps:
Change remote void function create to remote struct function create
Add var result = {"status" = 201, "content" = SerializeJSON( people )}
Change your restSetResponse(..) call to restSetResponse(result)
Add return result;
* The solution will not currently work, b/c ColdFusion ticket CF-3546046 was not fixed completely. I've asked Adobe to re-open it and also filed CF-4198298 to get this issue fixed, just in case CF-3546046 isn't re-opened. Please see my most recent comment on CF-3546046, and feel free to vote for either ticket. Once either is fixed completely, then the above-listed changes to your code will allow it to set the correct HTTP response when called via REST and to return the function's return variable when invoked directly. Note: you could also specify a headers struct w/in the result struct in step 2, if you also want to return headers when the function is invoked directly.
Thanks!,
-Aaron Neff

ZF2 JSON-RPC - Error response, data parameter, how to access?

Let's say I have an RPC call for adding colors. A user can only add colors once. If they add one a second time, I want to return an error response that tells them they screwed up, and why.
The JSON-RPC error response describes an error object that includes a space for a data parameter. It is in here that it seems appropriate to include my error code for "color already added". However, I cannot figure out how to return this in the response.
$jsonRpc = new Server();
$jsonRpc->setClass(new Testi());
$jsonRpc->getRequest()->setVersion(Server::VERSION_2);
if ($_SERVER['REQUEST_METHOD'] == 'GET') {
echo $jsonRpc->getServiceMap()->setEnvelope(Smd::ENV_JSONRPC_2);
}
else {
$jsonRpc->handle();
}
class Testi {
const ERROR_CODE_COLOR_EXISTS = 5;
private $colors = ['red', 'green'];
/**
* #param $color
* #return array
*/
public function addColor($color) {
if (in_array($color, $this->colors)) {
throw new \Exception('Color exists');
}
else {
$this->colors[] = $color;
}
return $this->colors;
}
}
This works, to the degree that an error response is returned, but gives me no option to include my error code (self::ERROR_CODE_COLOR_EXISTS).
{"error":{"code":-32000,"message":"Color exists","data":{}},"id":"","jsonrpc":"2.0"}
How do I put info into that DATA parameter!?
Thanks,
Adam
Turns out you have two choices to do this:
1) Add parameters to an exception:
$e = new Exception('I pooped my pants');
$e->color = 'brown';
$->smell = 'bad';
color and smell would then be in the data parameter of the error response.
2) Pass the server (in my code, $jsonRpc) into the object (in my code, it would look something like: new Testi($jsonRpc)), and use the fault(...) method, which allows you to pass the data array/object into it.
The latter approach gives you more flexibility as you can do data->code, data->message, neither of which can you set on the $e object, because they are existing, protected parameters. But you are then coupling your model to the $jsonRpc server, which is not good.
All that being said, it's not the correct way to respond to the scenario I outlined above. The error response is more or less reserved for true, unrecoverable server errors, akin to real exceptions, not user validation errors. In my case it was better to define a response type that allows me to return success/fail values with appropriate response codes. {success: false, code: 5}.
Cheers,
Adam

How to skip controller actions in case of authorization is not succeed in ZF2

I am implementing REST API on ZF2. Now I need to check authorization token on Module.php and return with error if authorization failed. But I did not know how to return response from Module.php.
I wrote code to check authorization in DISPATCH Event of onBootstrap. Now how to return error from Module.php without accessing controllers if authorization failed. As only exit function/call make possible to return without accessing controller. But In that case I did not getting any response. Using json_encode(array) is not look like a standard as I am already enabled ViewJsonStrategy and using JsonModel in controllers.
You can shortcircuit the event by having your listener return a response, eg...
public function onBootstrap(EventInterface $e)
{
$eventManager = $e->getApplication()->getEventManager();
// attach dispatch listener
$eventManager->attach('dispatch', function($e) {
// do your auth checks...
if (!$allowed) {
// get response from event
$response = $e->getResponse();
// set status 403 (forbidden)
$response->setStatusCode(403);
// shortcircuit by returning response
return $response;
}
});
}