[pve-devel] [PATCH common 1/2] JSONSchema: add format validator support and cleanup check_format

Stefan Reiter s.reiter at proxmox.com
Thu Apr 16 16:39:32 CEST 2020


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.reiter at 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





More information about the pve-devel mailing list