Files
Kyle Isom 0c441f5c4f Implement Phase 9: client libraries (Go, Rust, Lisp, Python)
- clients/README.md: canonical API surface and error type reference
- clients/testdata/: shared JSON response fixtures
- clients/go/: mciasgoclient package; net/http + TLS 1.2+; sync.RWMutex
  token state; DisallowUnknownFields on all decoders; 25 tests pass
- clients/rust/: async mcias-client crate; reqwest+rustls (no OpenSSL);
  thiserror MciasError enum; Arc<RwLock> token state; 22+1 tests pass;
  cargo clippy -D warnings clean
- clients/lisp/: ASDF mcias-client; dexador HTTP, yason JSON; mcias-error
  condition hierarchy; Hunchentoot mock-dispatcher; 37 fiveam checks pass
  on SBCL 2.6.1; yason boolean normalisation in validate-token
- clients/python/: mcias_client package (Python 3.11+); httpx sync;
  py.typed; dataclasses; 32 pytest tests; mypy --strict + ruff clean
- test/mock/mockserver.go: in-memory mock server for Go client tests
- ARCHITECTURE.md §19: updated per-language notes to match implementation
- PROGRESS.md: Phase 9 marked complete
- .gitignore: exclude clients/rust/target/, python .venv, .pytest_cache,
  .fasl files
Security: token never logged or exposed in error messages in any library;
TLS enforced in all four languages; token stored under lock/mutex/RwLock
2026-03-11 16:38:32 -07:00
..

mcias-client (Common Lisp)

Common Lisp client library for the MCIAS identity and access management API.

Requirements

  • SBCL 2.x (primary), CCL (secondary)
  • Quicklisp

Installation

Place the clients/lisp/ directory on ASDF's central registry or load it via Quicklisp local-projects:

ln -s /path/to/mcias/clients/lisp ~/.quicklisp/local-projects/mcias-client

Then in your Lisp image:

(ql:quickload :mcias-client)

Quick Start

(use-package :mcias-client)

;; Connect to the MCIAS server.
(defvar *client* (make-client "https://auth.example.com"))

;; Authenticate.
(multiple-value-bind (token expires-at)
    (login *client* "alice" "s3cret")
  (format t "token expires at ~A~%" expires-at))

;; The token is stored in the client automatically.
(let ((accounts (list-accounts *client*)))
  (format t "~A accounts~%" (length accounts)))

;; Revoke the token when done.
(logout *client*)

Custom CA Certificate

(defvar *client*
  (make-client "https://auth.example.com"
               :ca-cert "/etc/mcias/ca.pem"))

Error Handling

All functions signal typed conditions on error:

(handler-case
    (login *client* "alice" "wrongpass")
  (mcias-auth-error (e)
    (format t "auth failed: ~A~%" (mcias-error-message e)))
  (mcias-forbidden-error (e)
    (format t "forbidden: ~A~%" (mcias-error-message e)))
  (mcias-not-found-error (e)
    (format t "not found: ~A~%" (mcias-error-message e)))
  (mcias-input-error (e)
    (format t "bad input: ~A~%" (mcias-error-message e)))
  (mcias-conflict-error (e)
    (format t "conflict: ~A~%" (mcias-error-message e)))
  (mcias-server-error (e)
    (format t "server error ~A: ~A~%"
            (mcias-error-status e)
            (mcias-error-message e))))

All condition types are subclasses of mcias-error, which has slots:

  • mcias-error-status — HTTP status code (integer)
  • mcias-error-message — server error message (string)

validate-token Return Value

validate-token returns a property list. The :valid key is T if the token is valid, NIL otherwise (never raises an error for an invalid token):

(let ((result (validate-token *client* some-token)))
  (if (getf result :valid)
      (format t "valid; sub=~A~%" (getf result :sub))
      (format t "invalid~%")))

Running Tests

sbcl --non-interactive \
  --eval '(require :asdf)' \
  --eval "(push #P\"$(pwd)/\" asdf:*central-registry*)" \
  --eval '(ql:quickload :mcias-client/tests :silent t)' \
  --eval '(mcias-client-tests:run-all-tests)' \
  --eval '(uiop:quit)'