Co-authored-by: Felix Lechner
After spending some time with old and new Guix services, I'd like to suggest some potential improvements to our define-configuration macro: User-specified sanitizer support =============================================================================== The sanitizers should be integrated with the type. Otherwise, they are tedious to use and appear verbose when repeatedly applied to multiple fields. --8<---------------cut here---------------start------------->8--- ;; Suggestion #1 ;; The procedure could return a sanitized value. Upon failure, there are ;; the following options: ;; - The procedure returns only a special value (akin to %unset-value) ;; and an error message, as a pair. ;; Exception raising is done by define-sanitized macro behind the scenes ;; which makes the procedure easier to write. ;; - The procedure raises an exception. There would be no consistency ;; on the message formats, however, except for any agreed convention. ;; This would involve some code duplication. ;; Cons: too specific, not extensible. (define-sanitized typename procedure (prefix ...)) ;; Suggestion #2 ;; A user-supplied procedure ('procname' below) would work just like the ;; procedure in option #1. ;; There is some similiarity to the Guix record-type*. ;; This could be extended more easily in the future should it be required. (define-type typename ; maybe call this 'define-configuration-type' ? (sanitizer procname) (maybe-type? #t) ;; The properties below are service specific. ;; If this is implemented with Guix record-type* then we could have a ;; module containing generic types and do something along the lines of: ;; (define-type foo-ip-address ;; (inherit generic-ip-address) ;; (serializer ...)) (serializer procname) ; define-type/no-serialization = sets this field to #f ? (prefix ...)) --8<---------------cut here---------------end--------------->8--- The original motivation for this proposal stems from the attempts to resolve issue #61570. There, one potential fix was to handle the user and group fields similarly to the way greetd service handles them. There is some opportunity for generalization here, types that might be useful elsewhere, such as a port number or a host name, could be placed in a separate module. This proposal should also make it easier to handle situations when fields become obsolete. Record Validator =============================================================================== There is also a need to validate records. Matching fields alone do not actually ensure that the configuration is coherent and usable. For example, some fields may be mutually incompatible with others. We could provide procedures that validate each record type within define-configuration itself instead of validating the value at runtime (i.e. within the body of the service-type). --8<---------------cut here---------------start------------->8--- ;; the common case (define-configuration foo-configuration (name string "Lorem ipsum...") ;; ... (validator procname)) ;; [bonus] Simpler configurations that only care for mutually-exclusive fields (define-configuration foo-configuration (name string "Lorem ipsum...") (title string "Lorem ipsum..." (conflicts 'name))) --8<---------------cut here---------------end--------------->8--- Comments regarding literal placement ------------------------------------------------------------------------------- Does the placement order matter for the extra fields/literals for define-configuration? Can they be placed arbitrarily or only at the bottom? (where custom-serializer is specified) Another point, extra parameters given to define-configuration should never clash with field names. For example, it should be possible to name a field 'prefix', 'location' or similar without causing issues like #59423. Coalesced documentation =============================================================================== Currently, we manually edit the texinfo output from configuration->documentation if we're unsatisfied with the generated result. For instance, substituting @item with an @itemx marker for fields whose documentation is similar. Instead, we could embed hints in define-configuration that affect the texinfo generation in smarter ways. In the long term, guix.texi should source some portions of the documentation directly from the code base. The current workflow involving copy and paste the output from the evaluation of configuration->documentation carries the unnecessary risk that future documentation patches are done against guix.texi rather than the .scm file from where it was generated. (issue #60582) Snippet based on mympd-ip-acl (gnu/services/audio.scm): --8<---------------cut here---------------start------------->8--- (define-configuration/no-serialization mympd-ip-acl (allow (list-of-string '()) "Allowed/Disallowed IP addresses.") (deny (list-of-string '()) (documentation 'allow))) ; group with field 'allow ;;; --- texi output below --- @c %start of fragment @deftp {Data Type} mympd-ip-acl Available @code{mympd-ip-acl} fields are: @table @asis @item @code{allow} @itemx @code{deny} (default: @code{()}) (type: list-of-string) Allowed/Disallowed IP addresses. @end table @end deftp @c %end of fragment --8<---------------cut here---------------end--------------->8--- Serializer access to other fields =============================================================================== Serialization procedures generally only have access to the values of its own field. That may be insufficient in some cases as whether a field can be serialized or how that is done, for example, can depend on the value of other fields. mympd-service-type is one example where each serialized field depends on the value of another field. Our standard serializer procedures were useless for that case. As a side note, the 'this-record' macro does not work with define-configuration which made it impossible to use (custom-serializer) for the same effect. Instead, serializer procedures could take additional keyword arguments: --8<---------------cut here---------------start------------->8--- (define* (serialize-string field-name value (#:key config)) (let ((baz-value (assoc-ref config 'baz))) (string-append baz-value "/" value) ...)) --8<---------------cut here---------------end--------------->8--- Inheritable record-type definition =============================================================================== The openvpn-service pairs in (gnu services vpn) define a special macro define-split-configuration with the purpose to avoid code duplication since the service pairs have multiple fields in common. Perhaps in a similar vein to SRFI-136, we could have: --8<---------------cut here---------------start------------->8--- (define-configuration openvpn-common-configuration (openvpn (file-like openvpn) "The OpenVPN package.") ;; ... ) (define-configuration openvpn-server-configuration openvpn-common-configuration (tls-auth (tls-auth-server #f) "Add an additional layer of HMAC authentication on top of the TLS control channel to protect against DoS attacks.") ;; ... ) ;;; or through a literal/keyword approach (define-configuration openvpn-server-configuration (tls-auth (tls-auth-server #f) "Add an additional layer of HMAC authentication on top of the TLS control channel to protect against DoS attacks.") (parent openvpn-common-configuration)) --8<---------------cut here---------------end--------------->8--- Generic serialize-configuration =============================================================================== The procedure serialize-configuration inherently assumes that the serialized configuration must be a single string. This assumption needn't always hold, especially if the service in question is not a shepherd service. We could possibly make this procedure a bit more "generic", maybe picking some ideas from SRFI-171 Transducers. Another improvement to this procedure is to eliminate (or make optional) the second parameter, the configuration fields. It's unclear what value it brings since one can't use fields from another configuration type here. An argument can be made for selectively serializing some of the fields but then it would be more practical to make this an optional parameter. PS. =============================================================================== Kind of a late realization, but couldn't many of the items above be satisfied by improvements to define-record-type* instead? (define-record-type* paired with a documentation literal is nearly equivalent to define-configuration, sans the serialization scaffolding) Cheers, Bruno