diff options
| author | Ludovic Courtès <ludo@gnu.org> | 2022-06-18 22:37:20 +0200 |
|---|---|---|
| committer | Ludovic Courtès <ludo@gnu.org> | 2022-06-24 23:18:20 +0200 |
| commit | fb7e6ccba7cc243cd96cdc3fde3daa9a5f08e531 (patch) | |
| tree | 2818996e5527553d52cdabd431dd245acc33a8da | |
| parent | 43137d058fe575a70707073bede3465b4c5f555a (diff) | |
services: configuration: Report the location of field type errors.
Previously field type errors would be reported in a non-standard way,
and without any source location information. This fixes it.
* gnu/services/configuration.scm (configuration-field-error): Add a
'loc' parameter and honor it. Use 'formatted-message' instead of plain
'format'.
(define-configuration-helper)[field-sanitizer]: New procedure.
Use it. Use STEM as the identifier of the syntactic constructor of the
record type. Add a 'sanitize' property to each field. Remove now
useless STEM macro that would call 'validate-configuration'.
* gnu/services/mail.scm (serialize-listener-configuration): Adjust to
new 'configuration-field-error' prototype.
* tests/services/configuration.scm ("wrong type for a field"): New test.
* po/guix/POTFILES.in: Add gnu/services/configuration.scm.
| -rw-r--r-- | gnu/services/configuration.scm | 55 | ||||
| -rw-r--r-- | gnu/services/mail.scm | 2 | ||||
| -rw-r--r-- | po/guix/POTFILES.in | 1 | ||||
| -rw-r--r-- | tests/services/configuration.scm | 13 |
4 files changed, 58 insertions, 13 deletions
diff --git a/gnu/services/configuration.scm b/gnu/services/configuration.scm index f6b20fb82b0..c39ea5a02a0 100644 --- a/gnu/services/configuration.scm +++ b/gnu/services/configuration.scm | |||
| @@ -27,7 +27,8 @@ | |||
| 27 | #:use-module (guix records) | 27 | #:use-module (guix records) |
| 28 | #:use-module (guix gexp) | 28 | #:use-module (guix gexp) |
| 29 | #:use-module ((guix utils) #:select (source-properties->location)) | 29 | #:use-module ((guix utils) #:select (source-properties->location)) |
| 30 | #:use-module ((guix diagnostics) #:select (formatted-message location-file)) | 30 | #:use-module ((guix diagnostics) |
| 31 | #:select (formatted-message location-file &error-location)) | ||
| 31 | #:use-module ((guix modules) #:select (file-name->module-name)) | 32 | #:use-module ((guix modules) #:select (file-name->module-name)) |
| 32 | #:use-module (guix i18n) | 33 | #:use-module (guix i18n) |
| 33 | #:autoload (texinfo) (texi-fragment->stexi) | 34 | #:autoload (texinfo) (texi-fragment->stexi) |
| @@ -87,9 +88,17 @@ | |||
| 87 | (define (configuration-error message) | 88 | (define (configuration-error message) |
| 88 | (raise (condition (&message (message message)) | 89 | (raise (condition (&message (message message)) |
| 89 | (&configuration-error)))) | 90 | (&configuration-error)))) |
| 90 | (define (configuration-field-error field val) | 91 | (define (configuration-field-error loc field value) |
| 91 | (configuration-error | 92 | (raise (apply |
| 92 | (format #f "Invalid value for field ~a: ~s" field val))) | 93 | make-compound-condition |
| 94 | (formatted-message (G_ "invalid value ~s for field '~a'") | ||
| 95 | value field) | ||
| 96 | (condition (&configuration-error)) | ||
| 97 | (if loc | ||
| 98 | (list (condition | ||
| 99 | (&error-location (location loc)))) | ||
| 100 | '())))) | ||
| 101 | |||
| 93 | (define (configuration-missing-field kind field) | 102 | (define (configuration-missing-field kind field) |
| 94 | (configuration-error | 103 | (configuration-error |
| 95 | (format #f "~a configuration missing required field ~a" kind field))) | 104 | (format #f "~a configuration missing required field ~a" kind field))) |
| @@ -210,9 +219,33 @@ does not have a default value" field kind))) | |||
| 210 | (id #'stem #'serialize- type)))))) | 219 | (id #'stem #'serialize- type)))))) |
| 211 | #'(field-type ...) | 220 | #'(field-type ...) |
| 212 | #'((custom-serializer ...) ...)))) | 221 | #'((custom-serializer ...) ...)))) |
| 222 | (define (field-sanitizer name pred) | ||
| 223 | ;; Define a macro for use as a record field sanitizer, where NAME | ||
| 224 | ;; is the name of the field and PRED is the predicate that tells | ||
| 225 | ;; whether a value is valid for this field. | ||
| 226 | #`(define-syntax #,(id #'stem #'validate- #'stem #'- name) | ||
| 227 | (lambda (s) | ||
| 228 | ;; Make sure the given VALUE, for field NAME, passes PRED. | ||
| 229 | (syntax-case s () | ||
| 230 | ((_ value) | ||
| 231 | (with-syntax ((name #'#,name) | ||
| 232 | (pred #'#,pred) | ||
| 233 | (loc (datum->syntax #'value | ||
| 234 | (syntax-source #'value)))) | ||
| 235 | #'(if (pred value) | ||
| 236 | value | ||
| 237 | (configuration-field-error | ||
| 238 | (and=> 'loc source-properties->location) | ||
| 239 | 'name value)))))))) | ||
| 240 | |||
| 213 | #`(begin | 241 | #`(begin |
| 242 | ;; Define field validation macros. | ||
| 243 | #,@(map field-sanitizer | ||
| 244 | #'(field ...) | ||
| 245 | #'(field-predicate ...)) | ||
| 246 | |||
| 214 | (define-record-type* #,(id #'stem #'< #'stem #'>) | 247 | (define-record-type* #,(id #'stem #'< #'stem #'>) |
| 215 | #,(id #'stem #'% #'stem) | 248 | stem |
| 216 | #,(id #'stem #'make- #'stem) | 249 | #,(id #'stem #'make- #'stem) |
| 217 | #,(id #'stem #'stem #'?) | 250 | #,(id #'stem #'stem #'?) |
| 218 | (%location #,(id #'stem #'stem #'-location) | 251 | (%location #,(id #'stem #'stem #'-location) |
| @@ -220,10 +253,13 @@ does not have a default value" field kind))) | |||
| 220 | source-properties->location)) | 253 | source-properties->location)) |
| 221 | (innate)) | 254 | (innate)) |
| 222 | #,@(map (lambda (name getter def) | 255 | #,@(map (lambda (name getter def) |
| 223 | #`(#,name #,getter (default #,def))) | 256 | #`(#,name #,getter (default #,def) |
| 257 | (sanitize | ||
| 258 | #,(id #'stem #'validate- #'stem #'- name)))) | ||
| 224 | #'(field ...) | 259 | #'(field ...) |
| 225 | #'(field-getter ...) | 260 | #'(field-getter ...) |
| 226 | #'(field-default ...))) | 261 | #'(field-default ...))) |
| 262 | |||
| 227 | (define #,(id #'stem #'stem #'-fields) | 263 | (define #,(id #'stem #'stem #'-fields) |
| 228 | (list (configuration-field | 264 | (list (configuration-field |
| 229 | (name 'field) | 265 | (name 'field) |
| @@ -240,12 +276,7 @@ does not have a default value" field kind))) | |||
| 240 | '#,(id #'stem #'% #'stem) 'field) | 276 | '#,(id #'stem #'% #'stem) 'field) |
| 241 | field-default))) | 277 | field-default))) |
| 242 | (documentation doc)) | 278 | (documentation doc)) |
| 243 | ...)) | 279 | ...)))))))) |
| 244 | (define-syntax-rule (stem arg (... ...)) | ||
| 245 | (let ((conf (#,(id #'stem #'% #'stem) arg (... ...)))) | ||
| 246 | (validate-configuration conf | ||
| 247 | #,(id #'stem #'stem #'-fields)) | ||
| 248 | conf)))))))) | ||
| 249 | 280 | ||
| 250 | (define no-serialization ;syntactic keyword for 'define-configuration' | 281 | (define no-serialization ;syntactic keyword for 'define-configuration' |
| 251 | '(no serialization)) | 282 | '(no serialization)) |
diff --git a/gnu/services/mail.scm b/gnu/services/mail.scm index d99743ac311..c2fd4d8670b 100644 --- a/gnu/services/mail.scm +++ b/gnu/services/mail.scm | |||
| @@ -285,7 +285,7 @@ the section name.") | |||
| 285 | (serialize-fifo-listener-configuration field-name val)) | 285 | (serialize-fifo-listener-configuration field-name val)) |
| 286 | ((inet-listener-configuration? val) | 286 | ((inet-listener-configuration? val) |
| 287 | (serialize-inet-listener-configuration field-name val)) | 287 | (serialize-inet-listener-configuration field-name val)) |
| 288 | (else (configuration-field-error field-name val)))) | 288 | (else (configuration-field-error #f field-name val)))) |
| 289 | (define (listener-configuration-list? val) | 289 | (define (listener-configuration-list? val) |
| 290 | (and (list? val) (and-map listener-configuration? val))) | 290 | (and (list? val) (and-map listener-configuration? val))) |
| 291 | (define (serialize-listener-configuration-list field-name val) | 291 | (define (serialize-listener-configuration-list field-name val) |
diff --git a/po/guix/POTFILES.in b/po/guix/POTFILES.in index 201e5dcc87e..f50dd004228 100644 --- a/po/guix/POTFILES.in +++ b/po/guix/POTFILES.in | |||
| @@ -4,6 +4,7 @@ gnu.scm | |||
| 4 | gnu/packages.scm | 4 | gnu/packages.scm |
| 5 | gnu/services.scm | 5 | gnu/services.scm |
| 6 | gnu/system.scm | 6 | gnu/system.scm |
| 7 | gnu/services/configuration.scm | ||
| 7 | gnu/services/shepherd.scm | 8 | gnu/services/shepherd.scm |
| 8 | gnu/home/services.scm | 9 | gnu/home/services.scm |
| 9 | gnu/home/services/ssh.scm | 10 | gnu/home/services/ssh.scm |
diff --git a/tests/services/configuration.scm b/tests/services/configuration.scm index 334a1e409b7..6268525317a 100644 --- a/tests/services/configuration.scm +++ b/tests/services/configuration.scm | |||
| @@ -1,6 +1,7 @@ | |||
| 1 | ;;; GNU Guix --- Functional package management for GNU | 1 | ;;; GNU Guix --- Functional package management for GNU |
| 2 | ;;; Copyright © 2021 Maxim Cournoyer <maxim.cournoyer@gmail.com> | 2 | ;;; Copyright © 2021 Maxim Cournoyer <maxim.cournoyer@gmail.com> |
| 3 | ;;; Copyright © 2021 Xinglu Chen <public@yoctocell.xyz> | 3 | ;;; Copyright © 2021 Xinglu Chen <public@yoctocell.xyz> |
| 4 | ;;; Copyright © 2022 Ludovic Courtès <ludo@gnu.org> | ||
| 4 | ;;; | 5 | ;;; |
| 5 | ;;; This file is part of GNU Guix. | 6 | ;;; This file is part of GNU Guix. |
| 6 | ;;; | 7 | ;;; |
| @@ -19,6 +20,7 @@ | |||
| 19 | 20 | ||
| 20 | (define-module (tests services configuration) | 21 | (define-module (tests services configuration) |
| 21 | #:use-module (gnu services configuration) | 22 | #:use-module (gnu services configuration) |
| 23 | #:use-module (guix diagnostics) | ||
| 22 | #:use-module (guix gexp) | 24 | #:use-module (guix gexp) |
| 23 | #:use-module (srfi srfi-34) | 25 | #:use-module (srfi srfi-34) |
| 24 | #:use-module (srfi srfi-64)) | 26 | #:use-module (srfi srfi-64)) |
| @@ -43,6 +45,17 @@ | |||
| 43 | 80 | 45 | 80 |
| 44 | (port-configuration-port (port-configuration))) | 46 | (port-configuration-port (port-configuration))) |
| 45 | 47 | ||
| 48 | (test-equal "wrong type for a field" | ||
| 49 | '("configuration.scm" 57 11) ;error location | ||
| 50 | (guard (c ((configuration-error? c) | ||
| 51 | (let ((loc (error-location c))) | ||
| 52 | (list (basename (location-file loc)) | ||
| 53 | (location-line loc) | ||
| 54 | (location-column loc))))) | ||
| 55 | (port-configuration | ||
| 56 | ;; This is line 56; the test relies on line/column numbers! | ||
| 57 | (port "This is not a number!")))) | ||
| 58 | |||
| 46 | (define-configuration port-configuration-cs | 59 | (define-configuration port-configuration-cs |
| 47 | (port (number 80) "The port number." empty-serializer)) | 60 | (port (number 80) "The port number." empty-serializer)) |
| 48 | 61 | ||
