Commit 9e504c1f authored by Sebastian Seufert's avatar Sebastian Seufert
Browse files

Merge branch '22-map-http_reply-exceptions-to-json-reply' of...

Merge branch '22-map-http_reply-exceptions-to-json-reply' of https://gitlab.rz.uni-bamberg.de/cogsys/dare2del/demonstrator.git
parents 8469eb64 c5d74f81
:- multifile http:map_exception_to_http_status_hook/4,
prolog:message//1,
prolog:error_message//1.
:- discontiguous http:map_exception_to_http_status_hook/4,
prolog:message//1,
prolog:error_message//1.
/*******************************************
* *
* Translating exceptions to user messages *
* *
*******************************************/
/************************
* Request JSON errors *
************************/
/* unknown_key(Key) => bad request
*
* Client supplied a key-value pair for key Key, but the API
* endpoint does not know how to handle this key.
*/
http:map_exception_to_http_status_hook(unknown_key(Key),
bad_request(unknown_key(Key)), [], []).
prolog:message(unknown_key(Key)) -->
["Unknown key in request: ", Key].
/* missing_key(Key) => bad request
*
* The client did not supply a key-value pair for key Key. However,
* the API endpoint requires the key to fullfil the request.
*/
http:map_exception_to_http_status_hook(missing_key(Key),
bad_request(missing_key(Key)), [], []).
prolog:message(missing_key(Key)) -->
["Required key missing in request: ", Key].
/* wrong_type(Key, ExpectedType, Value) => bad request
*
* The client supplied a key-value pair for key Key, but the API
* endpoint does not know how to handle the associated value as
* it expected the value to be of another type.
*
* N.B.: We throw this as Bad Request. However, we cannot check the
* type of the value prior to parsing. Thus, we are comparing
* prolog term with expected prolog types. So in principle this
* exception might also be thrown due to a parsing mistake,
* i.e. a server error.
*/
http:map_exception_to_http_status_hook(wrong_type(Key, ExpectedType, Value),
bad_request(wrong_type(Key, ExpectedType, Value)), [], []).
prolog:message(wrong_type(Key, ExpectedType, Value)) -->
{ http_json:atom_json_term(ValueStr,Value,[as(string)]),
once(json_type_name(ExpectedType, JSONTypeStr)) },
["Value for key ", Key, " has wrong type.", nl,
"Expected ", JSONTypeStr, ", received: ", ValueStr].
json_type_name(Type,String) :- json_type_name(singular, Type, String).
json_type_name(singular, TextType, "a string") :-
member(TextType, [atom, codes, string, chars, text]).
json_type_name(plural,TextType, "strings") :-
member(TextType, [atom, codes, string, chars, text]).
json_type_name(singular, integer, "an integer").
json_type_name(plural, integer, "integers").
json_type_name(singular, boolean, "a boolean").
json_type_name(plural, boolean, "booleans").
json_type_name(singular, float, "a float").
json_type_name(plural, float, "floats").
json_type_name(singular, nonneg, "a nonnegative integer").
json_type_name(plural, nonneg, "nonnegative integers").
json_type_name(singular, positive_integer, "a positive integer").
json_type_name(plural, positive_integer, "positive integers").
json_type_name(singular, list, "a list").
json_type_name(plural, list, "lists").
json_type_name(singular, list(SubType), ListStr) :-
json_type_name(plural, SubType, SubTypeStr),
format(string(ListStr), 'a list of ~s', [SubTypeStr]).
json_type_name(plural, list(SubType), ListStr) :-
json_type_name(plural, SubType, SubTypeStr),
format(string(ListStr), 'lists of ~s', [SubTypeStr]).
json_type_name(Count, one_of(L), Str) :-
json_type_name_or(Count, L,0,_,Types),
format(string(Str), 'either ~s', [Types]).
json_type_name_or(Count, [Last],Before,0,LastStr) :-
!,
json_type_name(Count, Last, LastName),
( Before==0
-> LastStr = LastName
; ( Before==1
-> format(string(LastStr), ' or ~s', [LastName])
; format(string(LastStr), ', or ~s', [LastName])
)
).
json_type_name_or(Count, [E|Es],Before,After,Str) :-
succ(Before, BeforeNext),
json_type_name_or(Count, Es,BeforeNext, AfterNext,TailStr),
succ(AfterNext,After),
json_type_name(Count, E, EName),
( Before==0
-> format(string(Str), '~s~s', [EName, TailStr])
; format(string(Str), ', ~s~s', [EName, TailStr])
).
/********************
* Semantic Errors *
********************/
/* not_irrelevant(AbsPath) => bad request
*
* The API endpoints expected the item at path AbsPath to be
* irrelevant. However, the item is not. Thus, the behavior of
* the endpoint is undefined.
*/
http:map_exception_to_http_status_hook(not_irrelevant(AbsPath),
bad_request(not_irrelevant(AbsPath)), [], []).
prolog:message(not_irrelevant(AbsPath)) -->
{theory_bg:item(AbsPath, Item)}
-> item_not_relevant(Item)
; no_item(AbsPath).
no_item(AbsPath) -->
{format(string(AbsPathStr), '~w', [AbsPath])},
["Unknown path: ", AbsPathStr].
item_not_relevant(Item) -->
( { is_dict(Item, file) }
-> ["File"]
; ["Directory"]
),
[" not irrelevant :", Item.abs_path].
/******************
* Server Errors *
******************/
/* api_error(Hash, Error) => server error
* error(api_error(Hash, Error), _) => server_error
*
* Some part of the code did not run as expected. This includes a handler
* failing, which the http-server architecture considers an error.
*/
prolog:message(api_error(Hash, Error)) -->
api_error_message(Error),
[
nl, "Please consult the server logs or the webmaster for more ",
"information (IncidentID: "
],
as_text(Hash),
[")."].
% prolog:error_message is used if an error(api_error(_,_),_) is thrown!
prolog:error_message(api_error(Hash, Error)) -->
prolog:message(api_error(Hash, Error)).
% Error messages for specific API errors
api_error_message(goal_failed) --> ["An internal function failed."].
api_error_message(E) --> ["Unknown internal error: "], as_text(E).
/***************************
* *
* Intercepting Exceptions *
* *
***************************/
/* error(goal_failed(web_api:Goal),C)
*
* Thrown by http_dispatch:call_action/2 if one of our handlers fails.
* Rethrown as error(api_error(IncidentID, goal_failed),C). Additional
* information on the error is stored in the log using the same incident ID.
*
* N.B.: The SWI-Prolog documentation states "The hook is not allowed to modify
* ExceptionOut in such a way that it no longer unifies with the catching
* frame." The Exception is caught in httpd_wrapper:handler_with_output_to/5,
* where the catcher is unbound. Thus, we do not violate this constraint.
*/
user:prolog_exception_hook(error(goal_failed(web_api:Goal),Context),
error(api_error(Hash, goal_failed),Context), Frame, _) :-
Goal =.. [_PredName | Args],
last(Args, Request),
incident_id(Request, Hash),
get_prolog_backtrace(20, Backtrace, [frame(Frame), clause_references(false)]),
http_log('incident(~a, ~q, ~q).~n',[Hash, goal_failed, [goal(Goal), prolog_stack(Backtrace), original_context(Context)]]).
/***********
* Helpers *
***********/
%! incident_id(+Request, --Hash) is det
% Constructs a likely unique identifier for an api_error. Hash is bound to an
% atom of the form XXXX-XXXX-XXXX-XXXX where each X is an hex digit.
incident_id(Request, Hash) :-
get_time(TimeStamp),
random_between(0,1000000,RandInt),
variant_sha1( error_hash(TimeStamp, RandInt, Request), FullHash),
atom_chars(FullHash,HashChars),
length(HashStartChars, 16),
append(HashStartChars,_,HashChars),
format(atom(Hash), '~s~s~s~s-~s~s~s~s-~s~s~s~s-~s~s~s~s', HashStartChars).
% as_text(Content)
% DCG returning the given Content as string.
as_text(Content, [ContentStr|Rest],Rest) :-
format(string(ContentStr), '~q', [Content]).
:- module(web_api, [server_main/0]).
:- include(include/common/error_handling).
:- include(include/common/api_response).
:- include('include/irrelevance.pl').
:- include('include/explanations.pl').
......@@ -84,6 +85,13 @@ server_opts(Opts) :-
:- http_handler(root(remove), handle_remove, []).
:- http_handler(root(query), handle_query, []).
/*
* debug only
*/
:- initialization http_handler(root(test), handle_test, []).
handle_test(_Request) :-
throw(missing_key(a_key)).
/* Closures for handlers */
%! handle_irrelevant_file(++Request) is det
%
......@@ -109,9 +117,9 @@ handle_irrelevant(file,Req) :-
),
error(E,Context),
( E=existence_error(key,Key,_)
-> throw(http_reply(bad_request(missing_key(Key))))
-> throw(missing_key(Key))
; ( E=unknown_key(Key,_)
-> throw(http_reply(bad_request(unknown_key(Key))))
-> throw(unknown_key(Key))
; ( E=type_error(atom, Value)
-> ( string(Value) % should not happen as we try
% to parse atoms
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment