From dc906585b26adfc928c10f3f641d2f970e75eed4 Mon Sep 17 00:00:00 2001
From: David Byers <david.byers@liu.se>
Date: Thu, 23 Jul 2020 18:30:19 +0200
Subject: [PATCH] Add type and range checks to public functions.

---
 CHANGELOG       |   7 +++
 olc.el          |  41 ++++++++++++++----
 test/olctest.el | 112 ++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 152 insertions(+), 8 deletions(-)

diff --git a/CHANGELOG b/CHANGELOG
index c9ae479..95e5b58 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,5 +1,12 @@
 2020-07-23  David Byers  <david.byers@liu.se>
 
+	Fix issue #2:
+	* olc.el (olc-parse-code): Add type checks.
+	(olc-encode): Add type checks.
+	(olc-shorten): Add type checks.
+	(olc-shorten-compound): Add type and range checks.
+	(olc-recover): Add type checks.
+
 	Fix issue #3:
 	* olc.el (olc-parse-code): Save match data.
 	(olc-is-valid): Save match data.
diff --git a/olc.el b/olc.el
index 3be5a31..fa26870 100644
--- a/olc.el
+++ b/olc.el
@@ -3,7 +3,7 @@
 ;; Copyright (C) 2020 David Byers
 ;;
 ;; Author: David Byers <david.byers@liu.se>
-;; Version: 1.0.1
+;; Version: 1.0.2
 ;; Package-Requires: ((emacs "25.1"))
 ;; Keywords: extensions, lisp
 ;; URL: https://gitlab.liu.se/davby02/olc
@@ -165,10 +165,11 @@ raise, and args for the raised error.
       (expt 20 (- (floor (+ 2 (/ len 2)))))
     (/ (expt 20 -3) (expt 5 (- len 10)))))
 
-(defun olc-parse-code (code)
+(cl-defun olc-parse-code (code)
   "Parse an open location code CODE."
   (if (olc-parse-p code)
       code
+    (cl-check-type code stringp)
     (save-match-data
       (let ((pos 0)
             (pairs nil)
@@ -344,6 +345,10 @@ LEN is automatically clipped to between 2 and 15.
 
 Returns an olc-area structure. Raises olc-encode-error if the
 values cannot (legally) be encoded to the selected length."
+  (cl-check-type lat number)
+  (cl-check-type lon number)
+  (cl-check-type len integer)
+
   (setq len (max 2 (min 15 len)))
   (when (and (< len 11) (/= (% len 2) 0))
     (signal 'olc-encode-error "invalid encoding length"))
@@ -445,11 +450,12 @@ specified, then the code will be shortened by at most that many
 digits. If the code can't be shortened, the original code is
 returned. `olc-shorten-error' is raised if CODE is a padded or
 shortened code, of if LIMIT is not positive and even."
+  (cl-check-type lat number)
+  (cl-check-type lon number)
+  (cl-check-type limit (member 2 4 6 8 10 12))
+
   (let* ((parse (olc-parse-code code))
          (area (olc-decode parse)))
-    (unless (and (> limit 0) (= 0 (% limit 2)))
-      (signal 'olc-shorten-error
-              (list "limit must be even and positive" code)))
     (when (olc-is-short parse)
       (signal 'olc-shorten-error
               (list "can't shorten shortened codes" code)))
@@ -493,15 +499,29 @@ it can take some time to complete. If you can set the zoom level
 to a single number, then it will make one call only, and is much
 faster.
 "
+  (cl-check-type code stringp)
+  (cl-check-type limit (member 2 4 6 8 10 12))
+  (cl-check-type zoom (or integer listp))
+
   (save-match-data
     (let* ((area (olc-decode code))
            (zoom-lo (cond ((numberp zoom) zoom)
                           ((listp zoom) (elt zoom 0))
                           (t (signal 'args-out-of-range zoom))))
-           (zoom-hi (cond ((numberp zoom) (1+ zoom))
-                          ((listp zoom) (1+ (elt zoom 1)))
+           (zoom-hi (cond ((numberp zoom) zoom)
+                          ((listp zoom) (elt zoom 1))
                           (t (signal 'args-out-of-range zoom))))
            result)
+
+      ;; Check that zoom range is not inverted
+      (when (or (< zoom-hi zoom-lo)
+                (< zoom-hi 1) (> zoom-hi 18)
+                (< zoom-lo 1) (> zoom-lo 18))
+        (signal 'args-out-of-range zoom))
+
+      ;; Otherwise we may never hit the high limit
+      (setq zoom-hi (1+ zoom-hi))
+
       (catch 'result
         (while (< zoom-lo zoom-hi)
           (let* ((zoom (floor (+ zoom-lo zoom-hi) 2))
@@ -552,6 +572,11 @@ center of the recovered area (LATITUDE . LONGITUDE) is returned.
 
 If FORMAT is `area' (or any other value), the returned value is an
 full open location code."
+  (cl-check-type code stringp)
+  (cl-check-type lat number)
+  (cl-check-type lon number)
+  (cl-check-type format (member area latlon))
+
   (let ((parse (olc-parse-code code)))
     (if (olc-is-full parse)
         (if (eq format 'latlon)
@@ -601,7 +626,7 @@ full open location code."
 
     ;; Check types (defer check of ref)
     (cl-check-type code stringp)
-    (cl-check-type format (member latlon area nil))
+    (cl-check-type format (member latlon area))
 
     ;; Process code and check ref
     (cond ((string-match "^\\(\\S-+\\)\\s-+\\(.*\\)$" code)
diff --git a/test/olctest.el b/test/olctest.el
index 7a8e51d..0e0f4db 100644
--- a/test/olctest.el
+++ b/test/olctest.el
@@ -261,6 +261,117 @@
                    :act (olc-is-short "+12345678")
                    :msg "S3")))
 
+(defun olctest-issue-2 ()
+  (olctest-testcase "local:issue-2"
+    (olctest-assert-error (:exp (wrong-type-argument) :msg "olc-parse-code")
+      (olc-parse-code nil))
+
+    (olctest-assert-error (:exp (wrong-type-argument) :msg "olc-is-valid")
+      (olc-is-valid nil))
+
+    (olctest-assert-error (:exp (wrong-type-argument) :msg "olc-is-short")
+      (olc-is-short nil))
+
+    (olctest-assert-error (:exp (wrong-type-argument) :msg "olc-is-full")
+      (olc-is-full nil))
+
+    (olctest-assert-error (:exp (wrong-type-argument) :msg "olc-code-precision")
+      (olc-code-precision nil))
+
+    (olctest-assert-error (:exp (wrong-type-argument) :msg "olc-encode:lat")
+      (olc-encode nil 1))
+
+    (olctest-assert-error (:exp (wrong-type-argument) :msg "olc-encode:lon")
+      (olc-encode 1 nil))
+
+    (olctest-assert-error (:exp (wrong-type-argument) :msg "olc-encode:key")
+      (olc-encode 1 1 :len nil))
+
+    (olctest-assert-error (:exp (wrong-type-argument) :msg "olc-decode")
+      (olc-decode nil))
+
+    (olctest-assert-error (:exp (wrong-type-argument) :msg "olc-shorten:code")
+      (olc-shorten nil 1 1))
+
+    (olctest-assert-error (:exp (wrong-type-argument) :msg "olc-shorten:lat")
+      (olc-shorten "" nil 1))
+
+    (olctest-assert-error (:exp (wrong-type-argument) :msg "olc-shorten:lon")
+      (olc-shorten "" 1 nil))
+
+    (olctest-assert-error (:exp (wrong-type-argument) :msg "olc-shorten:limit:nil")
+      (olc-shorten "" 1 nil))
+
+    (olctest-assert-error (:exp (wrong-type-argument) :msg "olc-shorten:limit:lo")
+      (olc-shorten "" 1 1 :limit 0))
+
+    (olctest-assert-error (:exp (wrong-type-argument) :msg "olc-shorten:limit:hi")
+      (olc-shorten "" 1 1 :limit 19))
+
+    (olctest-assert-error (:exp (wrong-type-argument) :msg "olc-shorten:limit:odd")
+      (olc-shorten "" 1 1 :limit 3))
+
+    (olctest-assert-error (:exp (wrong-type-argument) :msg "olc-shorten-compound:code:nil")
+      (olc-shorten-compound nil))
+
+    (olctest-assert-error (:exp (wrong-type-argument) :msg "olc-shorten-compound:limit:nil")
+      (olc-shorten-compound "22222222+" :limit nil))
+
+    (olctest-assert-error (:exp (wrong-type-argument) :msg "olc-shorten-compound:limit:lo")
+      (olc-shorten-compound "22222222+" :limit 0))
+
+    (olctest-assert-error (:exp (wrong-type-argument) :msg "olc-shorten-compound:limit:hi")
+      (olc-shorten-compound "22222222+" :limit 19))
+
+    (olctest-assert-error (:exp (wrong-type-argument) :msg "olc-shorten-compound:limit:odd")
+      (olc-shorten-compound "22222222+" :limit 3))
+
+    (olctest-assert-error (:exp (wrong-type-argument) :msg "olc-shorten-compound:zoom:nil")
+      (olc-shorten-compound "22222222+" :zoom nil))
+
+    (olctest-assert-error (:exp (args-out-of-range) :msg "olc-shorten-compound:zoom:lo")
+      (olc-shorten-compound "22222222+" :zoom 0))
+
+    (olctest-assert-error (:exp (args-out-of-range) :msg "olc-shorten-compound:zoom:hi")
+      (olc-shorten-compound "22222222+" :zoom 19))
+
+    (olctest-assert-error (:exp (args-out-of-range) :msg "olc-shorten-compound:zoom:llo")
+      (olc-shorten-compound "22222222+" :zoom '(0 8)))
+
+    (olctest-assert-error (:exp (args-out-of-range) :msg "olc-shorten-compound:zoom:rhi")
+      (olc-shorten-compound "22222222+" :zoom '(1 19)))
+
+    (olctest-assert-error (:exp (args-out-of-range) :msg "olc-shorten-compound:zoom:inv")
+      (olc-shorten-compound "22222222+" :zoom '(5 4)))
+
+    (olctest-assert-error (:exp (wrong-type-argument) :msg "olc-recover:code")
+      (olc-recover nil 1 1))
+
+    (olctest-assert-error (:exp (wrong-type-argument) :msg "olc-recover:lat")
+      (olc-recover "" nil 1))
+
+    (olctest-assert-error (:exp (wrong-type-argument) :msg "olc-recover:lon")
+      (olc-recover "" 1 nil))
+
+    (olctest-assert-error (:exp (wrong-type-argument) :msg "olc-recover:format")
+      (olc-recover "22222222+" 1 1 :format 'invalid))
+
+    (olctest-assert-error (:exp (wrong-type-argument) :msg "olc-recover-compound:code")
+      (olc-recover-compound nil :ref "Stockholm"))
+
+    (olctest-assert-error (:exp (wrong-type-argument) :msg "olc-recover-compound:ref:double")
+      (olc-recover-compound "2222+ Stockholm" :ref "Stockholm"))
+
+    (olctest-assert-error (:exp (wrong-type-argument) :msg "olc-recover-compound:ref:nil")
+      (olc-recover-compound "2222+" :ref nil))
+
+    (olctest-assert-error (:exp (wrong-type-argument) :msg "olc-recover-compound:format")
+      (olc-recover-compound "2222+" :ref "Stockholm" :format 'invalid))
+
+    ))
+
+
+
 (defun olctest-issue-1 ()
   (olctest-testcase "local:issue-1"
     (olctest-assert-error (:exp (wrong-type-argument) :msg "F1")
@@ -310,6 +421,7 @@
        (olctest-validity)
        (olctest-localtests)
        (olctest-issue-3)
+       (olctest-issue-2)
        (olctest-issue-1)
        ))
 
-- 
GitLab