Adds a third, optional parameter to register_format that allows specifying
a function that will be called after parsing and can validate the parsed
data. A validator should die on failed validation, and can also change the
parsed object by returning a modified version of it.

This is useful so one can register a format with its hash, thus allowing
documentation to be generated automatically, while still enforcing certain
validation rules.

And since I found it impossible to extend the current check_format code,
clean that function up as well (which pretty much amounts to a rewrite).
Also use get_format consistently to avoid future breakages.

No existing functionality is intentionally changed.

Signed-off-by: Stefan Reiter <s.rei...@proxmox.com>
---

@Fabian G.: Since we discussed this off-list, is this good? At least there
shouldn't be a possibility for a parser/hash format drift the way I've
implemented it.

 src/PVE/JSONSchema.pm | 60 +++++++++++++++++++++++++++----------------
 1 file changed, 38 insertions(+), 22 deletions(-)

diff --git a/src/PVE/JSONSchema.pm b/src/PVE/JSONSchema.pm
index 01a3cce..106a812 100644
--- a/src/PVE/JSONSchema.pm
+++ b/src/PVE/JSONSchema.pm
@@ -120,19 +120,26 @@ register_standard_option('pve-snapshot-name', {
 });
 
 my $format_list = {};
+my $format_validators = {};
 
 sub register_format {
-    my ($format, $code) = @_;
+    my ($name, $format, $validator) = @_;
 
-    die "JSON schema format '$format' already registered\n"
-       if $format_list->{$format};
+    die "JSON schema format '$name' already registered\n"
+       if $format_list->{$name};
 
-    $format_list->{$format} = $code;
+    if ($validator) {
+       die "A \$validator function can only be specified for hash-based 
formats\n"
+           if ref($format) ne 'HASH';
+       $format_validators->{$name} = $validator;
+    }
+
+    $format_list->{$name} = $format;
 }
 
 sub get_format {
-    my ($format) = @_;
-    return $format_list->{$format};
+    my ($name) = @_;
+    return $format_list->{$name};
 }
 
 my $renderer_hash = {};
@@ -646,13 +653,16 @@ sub pve_verify_tfa_secret {
 sub check_format {
     my ($format, $value, $path) = @_;
 
-    return parse_property_string($format, $value, $path) if ref($format) eq 
'HASH';
     return if $format eq 'regex';
 
-    if ($format =~ m/^(.*)-a?list$/) {
+    my $parsed;
 
-       my $code = $format_list->{$1};
+    if (ref($format) eq 'HASH') {
+       # in case it's a hash we can't have a validator registered, so return
+       return parse_property_string($format, $value, $path);
 
+    } elsif ($format =~ m/^(.*)-a?list$/) {
+       my $code = get_format($1);
        die "undefined format '$format'\n" if !$code;
 
        # Note: we allow empty lists
@@ -660,25 +670,31 @@ sub check_format {
            &$code($v);
        }
 
-    } elsif ($format =~ m/^(.*)-opt$/) {
-
-       my $code = $format_list->{$1};
+       # since the list might contain multiple values, we don't allow running
+       # validator functions either
+       return undef;
 
+    } elsif ($format =~ m/^(.*)-opt$/) {
+       my $code = get_format($1);
        die "undefined format '$format'\n" if !$code;
 
-       return if !$value; # allow empty string
-
-       &$code($value);
+       # empty string is allowed
+       $parsed = $code->($value) if $value;
 
    } else {
+       my $registered_format = get_format($format);
+       die "undefined format '$format'\n" if !$registered_format;
 
-       my $code = $format_list->{$format};
-
-       die "undefined format '$format'\n" if !$code;
-
-       return parse_property_string($code, $value, $path) if ref($code) eq 
'HASH';
-       &$code($value);
+       if (ref($registered_format) eq 'HASH') {
+           $parsed = parse_property_string($registered_format, $value, $path);
+       } else {
+           $parsed = $registered_format->($value);
+       }
     }
+
+    my $validator = $format_validators->{$format};
+    $parsed = $validator->($parsed) if $validator;
+    return $parsed;
 }
 
 sub parse_size {
@@ -735,7 +751,7 @@ sub parse_property_string {
 
     # Support named formats here, too:
     if (!ref($format)) {
-       if (my $desc = $format_list->{$format}) {
+       if (my $desc = get_format($format)) {
            $format = $desc;
        } else {
            die "unknown format: $format\n";
-- 
2.26.1


_______________________________________________
pve-devel mailing list
pve-devel@pve.proxmox.com
https://pve.proxmox.com/cgi-bin/mailman/listinfo/pve-devel

Reply via email to