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