Nice way to handle feature rich errors #2927
Unanswered
cce-dsantos
asked this question in
Q&A
Replies: 2 comments 9 replies
-
|
for libcurl it isn't too difficult. the error enum can be copied almost as it is, and (at least in my editor) I was able to do a find and replace to get all the enum names as a faultdef as well. The enum & faultdefs used in the below examples. press to expandmodule curl;
enum CURLcode : const CInt
{
CURLE_OK = 0,
CURLE_UNSUPPORTED_PROTOCOL, /* 1 */
CURLE_FAILED_INIT, /* 2 */
CURLE_URL_MALFORMAT, /* 3 */
CURLE_NOT_BUILT_IN, /* 4 - [was obsoleted in August 2007 for
7.17.0, reused in April 2011 for 7.21.5] */
CURLE_COULDNT_RESOLVE_PROXY, /* 5 */
CURLE_COULDNT_RESOLVE_HOST, /* 6 */
CURLE_COULDNT_CONNECT, /* 7 */
CURLE_WEIRD_SERVER_REPLY, /* 8 */
CURLE_REMOTE_ACCESS_DENIED, /* 9 a service was denied by the server
due to lack of access - when login fails
this is not returned. */
CURLE_FTP_ACCEPT_FAILED, /* 10 - [was obsoleted in April 2006 for
7.15.4, reused in Dec 2011 for 7.24.0]*/
CURLE_FTP_WEIRD_PASS_REPLY, /* 11 */
CURLE_FTP_ACCEPT_TIMEOUT, /* 12 - timeout occurred accepting server
[was obsoleted in August 2007 for 7.17.0,
reused in Dec 2011 for 7.24.0]*/
CURLE_FTP_WEIRD_PASV_REPLY, /* 13 */
CURLE_FTP_WEIRD_227_FORMAT, /* 14 */
CURLE_FTP_CANT_GET_HOST, /* 15 */
CURLE_HTTP2, /* 16 - A problem in the http2 framing layer.
[was obsoleted in August 2007 for 7.17.0,
reused in July 2014 for 7.38.0] */
CURLE_FTP_COULDNT_SET_TYPE, /* 17 */
CURLE_PARTIAL_FILE, /* 18 */
CURLE_FTP_COULDNT_RETR_FILE, /* 19 */
CURLE_OBSOLETE20, /* 20 - NOT USED */
CURLE_QUOTE_ERROR, /* 21 - quote command failure */
CURLE_HTTP_RETURNED_ERROR, /* 22 */
CURLE_WRITE_ERROR, /* 23 */
CURLE_OBSOLETE24, /* 24 - NOT USED */
CURLE_UPLOAD_FAILED, /* 25 - failed upload "command" */
CURLE_READ_ERROR, /* 26 - could not open/read from file */
CURLE_OUT_OF_MEMORY, /* 27 */
CURLE_OPERATION_TIMEDOUT, /* 28 - the timeout time was reached */
CURLE_OBSOLETE29, /* 29 - NOT USED */
CURLE_FTP_PORT_FAILED, /* 30 - FTP PORT operation failed */
CURLE_FTP_COULDNT_USE_REST, /* 31 - the REST command failed */
CURLE_OBSOLETE32, /* 32 - NOT USED */
CURLE_RANGE_ERROR, /* 33 - RANGE "command" did not work */
CURLE_OBSOLETE34, /* 34 */
CURLE_SSL_CONNECT_ERROR, /* 35 - wrong when connecting with SSL */
CURLE_BAD_DOWNLOAD_RESUME, /* 36 - could not resume download */
CURLE_FILE_COULDNT_READ_FILE, /* 37 */
CURLE_LDAP_CANNOT_BIND, /* 38 */
CURLE_LDAP_SEARCH_FAILED, /* 39 */
CURLE_OBSOLETE40, /* 40 - NOT USED */
CURLE_OBSOLETE41, /* 41 - NOT USED starting with 7.53.0 */
CURLE_ABORTED_BY_CALLBACK, /* 42 */
CURLE_BAD_FUNCTION_ARGUMENT, /* 43 */
CURLE_OBSOLETE44, /* 44 - NOT USED */
CURLE_INTERFACE_FAILED, /* 45 - CURLOPT_INTERFACE failed */
CURLE_OBSOLETE46, /* 46 - NOT USED */
CURLE_TOO_MANY_REDIRECTS, /* 47 - catch endless re-direct loops */
CURLE_UNKNOWN_OPTION, /* 48 - User specified an unknown option */
CURLE_SETOPT_OPTION_SYNTAX, /* 49 - Malformed setopt option */
CURLE_OBSOLETE50, /* 50 - NOT USED */
CURLE_OBSOLETE51, /* 51 - NOT USED */
CURLE_GOT_NOTHING, /* 52 - when this is a specific error */
CURLE_SSL_ENGINE_NOTFOUND, /* 53 - SSL crypto engine not found */
CURLE_SSL_ENGINE_SETFAILED, /* 54 - can not set SSL crypto engine as
default */
CURLE_SEND_ERROR, /* 55 - failed sending network data */
CURLE_RECV_ERROR, /* 56 - failure in receiving network data */
CURLE_OBSOLETE57, /* 57 - NOT IN USE */
CURLE_SSL_CERTPROBLEM, /* 58 - problem with the local certificate */
CURLE_SSL_CIPHER, /* 59 - could not use specified cipher */
CURLE_PEER_FAILED_VERIFICATION, /* 60 - peer's certificate or fingerprint
was not verified fine */
CURLE_BAD_CONTENT_ENCODING, /* 61 - Unrecognized/bad encoding */
CURLE_OBSOLETE62, /* 62 - NOT IN USE since 7.82.0 */
CURLE_FILESIZE_EXCEEDED, /* 63 - Maximum file size exceeded */
CURLE_USE_SSL_FAILED, /* 64 - Requested FTP SSL level failed */
CURLE_SEND_FAIL_REWIND, /* 65 - Sending the data requires a rewind
that failed */
CURLE_SSL_ENGINE_INITFAILED, /* 66 - failed to initialise ENGINE */
CURLE_LOGIN_DENIED, /* 67 - user, password or similar was not
accepted and we failed to login */
CURLE_TFTP_NOTFOUND, /* 68 - file not found on server */
CURLE_TFTP_PERM, /* 69 - permission problem on server */
CURLE_REMOTE_DISK_FULL, /* 70 - out of disk space on server */
CURLE_TFTP_ILLEGAL, /* 71 - Illegal TFTP operation */
CURLE_TFTP_UNKNOWNID, /* 72 - Unknown transfer ID */
CURLE_REMOTE_FILE_EXISTS, /* 73 - File already exists */
CURLE_TFTP_NOSUCHUSER, /* 74 - No such user */
CURLE_OBSOLETE75, /* 75 - NOT IN USE since 7.82.0 */
CURLE_OBSOLETE76, /* 76 - NOT IN USE since 7.82.0 */
CURLE_SSL_CACERT_BADFILE, /* 77 - could not load CACERT file, missing
or wrong format */
CURLE_REMOTE_FILE_NOT_FOUND, /* 78 - remote file not found */
CURLE_SSH, /* 79 - error from the SSH layer, somewhat
generic so the error message will be of
interest when this has happened */
CURLE_SSL_SHUTDOWN_FAILED, /* 80 - Failed to shut down the SSL
connection */
CURLE_AGAIN, /* 81 - socket is not ready for send/recv,
wait till it is ready and try again (Added
in 7.18.2) */
CURLE_SSL_CRL_BADFILE, /* 82 - could not load CRL file, missing or
wrong format (Added in 7.19.0) */
CURLE_SSL_ISSUER_ERROR, /* 83 - Issuer check failed. (Added in
7.19.0) */
CURLE_FTP_PRET_FAILED, /* 84 - a PRET command failed */
CURLE_RTSP_CSEQ_ERROR, /* 85 - mismatch of RTSP CSeq numbers */
CURLE_RTSP_SESSION_ERROR, /* 86 - mismatch of RTSP Session Ids */
CURLE_FTP_BAD_FILE_LIST, /* 87 - unable to parse FTP file list */
CURLE_CHUNK_FAILED, /* 88 - chunk callback reported error */
CURLE_NO_CONNECTION_AVAILABLE, /* 89 - No connection available, the
session will be queued */
CURLE_SSL_PINNEDPUBKEYNOTMATCH, /* 90 - specified pinned public key did not
match */
CURLE_SSL_INVALIDCERTSTATUS, /* 91 - invalid certificate status */
CURLE_HTTP2_STREAM, /* 92 - stream error in HTTP/2 framing layer
*/
CURLE_RECURSIVE_API_CALL, /* 93 - an api function was called from
inside a callback */
CURLE_AUTH_ERROR, /* 94 - an authentication function returned an
error */
CURLE_HTTP3, /* 95 - An HTTP/3 layer problem */
CURLE_QUIC_CONNECT_ERROR, /* 96 - QUIC connection error */
CURLE_PROXY, /* 97 - proxy handshake error */
CURLE_SSL_CLIENTCERT, /* 98 - client-side certificate required */
CURLE_UNRECOVERABLE_POLL, /* 99 - poll/select returned fatal error */
CURLE_TOO_LARGE, /* 100 - a value/data met its maximum */
CURLE_ECH_REQUIRED, /* 101 - ECH tried but failed */
CURL_LAST /* never use! */
}
faultdef CURLE_OK,
CURLE_UNSUPPORTED_PROTOCOL,
CURLE_FAILED_INIT,
CURLE_URL_MALFORMAT,
CURLE_NOT_BUILT_IN,
CURLE_COULDNT_RESOLVE_PROXY,
CURLE_COULDNT_RESOLVE_HOST,
CURLE_COULDNT_CONNECT,
CURLE_WEIRD_SERVER_REPLY,
CURLE_REMOTE_ACCESS_DENIED,
CURLE_FTP_ACCEPT_FAILED,
CURLE_FTP_WEIRD_PASS_REPLY,
CURLE_FTP_ACCEPT_TIMEOUT,
CURLE_FTP_WEIRD_PASV_REPLY,
CURLE_FTP_WEIRD_227_FORMAT,
CURLE_FTP_CANT_GET_HOST,
CURLE_HTTP2,
CURLE_FTP_COULDNT_SET_TYPE,
CURLE_PARTIAL_FILE,
CURLE_FTP_COULDNT_RETR_FILE,
CURLE_OBSOLETE20,
CURLE_QUOTE_ERROR,
CURLE_HTTP_RETURNED_ERROR,
CURLE_WRITE_ERROR,
CURLE_OBSOLETE24,
CURLE_UPLOAD_FAILED,
CURLE_READ_ERROR,
CURLE_OUT_OF_MEMORY,
CURLE_OPERATION_TIMEDOUT,
CURLE_OBSOLETE29,
CURLE_FTP_PORT_FAILED,
CURLE_FTP_COULDNT_USE_REST,
CURLE_OBSOLETE32,
CURLE_RANGE_ERROR,
CURLE_OBSOLETE34,
CURLE_SSL_CONNECT_ERROR,
CURLE_BAD_DOWNLOAD_RESUME,
CURLE_FILE_COULDNT_READ_FILE,
CURLE_LDAP_CANNOT_BIND,
CURLE_LDAP_SEARCH_FAILED,
CURLE_OBSOLETE40,
CURLE_OBSOLETE41,
CURLE_ABORTED_BY_CALLBACK,
CURLE_BAD_FUNCTION_ARGUMENT,
CURLE_OBSOLETE44,
CURLE_INTERFACE_FAILED,
CURLE_OBSOLETE46,
CURLE_TOO_MANY_REDIRECTS,
CURLE_UNKNOWN_OPTION,
CURLE_SETOPT_OPTION_SYNTAX,
CURLE_OBSOLETE50,
CURLE_OBSOLETE51,
CURLE_GOT_NOTHING,
CURLE_SSL_ENGINE_NOTFOUND,
CURLE_SSL_ENGINE_SETFAILED,
CURLE_SEND_ERROR,
CURLE_RECV_ERROR,
CURLE_OBSOLETE57,
CURLE_SSL_CERTPROBLEM,
CURLE_SSL_CIPHER,
CURLE_PEER_FAILED_VERIFICATION,
CURLE_BAD_CONTENT_ENCODING,
CURLE_OBSOLETE62,
CURLE_FILESIZE_EXCEEDED,
CURLE_USE_SSL_FAILED,
CURLE_SEND_FAIL_REWIND,
CURLE_SSL_ENGINE_INITFAILED,
CURLE_LOGIN_DENIED,
CURLE_TFTP_NOTFOUND,
CURLE_TFTP_PERM,
CURLE_REMOTE_DISK_FULL,
CURLE_TFTP_ILLEGAL,
CURLE_TFTP_UNKNOWNID,
CURLE_REMOTE_FILE_EXISTS,
CURLE_TFTP_NOSUCHUSER,
CURLE_OBSOLETE75,
CURLE_OBSOLETE76,
CURLE_SSL_CACERT_BADFILE,
CURLE_REMOTE_FILE_NOT_FOUND,
CURLE_SSH,
CURLE_SSL_SHUTDOWN_FAILED,
CURLE_AGAIN,
CURLE_SSL_CRL_BADFILE,
CURLE_SSL_ISSUER_ERROR,
CURLE_FTP_PRET_FAILED,
CURLE_RTSP_CSEQ_ERROR,
CURLE_RTSP_SESSION_ERROR,
CURLE_FTP_BAD_FILE_LIST,
CURLE_CHUNK_FAILED,
CURLE_NO_CONNECTION_AVAILABLE,
CURLE_SSL_PINNEDPUBKEYNOTMATCH,
CURLE_SSL_INVALIDCERTSTATUS,
CURLE_HTTP2_STREAM,
CURLE_RECURSIVE_API_CALL,
CURLE_AUTH_ERROR,
CURLE_HTTP3,
CURLE_QUIC_CONNECT_ERROR,
CURLE_PROXY,
CURLE_SSL_CLIENTCERT,
CURLE_UNRECOVERABLE_POLL,
CURLE_TOO_LARGE,
CURLE_ECH_REQUIRED;module curl;
<*
Converts a CURLcode into a fault
*>
fn void? CURLcode.to_fault(self)
{
$foreach $member_name : CURLcode.names:
if (self == CURLcode.$member_name)
{
$switch CURLcode.$member_name:
$case CURLE_OK:
return; // if there was no error we return nothing
$case CURL_LAST:
unreachable("Function returned CURL_LAST error code");
$default:
// if there is an error we return the fault with the same name
return $eval($member_name)?; // this $eval here will evaluate to the faultdef with the same name as the enum value since it's in the global namespace and the enums are in the CURLcode namespace
$endswitch
}
$endforeach
}
<*
Converts a curl fault into a string
*>
fn ZString easy_error(fault err)
{
$foreach $member_name : CURLcode.names[1..^2]: // [1..^2] here loops through every error name except CURLE_OK and CURL_LAST
if (err == $eval($member_name)) // compare the passed fault to the curl fault with this error name
{
return internal_curl_bindings::curl_easy_error(CURLcode.$member_name); // if it matches then get the curl error code with this error name
}
$endforeach
unreachable("Fault was not a curl error");
}and then you can use them to make "idiomatic" C3 functions like this: module curl;
// for function that has no other return value
fn void? mime_name(CurlMimepart* part, String name) => @pool()
{
return internal_curl_bindings::curl_mime_name(part, name.zstr_tcopy()).to_fault();
}
// for a function that returns an error and also returns a value through a pointer
fn SomeType? some_example_curl_func()
{
SomeType t;
internal_curl_bindings::curl_some_example_func(&t).to_fault()!; // if the return value is not CURLE_OK we rethrow it
return t; // otherwise it was ok and we return the result
}
module main;
import curl;
fn int main()
{
if (catch err = curl::mime_name(null, "whatever"))
{
log::error("error getting mime name: %s", curl::easy_error(err));
return 1;
}
SomeType? val = curl::some_example_curl_func();
if (catch err = val)
{
log::error("error getting example value: %s", curl::easy_error(err));
return 1;
}
return 0;
} |
Beta Was this translation helpful? Give feedback.
6 replies
-
|
for libpcre2 you could probably do something like this: module pcre2;
// I've left out the definition of PCRE2ErrorCode and the corresponding faultdefs to save time.
// it won't be as simple to create the defintions as libcurl because of how they are laid out
// in https://github.com/PCRE2Project/pcre2/blob/a3def8faad44fe4e35328339d8e34261d8509a06/src/pcre2.h.in#L223-L441,
// but if your text editor supports multiple cursors or find & replace then it shouldn't be too tedious.
// I've made error_offset an [&out] param so it can't be null. while the pcre2_compile function technically
// allows it to be null, it will cause it to return without compiling the regex.
// if the compiler doesn't like it being an [&out] parameter then changing to an [&inout] parameter should work instead.
<*
@param [&out] error_offset
*>
fn PCRE2Code*? compile(String pattern, PcreOptionsBitStruct options, PCRE2CompileContext ccontext, usz* error_offset)
{
PCRE2ErrorCode error_code; // this would be an enum like `CRLcode` and have a similar .to_fault() method
PCRE2Code* val = internal_bindings::pcre2_compile(pattern.ptr, pattern.len, options, &error_code, error_offset, ccontext);
error_code.to_fault()!; // again this would rethrow the error if there is one
return val; // if there is no error it returns the value
}
fn PCRE2ErrorCode fault_to_error_code(fault err)
{
$foreach $member_name : PCRE2ErrorCode.names:
// this requires that the fault names have the exact same name as the enum names, if you have
// PCRE2ErrorCode.PCRE2_ERROR_BADDATA you must also have `faultdef PCRE2_ERROR_BADDATA` or this won't work
if (err == $eval($member_name))
{
return PCRE2ErrorCode.$member_name;
}
$endforeach
unreachable("Fault was not a pcre2 error");
}
// this is just an example get_error_message function,
// you could require passing in an error message buffer instead of allocating one on a passed allocator
fn String get_error_message(Allocator alloc, fault err)
{
PCRE2ErrorCode error_code = fault_to_error_code(err);
// this should be more than enough for all the error messages defined in
// https://github.com/PCRE2Project/pcre2/blob/a3def8faad44fe4e35328339d8e34261d8509a06/src/pcre2_error.c#L63-L311
char[512] error_buf;
int len = internal_bindings::pcre2_get_error_message(error_code, &error_buf, error_buf.len);
// none of these errors should ever happen, but if they somehow do then we want to check for it when in debug mode
if (len < 0) switch (PCRE2ErrorCode code = (PCRE2ErrorCode)len)
{
case PCRE2_ERROR_NOMEMORY:
// this isn't actually a fatal error, it just means the error message got truncated, but it still shouldn't happen.
unreachable("512 bytes was not enough for the error buffer, this shouldn't happen");
case PCRE2_ERROR_BADDATA:
unreachable("err was a PCRE2ErrorCode fault but was not a valid pcre2 error code, this is likely a problem with the bindings");
default:
unreachable("get_error_message returned unexpected error code %s", code);
}
return ((String)error_buf[:len]).copy(alloc);
}
// example usage
module main;
import pcre2;
fn int main()
{
usz pcre2_error_offset;
// c3 number regex, this should be valid unless it uses a different regex version that's incompatible with pcre2.
String number_regex = "\b[+-]?(?:0(?:[xX][0-9a-fA-F](?:_*[0-9a-fA-F])*|[oO][0-7](?:_*[0-7])*|[bB][10](?:_*[10])*)|[0-9](?:_*[0-9])*(?:_*[eE][+-]?[0-9]+)?)(?:[iIuU](?:8|16|32|64|128)?|[fF](?:32|64)?|[uU][lL])?\b";
PCRE2Code*? code = pcre2::compile(
pattern: number_regex
// empty options bitstruct. I *assume* that the pcre options are translatable into a bitstruct,
// but if they aren't then the const enum method with `options: PCRE2Options.SOME_FLAG | PCRE2Options.OTHER_FLAG`
// would work as well
options: {},
ccontext: null,
error_offset: &pcre2_error_offset,
);
if (catch err = code) @pool()
{
log::error("Error compiling regex: '%s' at offset %s (char '%c')", pcre2::get_error_message(tmem, err), pcre2_error_offset, number_regex[pcre2_error_offset]);
};
return 0;
} |
Beta Was this translation helpful? Give feedback.
3 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
-
playing around C interop I've reached two nice cases where I feel like faultdef is a bit to little to handle:
libcurl
libcurl has a very friendly way to figure out when something went wrong, several functions return a CURLcode which is a huge enum and curl_easy_strerror will get you a error message.
caveats
it (try excuse = curl_....)libpcre2
something similar happens here as well, pcre2_compile amongo others expects two int*, one for the error number and another for the error offset, which means it says what the error is and where it has happened and pcre2_get_error_message will get you a nice and charm error message based on error number.
same caveats here
any suggestion about how to handle this better?
believe a couple of /scripts would help, but it does not feel like the right path, same for maybe which would kill try/catch semantics....
tagged union candidate may be????
Beta Was this translation helpful? Give feedback.
All reactions