# mcias-client (Common Lisp)

Common Lisp client library for the [MCIAS](../../README.md) 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:

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

Then in your Lisp image:

```lisp
(ql:quickload :mcias-client)
```

## Quick Start

```lisp
(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

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

## Error Handling

All functions signal typed conditions on error:

```lisp
(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):

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

## Running Tests

```sh
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)'
```
