Api Call
http://localhost:8888/api/v1/users/100 //doesn't exist
Html Call
http://localhost:8888/admin/users/100 //doesn't exist
Obviously, I don't want the Html Call exception to return json data and I don't want the Api Call to return Html Data.
I am not exception handling in the controller. I am exception handling in my UserRepository. As such, my controllers are just returning a result from the user repository.
class Sentry2UserRepository implements UserInterface {
public function findById($id) {
try {
return Sentry::findUserById($id);
}
catch (\Cartalyst\Sentry\Users\UserNotFoundException $e) {
// Do something here
return false;
}
}
}
Question 1: What is the normal / proper way of passing an error back to controller so that it will know what to display?
Question 2: Is there a standard json API format for exceptions / errors?
Question 3: Is it good practice for a Web UI to consume an internal JsonApi? Or am I doing things the right way at the moment with my WebUi controllers Querying the same Repositories as the Api?
Try this magic in your filters.php:
App::error(function(Exception $exception, $httpCode)
{
if (Request::is('api/*')){
return Response::json( ['code' => $exception->getCode(), 'error' => $exception->getMessage()], $httpCode );
}else{
$layout = View::make('layouts.main');
$layout->content = View::make('errors.error')->with('code', $exception->getCode())->with('error', $exception->getMessage())->with('httpCode',$httpCode);
return Response::make($layout, $httpCode);
}
});
First of all, I think your approach in Sentry2UserRepository is not bad, it's ok, IMO.
Question 1: What is the normal / proper way of passing an error back
to controller so that it will know what to display?
Well, IMO, depending on the application you should determine how you should handle exceptions. You mentioned so that it will know what to display and in this case it depends on how and what information you need from an exception to take the next action after an exception occured. now if you need the error message then you may return the return $e->getMessage() so you'll exactly know what actually happened. There are many ways to do this, for example, using a single catch :
try{
// ...
}
catch( Exception $e )
{
if ($e instanceof UserNotFoundException) {
// it's an instance of UserNotFoundException, return accordingly
}
elseif ($e instanceof SomethinElseException) {
// it's an instance of SomethinElseException, return accordingly
}
}
Also, you can use different custom exception classes and may use multiple catch blocks, i.e.
class AnException extends Exception
{
public function customErrorMessage()
{
return `AnException occurred!`
}
}
class AnotherException extends Exception
{
public function customErrorMessage()
{
return `AnotherException occurred!`
}
}
Then catch using multiple catch blocks, i.e.
try
{
// ...
}
catch(AnException $e)
{
return $e->customErrorMessage();
}
catch(AnotherException $e)
{
return $e->customErrorMessage();
}
catch(Exception $e)
{
return $e->getMessage();
}
Question 2: Is there a standard json API format for exceptions / errors?
Question 3: Is it good practice for a Web UI to consume an internal JsonApi? Or am I doing things the right way at the moment with my WebUi controllers Querying the same Repositories as the Api?
Actually I don't know about such an api and you are doing right, IMO. It's because, you have this
class Sentry2UserRepository implements UserInterface {
public function findById($id) {
try {
return Sentry::findUserById($id);
}
catch (\Cartalyst\Sentry\Users\UserNotFoundException $e) {
// Do something here
return false;
}
}
}
So, it's possible to write code in controller something like this
if(findById(5)) {
// found and dump it to the view
}
else {
// show "Not Found !", false will be back only for UserNotFoundException
}
But, if you had this in your UserNotFoundException catch
return $e; // or anything else (maybe an array containing status and message)
Then It's not possible to write this simple code
if(findById(5)) {
// found and dump it to the view
}
Because, is statement will be true for $e object oe for an array, so you have to check it again, using somrthing like this
$result = findById(5);
if($result && $result->code && $result->code === 0) {
// something according to code
}
Or maybe, something like this
if($result && $result->code) {
// error happened, now determine the code
switch($result->code){
case 0:
// show message
break;
case 1:
// show message
break;
}
}
So, IMO, why, you need to show the user, what error happened in your application, why not just to states, either you got data or you didn't get it. isn't it simple ? Just KISS. This is my opinion only, that's it.
Related
I have written an async Flutter/Dart function which behaves unexpectedly in my opinion. Following code structure:
static Future<bool> verifySometing() async {
try {
await getCloudData().then((snapshot) {
if (snapshot.exists && snapshot.hasData) {
bool dataValid = await validateData(snapshot.data);
if (dataValid) {
print('Data is correct');
return true;
}
}
});
} catch (e) {
print('Error $e');
return false;
}
print('Something went wrong');
return false;
}
The expected result would be that the function awaits the cloud data, then awaits validation and returns true if the data is valid. In this case, the console would show the following and the function would return true:
Data is correct
What happens in practice is that the console shows the following output and the function first returns true and then false:
Data is correct
Something went wrong
This goes against anything I thought to know about funtions in Dart because I always assumed that once return is fired, the function is done. Any ideas how this happens?
The issue is with this line.
await getCloudData().then((snapshot) {
Here, instead of just awaiting, you have also attached a then callback. So in actuality, whatever you are returning is return value of the callback function ie., (snapshot) {}.
The callback function that you need to pass into the then takes that return true and gives it to us as the result of await.
So, if you would've put something like
var bool = await getCloudData().then((snapshot) { ..... });
Then this bool, would've been equal to true. That's it. No return from the your main function.
Change it to this,
var snapshot = await getCloudData();
if (snapshot.exists && snapshot.hasData) {
bool dataValid = await validateData(snapshot.data);
if (dataValid) {
print('Data is correct');
return true;
}
}
Hope, I was able to explain clearly.
There are a few of faults in your assumptions.
First, you've attached a then to the future returned by getCloudData(). This means that you aren't awaiting getCloudData(), but instead the additional future returned by getCloudData().then(...), and that future will return when the callback function passed to it completes. (And unless the first future throws an error, the callback will be called.)
Second, the callback function operates on its own scope. So this code is not doing what you think it's doing:
bool dataValid = await validateData(snapshot.data);
if (dataValid) {
print('Data is correct');
return true;
}
This return will affect the callback function, not the verifySomething function.
Given these, the order of operation is as follows:
The validateSomething function awaits getCloudData().then(...).
getCloudData() gets called.
getCloudData() returns, the callback passed to then is called.
(Assuming the snapshot has data) validateData is called.
(Assuming data is successfully validated) "Data is correct" gets printed and the callback function returns true.
validateSomething is notified that the awaited future is complete, so execution resumes.
"Something went wrong" gets printed and the validateSomething function returns false.
Generally speaking, these kinds of errors are common when mixing async/await and then patterns. Unless you know what you're doing, stick with either one or the other, preferably the async/await pattern. For example, a refactor of your code to eliminate the call to then is as follows:
static Future<bool> verifySometing() async {
try {
final snapshot = await getCloudData();
if (snapshot.exists && snapshot.hasData) {
bool dataValid = await validateData(snapshot.data);
if (dataValid) {
print('Data is correct');
return true;
}
}
} catch (e) {
print('Error $e');
return false;
}
print('Something went wrong');
return false;
}
Now that there isn't a pesky closure to deal with, return will return from validateSomething as expected and you don't need to deal with issues like callbacks and scope.
In my application, Need to handle exceptions in two ways:
Can be handled by Front desk
Try
{
//code
}
catch(Exception ex)
{
Exception("Contact Admin");
}
Displaying user friendly exact error message to user that can be handled by users itself.
Try
{
enter code here
}
Catch(Exception ex)
{
`code here`
}
How to achieve the 2nd way in application?
The way I have seen it previously are the following:
To provide an optional list of user messages as part of your response:
Example:
MyExtendedObject o = new MyExtendedObject();
try {
// do your processing
o.setValue(...);
return o;
} catch (Exception e) {
log.error(e);
o.addErrorMessage("The processing has been unable to complete, please try again");
// getValue() will be empty for the front-end.
return o;
}
If you are in a web application, Return a user-friendly message as part of your http error response
In my Flutter app I'd like to make multiple network calls simultaneously and then do something when they all have finished. For this I use Future.wait(), which does what I want. However when a call fails it throws an exception, which is somehow not caught in the exception handler (i.e. uncaught exception).
When I do await _fetchSomeData() separately (outside Future.wait()) the exception does get called by the exception handler as expected.
Future<bool> someMethod() async {
try {
var results = await Future.wait([
_fetchSomeData(),
_fetchSomeOtherData()
]);
//do some stuf when both have finished...
return true;
}
on Exception catch(e) {
//does not get triggered somehow...
_handleError(e);
return false;
}
}
What do I need to do to catch the exceptions while using Future.wait()?
Update:
I have narrowed down the issue. Turns out if you use another await statement in the method that is called by the Future.wait() it causes the issue. Here an example:
void _futureWaitTest() async {
try {
//await _someMethod(); //using this does not cause an uncaught exception, but the line below does
await Future.wait([ _someMethod(), ]);
}
on Exception catch(e) {
print(e);
}
}
Future<bool> _someMethod() async {
await Future.delayed(Duration(seconds: 0), () => print('wait')); //removing this prevents the uncaught exception
throw Exception('some exception');
}
So if you either remove the await line from _someMethod() or if you just call _someMethod() outside of Future.wait() will prevent the uncaught exception. This is most unfortunate of course, I need await for an http call... some bug in Dart?
I have the Uncaught Exceptions breakpoints enabled. If I turn this off the issue seems to be gone. Perhaps it's an issue with the debugger. I am using Visual Studio Code and the latest flutter.
What do I need to do to catch the exceptions while using Future.wait()?
What I found out when I used the same code as you the code inside of each procedure which is used in Future.wait() must be wrapped with try/catch and on catch must return Future.error(). Also eagerError must be set to true.
try {
await Future.wait([proc1, ...], eagerError: true);
} on catch(e) {
print('error: $e')
}
/// Proc 1
Future<void> proc1() async {
try {
final result = await func();
} on SomeException catch(e) {
return Future.error('proc 1 error: $');
}
}
I think you are a bit mislead by the Future.wait() naming. Future.wait() returns another future that will have a List of elements returned by each future when it completes with success.
Now since the Future.wait() is still a future. You can handle it in two ways:
Using await with try catch.
Using onError callback.
Tis will be something like
Future.wait([futureOne, futureTwo])
.then((listOfValues) {
print("ALL GOOD")
},
onError: (error) { print("Something is not ok") }
I have an application with both MVC and 'new' ApiController endpoints in ASP.NET Core 2.2 co-existing together.
Prior to adding the API endpoints, I have been using a global exception handler registered as middleware using app.UseExceptionHandler((x) => { ... } which would redirect to an error page.
Of course, that does not work for an API response and I would like to return an ObjectResult (negotiated) 500 result with a ProblemDetails formatted result.
The problem is, I'm not sure how to reliably determine in my 'UseExceptionHandler' lambda if I am dealing with an MVC or a API request. I could use some kind of request URL matching (eg. /api/... prefix) but I would like a more robust solution that won't come back to bite me in the future.
Rough psuedo-code version of what I'm trying to implement is:
app.UseExceptionHandler(x =>
{
x.Run(async context =>
{
// extract the exception that was thrown
var ex = context.Features.Get<IExceptionHandlerFeature>()?.Error;
try
{
// generically handle the exception regardless of what our response needs to look like by logging it
// NOTE: ExceptionHandlerMiddleware itself will log the exception
// TODO: need to find a way to see if we have run with negotiation turned on (in which case we are API not MVC!! see below extensions for clues?)
// TODO: ... could just use "/api/" prefix but that seems rubbish
if (true)
{
// return a 500 with object (in RFC 7807 form) negotiated to the right content type (eg. json)
}
else
{
// otherwise, we handle the response as a 500 error page redirect
}
}
catch (Exception exofex)
{
// NOTE: absolutely terrible if we get into here
log.Fatal($"Unhandled exception in global error handler!", exofex);
log.Fatal($"Handling exception: ", ex);
}
});
});
}
Any ideas?
Cheers!
This might be a bit different than what you expect, but you could just check if the request is an AJAX request.
You can use this extension:
public static class HttpRequestExtensions
{
public static bool IsAjaxRequest(this HttpRequest request)
{
if (request == null)
throw new ArgumentNullException(nameof(request));
if (request.Headers == null)
return false;
return request.Headers["X-Requested-With"] == "XMLHttpRequest";
}
}
And then middleware with an invoke method that looks like:
public async Task Invoke(HttpContext context)
{
if (context.Request.IsAjaxRequest())
{
try
{
await _next(context);
}
catch (Exception ex)
{
//Handle the exception
await HandleExceptionAsync(context, ex);
}
}
else
{
await _next(context);
}
}
private static Task HandleExceptionAsync(HttpContext context, Exception exception)
{
//you can do more complex logic here, but a basic example would be:
var result = JsonConvert.SerializeObject(new { error = "An unexpected error occurred." });
context.Response.ContentType = "application/json";
context.Response.StatusCode = 500;
return context.Response.WriteAsync(result);
}
see this SO answer for a more detailed version.
If you want to check whether the request is routed to ApiController, you could try IExceptionFilter to hanlde the exceptions.
public class CustomExceptionFilter : IExceptionFilter
{
public void OnException(ExceptionContext context)
{
if (IsApi(context))
{
HttpStatusCode status = HttpStatusCode.InternalServerError;
var message = context.Result;
//You can enable logging error
context.ExceptionHandled = true;
HttpResponse response = context.HttpContext.Response;
response.StatusCode = (int)status;
response.ContentType = "application/json";
context.Result = new ObjectResult(new { ErrorMsg = message });
}
else
{
}
}
private bool IsApi(ExceptionContext context)
{
var controllerActionDesc = context.ActionDescriptor as ControllerActionDescriptor;
var attribute = controllerActionDesc
.ControllerTypeInfo
.CustomAttributes
.FirstOrDefault(c => c.AttributeType == typeof(ApiControllerAttribute));
return attribute == null ? false : true;
}
}
Thanks to all of the advice from others, but I have realised after some more thought and ideas from here that my approach wasn't right in the first place - and that I should be handling most exceptions locally in the controller and responding from there.
I have basically kept my error handling middleware the same as if it was handling MVC unhandled exceptions. The client will get a 500 with a HTML response, but at that point there isn't much the client can do anyway so no harm.
Thanks for your help!
In web app development I would like a consistent way to catch and report error conditions. For example, a database update routine may detect a variety of error conditions and ideally I would like the application to capture them and report gracefully. The code below din't work because retdiag is undefined when error is thrown...
function saveData(app,e) {
var db ;
var retdiag = "";
var lock = LockService.getPublicLock();
lock.waitLock(30000);
try {
// e.parameters has all the data fields from form
// re obtain the data to be updated
db = databaseLib.getDb();
var result = db.query({table: 'serviceUser',"idx":e.parameter.id});
if (result.getSize() !== 1) {
throw ("DB error - service user " + e.parameter.id);
}
//should be one & only one
retdiag = 'Save Data Finished Ok';
}
catch (ex) {
retdiag= ex.message // undefined!
}
finally {
lock.releaseLock();
return retdiag;
}
}
Is there a good or best practice for this is GAS?
To have a full error object, with message and stacktrace you have to build one, and not just throw a string. e.g. throw new Error("DB error ...");
Now, a more "consistent" way I usually implement is to wrap all my client-side calls into a function that will treat any errors for me. e.g.
function wrapper_(f,args) {
try {
return f.apply(this,args);
} catch(err) {
;//log error here or send an email to yourself, etc
throw err.message || err; //re-throw the message, so the client-side knows an error happend
}
}
//real client side called functions get wrapped like this (just examples)
function fileSelected(file,type) { return wrapper_(fileSelected_,[file,type]); }
function loadSettings(id) { return wrapper_(loadSettings_,[id]); }
function fileSelected_(file,type) {
; //do your thing
}
function loadSettings_(id) {
; //just examples
throw new Error("DB error ...");
}