198: Foreign Interface Status

by John Cowan, Lassi Kortela, and Harold Ancell

Status

This SRFI is currently in draft status. Here is an explanation of each status that a SRFI can hold. To provide input on this SRFI, please send email to srfi-198@nospamsrfi.schemers.org. To subscribe to the list, follow these instructions. You can access previous messages via the mailing list archive.

Abstract

This SRFI provides means to construct, return or signal, and extract information from Scheme interfaces with "foreign" systems such as the POSIX API, databases, and libraries.

Issues

Rationale

R6RS and R7RS-large have ambitions to become comprehensive ecosystems useful for developing real-world applications. This necessitates the development of many interfaces to "foreign" systems, ranging from direct Foreign Function Interfaces (FFIs) to low level assembler and C libraries, including APIs at that level such as POSIX, to protocols such as TCP/IP, all the way to client-server systems such as databases running in their own separate processes.

This SRFI provides a comprehensive framework for placing data about the status of an interaction with a foreign system into an object, starting with conventions to partition collections of them, such as 'errno, 'sqlstate, and 'generic-c-lib, to localize them, and to report them.

Sometimes it will make sense to simply return a status object, but an often preferred method for errors is to raise an exception, as in SRFI 170, in which procedures never return error codes nor use an analogue of the POSIX errno variable to indicate system-call-level errors.

Thus procedures can return useful values, and the programmer can assume that if a foreign interface procedure returns, it succeeded. This greatly simplifies the flow of the code from the programmer's point of view.

Specification

The universe of foreign statuses is divided into distinct collections named conventions based on their source. The conventions 'status and 'error are reserved for generic situations, such as sanity error checking before a foreign interface is invoked.

Example conventions and sub-conventions include:

'errno POSIX errnos
'sqlstate 'postgresql The PostgreSQL database
'generic-c-lib 'libsodium The libsodium cryptography library

POSIX errnos are a familiar error system with a moderate degree of structure, and were the initial inspiration for this SRFI. PostgreSQL is a representative SQL RDBMS with a richer set of SQLSTATE data that can be transmitted to the user, and its client/server organization is a useful counterpoint to using an FFI. libsodium has a typical simple UNIX™ style C API that returns 0 on success and -1 on error, with no inherent structure. The 'generic-c-lib convention is suitable for many of these libraries.

This SRFI uses property lists (plists). They are simple lists of alternating keys and values, such as:

('key1 "value1" 'key2 "value2")

Status object predicate

(foreign-status? object)    →    boolean       (procedure)

Returns #f unless object is a SRFI 198 status object.

(foreign-status? "This is not an foreign status object") ⇒ #f
(foreign-status? (make-foreign-status <argument>)) ⇒ #t

Status data getters

(foreign-status-ref foreign-status-object key)    →    value or #f      (procedure)

Returns the value in a foreign status object associated with key , or #f if key is not in the object.

(foreign-status-ref <foreign-status-object> <key not in object>) ⇒ #f
(foreign-status-ref <POSIX-foreign-error-object> 'convention) ⇒ errno

(foreign-status-keys foreign-status-object)     →     list       (procedure)

Returns all the keys in a foreign status object in a list.

(foreign-status-keys <POSIX-foreign-error-object>)
  ⇒ (convention errno number name scheme-procedure foreign-interface message args heritage)
(foreign-status-keys <PostgreSQL-wire-foreign-status-object>)
  ⇒ (convention sub-convention sqlstate category class-text condition-name scheme-procedure foreign-interface message args)
(foreign-status-keys <libsodium-foreign-status-object>)
  ⇒ (convention sub-convention scheme-procedure foreign-interface message)

(foreign-status->plist foreign-status-object)     →     plist       (procedure)

Returns all the key/value pairs in a foreign status object as a plist, the 'convention key is guaranteed to be the first one. It is an error to mutate the plist.

(foreign-status->plist <POSIX-foreign-error-object>)
  ⇒ (convention errno
     number 2
     name ENOENT
     scheme-procedure open-file
     foreign-interface open
     message "open-file called open: errno/ENOENT: No such file or directory"
     args ("not-a-valid-filename" 0 428)
     heritage . "SRFI 170")
(foreign-status->plist <PostgreSQL-wire-foreign-status-object>)
  ⇒ (convention sqlstate
     sub-convention postgresql
     sqlstate "28P01"
     category exception
     class-text "Invalid Authorization Specification"
     condition-name invalid_password
     scheme-procedure open-database-connection
     foreign-interface query-message
     message "open-database-connection: 28P01: incorrect password"
     args ("localhost" 5432 "ecb" "ecb"))
(foreign-status->plist <libsodium-foreign-status-object>)
 ⇒ (convention generic-c-lib
    sub-convention libsodium
    scheme-procedure generate-key
    foreign-interface sodium-init
    message "generate-key: ecb-generate-key could not initialize sodium library\n")

Status constructors

(make-foreign-status convention-value plist)   →   foreign-status-object    (procedure)

(raise-foreign-error convention-value plist)   →   undefined    (procedure)

These procedures are handed a value for the convention key, and a plist, and return an abstract data type object. It is an error to pass non-symbols as keys in the plist, if the plist does not have an even number of elements, or if the 'convention key/value pair is not in the object. If the plist argument is malformed, an error is signaled. Except for small memory implementations, these procedures must preserve the malformed argument for end users as the value of the 'args key.

A recommended approach to handle a malformed argument is to create a status object with either 'status or 'error as the value of the 'convention key, make the argument the value of the 'args key, and supply an informative 'message.

raise-foreign-error constructs a foreign-error-object like make-foreign-status, and raises an exception in a manner suitable for the Scheme implementation it is running on. In a R6RS or R7RS system following SRFI 35, or in a system supporting SRFI 18, raising an exception means to call raise on the object. In a system only supporting SRFI 23, raising an exception means calling error with a reason string and irritants following it.

(make-foreign-status 'status '()))
foreign-status-object
(raise-foreign-error
  errno
  '(number 2
    name ENOENT
    scheme-procedure open-file
    foreign-interface open
    message "open-file called open: errno/ENOENT: No such file or directory"
    arguments ("not-a-valid-filename" 0 428)
    heritage "SRFI 170")
  ⇒  undefined
(make-foreign-status
  generic-c-lib
  '(scheme-procedure generate-key
    foreign-interface sodium-init
    message "generate-key: ecb-generate-key could not initialize sodium library\n")
foreign-status-object
(raise-foreign-error 'error '()))
undefined

Standard conventions

Implementation

An old Chibi Scheme sample implementation of this SRFI can be found in the srfi directory of the SRFI's repository, it will be updated when the API settles down.

Acknowledgments

Thanks to Olin Shivers and all the Scheme implementers who have followed his work; this SRFI was inspired by scsh's errno-error facility. Thanks also to all the participants in the SRFI 170 and SRFI 198 mailing lists.

Copyright

Copyright © 2020 John Cowan, Lassi Kortela, and Harold Ancell

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


Editor: Arthur A. Gleckler