From 38460cbd4020feebb67754eb03751e6342020b00 Mon Sep 17 00:00:00 2001 From: Tomas Cohen Arazi Date: Thu, 13 Feb 2025 13:02:00 +0000 Subject: [PATCH] Bug 35246: Make Koha::Biblio->to_api throw relevant exception if no biblioitem row This patch makes the `to_api()` method return an proper exception when the `$self->biblioitem` accessor returns `undef`. This is a bad data scenario and we could do better in terms of logging the situation. The new exception will be correctly rendered in the logs by the `unhandled_exception` Mojolicious helper and no behavior change on the API. I wasn't sure to keep the api tests I wrote. I originally added handling for the specific exception in the controller, but it become obvious it was not worth: too much code, the `GET /biblios` endpoint would need the same treatment, etc. And the current `500 Internal server error` is good enough as a response, and the logs explain the situation correctly. To test: 1. On a fresh `KTD` pick a biblio (say biblionumber=1) 2. Use your favourite REST tool (Postman?) to access the record with `Accept: application/json`. i.e. ``` GET kohadev.localhost/api/v1/biblios/1 Accept: application/json ``` => SUCCESS: All good 3. Delete the related `biblioitems` row: $ ktd --shell k$ koha-mysql kohadev > DELETE FROM biblioitems WHERE biblionumber=1; \q 4. Repeat 2 => SUCCESS: You get a 500 error 5. Check the logs => FAIL: You get a cryptic message about accessing a method on an undefined object: ``` 2025/02/13 13:15:00] [ERROR] GET /api/v1/biblios: unhandled exception (Mojo::Exception)<> ``` 6. Apply this patch 7. Restart things: k$ restart_all 8. Repeat 2 => SUCCESS: You get a 500 error 9. Check the logs => SUCCESS: You get a more meaningful exception: ``` [2025/02/13 13:20:00] [ERROR] GET /api/v1/biblios/1: unhandled exception (Koha::Exceptions::RelatedObjectNotFound)< biblioitem, class => Koha::Biblioitem>> ``` 10. Run the tests: k$ prove t/db_dependent/Koha/Biblio.t \ t/db_dependent/api/v1/biblios.t => SUCCESS: They pass! 11. Sign off :-D Signed-off-by: Marcel de Rooy Signed-off-by: Nick Clemens Signed-off-by: Katrin Fischer (cherry picked from commit 03ae61e9f8126e3a030cfc20ea24ea2394f730ae) Signed-off-by: Baptiste Wojtkowski --- Koha/Biblio.pm | 7 ++++++- Koha/Exceptions.pm | 43 ++++++++++++++++++++++++------------------- 2 files changed, 30 insertions(+), 20 deletions(-) diff --git a/Koha/Biblio.pm b/Koha/Biblio.pm index d6a46fb185..23841897bc 100644 --- a/Koha/Biblio.pm +++ b/Koha/Biblio.pm @@ -1706,7 +1706,12 @@ sub to_api { $args = defined $args ? {%$args} : {}; delete $args->{embed}; - my $json_biblioitem = $self->biblioitem->to_api( $args ); + my $biblioitem = $self->biblioitem; + + Koha::Exceptions::RelatedObjectNotFound->throw( accessor => 'biblioitem', class => 'Koha::Biblioitem' ) + unless $biblioitem; + + my $json_biblioitem = $biblioitem->to_api($args); return unless $json_biblioitem; return { %$json_biblioitem, %$json_biblio }; diff --git a/Koha/Exceptions.pm b/Koha/Exceptions.pm index 0d5a1ac1ed..d308f19229 100644 --- a/Koha/Exceptions.pm +++ b/Koha/Exceptions.pm @@ -7,16 +7,16 @@ use Koha::Exception; use Exception::Class ( 'Koha::Exceptions::BadParameter' => { - isa => 'Koha::Exception', + isa => 'Koha::Exception', description => 'A bad parameter was given', - fields => ['parameter'], + fields => ['parameter'], }, 'Koha::Exceptions::DuplicateObject' => { - isa => 'Koha::Exception', + isa => 'Koha::Exception', description => 'Same object already exists', }, 'Koha::Exceptions::FileNotFound' => { - isa => 'Koha::Exception', + isa => 'Koha::Exception', description => 'File not found', fields => ['filename'], }, @@ -26,57 +26,62 @@ use Exception::Class ( fields => ['invalid_status'], }, 'Koha::Exceptions::ObjectNotFound' => { - isa => 'Koha::Exception', + isa => 'Koha::Exception', description => 'The required object doesn\'t exist', }, 'Koha::Exceptions::PayloadTooLarge' => { - isa => 'Koha::Exception', + isa => 'Koha::Exception', description => 'Request entity is larger than limits defined by server', }, 'Koha::Exceptions::ObjectNotCreated' => { - isa => 'Koha::Exception', + isa => 'Koha::Exception', description => 'The object have not been created', }, + 'Koha::Exceptions::RelatedObjectNotFound' => { + isa => 'Koha::Exception', + description => 'The requested related object does not exist', + fields => [ 'accessor', 'class' ], + }, 'Koha::Exceptions::CannotDeleteDefault' => { - isa => 'Koha::Exception', + isa => 'Koha::Exception', description => 'The default value cannot be deleted' }, 'Koha::Exceptions::CannotDeleteSystem' => { - isa => 'Koha::Exception', + isa => 'Koha::Exception', description => 'The system value cannot be deleted' }, 'Koha::Exceptions::MissingParameter' => { - isa => 'Koha::Exception', + isa => 'Koha::Exception', description => 'A required parameter is missing', - fields => ['parameter'], + fields => ['parameter'], }, 'Koha::Exceptions::TooManyParameters' => { - isa => 'Koha::Exception', + isa => 'Koha::Exception', description => 'Too many parameters given', }, 'Koha::Exceptions::ParameterTooHigh' => { - isa => 'Koha::Exception', + isa => 'Koha::Exception', description => 'A passed parameter value is too high' }, 'Koha::Exceptions::NoChanges' => { - isa => 'Koha::Exception', + isa => 'Koha::Exception', description => 'No changes were made', }, 'Koha::Exceptions::WrongParameter' => { - isa => 'Koha::Exception', + isa => 'Koha::Exception', description => 'Parameter has wrong value or type', - fields => ['name', 'value', 'type'], + fields => [ 'name', 'value', 'type' ], }, 'Koha::Exceptions::NoPermission' => { - isa => 'Koha::Exception', + isa => 'Koha::Exception', description => 'You do not have permission for this action', }, 'Koha::Exceptions::CannotAddLibraryLimit' => { - isa => 'Koha::Exception', + isa => 'Koha::Exception', description => 'General problem adding a library limit' }, 'Koha::Exceptions::UnderMaintenance' => { - isa => 'Koha::Exception', + isa => 'Koha::Exception', description => 'Koha is under maintenance.' }, 'Koha::Exceptions::NoInteger' => {