aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorcolin <colin@cicadas.surf>2023-11-29 18:06:43 -0800
committercolin <colin@cicadas.surf>2023-11-29 18:06:43 -0800
commit594708b0772d2482730a151ff5c079c76dec63e8 (patch)
treeabab1219606c88ebf71ccad95647c713e433d260
initial commit
-rw-r--r--terrafirma.asd9
-rw-r--r--terrafirma.lisp71
2 files changed, 80 insertions, 0 deletions
diff --git a/terrafirma.asd b/terrafirma.asd
new file mode 100644
index 0000000..0e2b6d6
--- /dev/null
+++ b/terrafirma.asd
@@ -0,0 +1,9 @@
+;;;; A-OK.asd
+
+(asdf:defsystem #:A-OK
+ :description "Describe A-OK here"
+ :author "Your Name <your.name@example.com>"
+ :license "Specify license here"
+ :version "0.0.1"
+ :serial t
+ :components ((:file "A-OK")))
diff --git a/terrafirma.lisp b/terrafirma.lisp
new file mode 100644
index 0000000..16938ee
--- /dev/null
+++ b/terrafirma.lisp
@@ -0,0 +1,71 @@
+;;;; terrafirma.lisp
+
+(defpackage #:terrafirma
+ (:use #:cl)
+ (:export
+ #:validate
+ #:defvalidator))
+
+(in-package #:terrafirma)
+
+(defvar *indentation* 0)
+
+(defun report-validation-error (err stream)
+ (with-slots (type reason suberror) err
+ (loop :repeat *indentation* :do (princ #\space stream))
+ (format stream "Error validating ~a: ~a~%" type reason)
+ (when suberror
+ (loop :repeat *indentation* :do (princ #\space stream))
+ (format stream "More specifically:~%")
+ (let ((*indentation* (+ 2 *indentation*)))
+ (report-validation-error suberror stream)))))
+
+(define-condition validation-error (error)
+ ((type
+ :initarg :type
+ :initform (error "TYPE Required")
+ :type symbol
+ :documentation "The type of the invalid object.")
+ (instance
+ :initarg :instance
+ :initform (error "INSTANCE Required")
+ :documentation "The invalid object.")
+ (reason
+ :initarg :reason
+ :initform (error "INSTANCE Required")
+ :type string
+ :documentation "A description of why the instance is invalid.")
+ (suberror
+ :initarg :suberror
+ :initform nil
+ :documentation
+ "A validation error that may have been signalled while validating a part of the structure of INSTANCE."))
+ (:report report-validation-error))
+
+
+(defvar *type*)
+(defvar *instance*)
+
+(defmacro validate (check msg &rest args)
+ (let ((suberr (gensym)))
+ `(prog1 t
+ (let (,suberr)
+ (assert (handler-case ,check (validation-error (sub) (setf ,suberr sub) nil))
+ () 'terrafirma::validation-error
+ :type terrafirma::*type*
+ :instance terrafirma::*instance*
+ :reason (format nil ,msg ,@args)
+ :suberror ,suberr)))))
+
+
+(defmacro defvalidator ((var type &key name) &body body)
+ (assert (and var (symbolp var)) (var) "VAR must be a symbol.")
+ (let ((validator-name
+ (cond ((and name (symbolp name)) name)
+ ((symbolp type) (intern (format nil "VALIDATE-~a" type)))
+ (t (error "Validator Name: Either TYPE must be a symbol or a NAME must be provided.")))))
+ `(defun ,validator-name (,var)
+ (let ((terrafirma::*type* ',type)
+ (terrafirma::*instance* ',var))
+ ,@body))))
+