How to Pass Custom Error Message from Route?

I’m setting up a route where I have to do several validation checks on the data being received. If some of the checks fail, I’d like to be able to send a ‘500’ response, but also add a more descriptive error message to display to the user.

In my template, I’m logging the response to the console, to see what gets sent:

console.log(response);

In my route, I’ve tried the following, as per suggestions in the docs:

throw new Exception('Invalid CSRF token');
return new Response('Invalid CSRF token','text/html',500);

…but neither one of them seems to allow me to access the passed string “Invalid CSRF token” on the page. Javascript’s usual response.statusText always give me ‘Internal Server Error’. And response.bodyUsed is always false, with response.body empty - I can see that in the console. I feel like I’m missing something obvious…

Hm, I set up a simple example that works as expected:

Route:

'routes' => [
	[
		'pattern' => 'testpattern',
		'action' => function() {
			return new Response('Invalid CSRF token','text/html', 500);
		}
	]

]

Script:

<script>
  let url = 'http://kit.test/testpattern';
  fetch(url)
  .then(function(response) {
    console.log(response.statusText);
    })
  .catch(function(error) {
   console.log(error);
  });  
</script>

Thank you for testing this out, @texnixe - the error was in my javascript’s then() method… :roll_eyes:

Actually, @texnixe, I think I spoke too soon. The code above is not working for me :frowning:

In your javascript code above, the catch() method will get called only if the Promise object is not fulfilled, or gets explicitly rejected - e.g., if there is a network error and we don’t get a response. Even if the response has a code of ‘500’, what gets called is the then() method. I believe the proper way to check success based on response codes should be:

<script>
let url = 'http://kit.test/testpattern';
fetch(url)
.then(function(response){
  if(response.ok){
    // this gets called if response code is in the 200 range:
    console.log(response.statusText);
  } else {
    // this will get called otherwise - it will 'reject' the response,
    // so it gets trapped by the 'catch' method below:
    throw new Error(response.statusText);
  }
})
.catch(function(error){
  console.log(error);
});  
</script>

However, the ‘response.statusText’ is a read-only string that always corresponds to the response code - i.e., “OK” if ‘200’, or “Internal Server Error” if ‘500’. So, in the example above, the ‘response.statusText’ that gets logged onto the console is always ‘Internal Server Error’.

I believe that if we want to pass a custom error code, we should be doing it in the body of the response. The strange thing is, that according to the docs on Kirby’s Response class, the first argument of the class constructor should indeed set the response body - so, in your example above, the body should be set to ‘Invalid CSRF Token’. In theory, we should probably be able to get our custom error code with something like this:

throw new Error(response.body.text);

But here is what is happening:

  • when we are not using ‘fetch’ or ajax, the response body works as expected, and gets (properly) rendered into a new HTML page
  • but if we use ‘fetch’ or ajax, the response body doesn’t seem to be found at all: response.bodyUsed always returns false, response.body is empty, and response.body.text is undefined.

So, I’m still stuck trying to find a way to return a custom error message… Do you think this is an issue with the Response class, or is there something obvious in the javascript that I’m overlooking?

After a few hours of research, trial and error, I think I may have found a workable solution. The key to this solution is knowing that when you return your Response object, if it is viewed in the browser as a page, the body of the Response is displayed. If your Response is received via ajax, however, the body will be ignored, and we will show a warning message sent as a header instead.

Our route will look like this:

'routes' => [
	[
		'pattern' => 'testpattern',
		'action' => function() {
			$body = '<h1>403: CSRF Error</h1><p>No valid CSRF token received.</p>';
			$headers = ['Warning' => 'No valid CSRF token received.'];
			return new Response($body,'text/html', 403,$headers);
		}
	]

]

If a standard, non-ajax request is made, the $body will be displayed in the browser. Now, if a request is made via ajax/fetch, our javascript can trap and display it by using our custom ‘Warning’ header, like this:

let url = 'http://kit.test/testpattern';
fetch(url)
.then(response => {
  if(response.ok){
    // this gets called if response code is in the 200 range:
    console.log(response.statusText);
  } else {
    // this will get called otherwise - it will 'reject' the response,
    // so it gets trapped by the 'catch' method below:
    throw new Error(response.status + ' - ' + response.headers.get('Warning'));
  }
})
.catch(error => {
  console.log(error);
});  

If anyone knows of better, more efficient or standards-compliant ways of doing this, please let us know!

OMG, sometimes I’m so blind, I didn’t even realize that the statusText and the message from the route were not the same :see_no_evil:.

I need new eyes for Xmas. Or a new brain.