]> git.proxmox.com Git - pve-common.git/blobdiff - src/PVE/JSONSchema.pm
get_property_description: add new 'text' format
[pve-common.git] / src / PVE / JSONSchema.pm
index 0cb281d9e84ffbb2b25df4aa3e68e30e950b6035..7716ed360d2a8b1652f90c0eeb098e79d3c51e30 100644 (file)
@@ -71,19 +71,19 @@ register_standard_option('pve-iface', {
     minLength => 2, maxLength => 20,
 });
 
-PVE::JSONSchema::register_standard_option('pve-storage-id', {
+register_standard_option('pve-storage-id', {
     description => "The storage identifier.",
     type => 'string', format => 'pve-storage-id',
 }); 
 
-PVE::JSONSchema::register_standard_option('pve-config-digest', {
+register_standard_option('pve-config-digest', {
     description => 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
     type => 'string',
     optional => 1,
     maxLength => 40, # sha1 hex digest lenght is 40
 });
 
-PVE::JSONSchema::register_standard_option('extra-args', {
+register_standard_option('extra-args', {
     description => "Extra arguments as array",
     type => 'array',
     items => { type => 'string' },
@@ -110,6 +110,16 @@ sub get_format {
 
 register_format('string', sub {}); # allow format => 'string-list'
 
+register_format('urlencoded', \&pve_verify_urlencoded);
+sub pve_verify_urlencoded {
+    my ($text, $noerr) = @_;
+    if ($text !~ /^[-%a-zA-Z0-9_.!~*'()]*$/) {
+       return undef if $noerr;
+       die "invalid urlencoded string: $text\n";
+    }
+    return $text;
+}
+
 register_format('pve-configid', \&pve_verify_configid);
 sub pve_verify_configid {
     my ($id, $noerr) = @_;
@@ -137,7 +147,7 @@ register_format('pve-vmid', \&pve_verify_vmid);
 sub pve_verify_vmid {
     my ($vmid, $noerr) = @_;
 
-    if ($vmid !~ m/^[1-9][0-9]+$/) {
+    if ($vmid !~ m/^[1-9][0-9]{2,8}$/) {
        return undef if $noerr;
        die "value does not look like a valid VM ID\n";
     }
@@ -248,7 +258,7 @@ register_format('CIDRv4', \&pve_verify_cidrv4);
 sub pve_verify_cidrv4 {
     my ($cidr, $noerr) = @_;
 
-    if ($cidr =~ m!^(?:$IPV4RE)(?:/(\d+))$! && ($1 > 7) &&  ($1 < 32)) {
+    if ($cidr =~ m!^(?:$IPV4RE)(?:/(\d+))$! && ($1 > 7) &&  ($1 <= 32)) {
        return $cidr;
     }
 
@@ -488,7 +498,10 @@ sub format_size {
 };
 
 sub parse_property_string {
-    my ($format, $data, $path) = @_;
+    my ($format, $data, $path, $additional_properties) = @_;
+
+    # In property strings we default to not allowing additional properties
+    $additional_properties = 0 if !defined($additional_properties);
 
     my $default_key;
 
@@ -498,16 +511,20 @@ sub parse_property_string {
 
        if ($part =~ /^([^=]+)=(.+)$/) {
            my ($k, $v) = ($1, $2);
-           die "duplicate key in comma-separated list property: $k" if defined($res->{$k});
+           die "duplicate key in comma-separated list property: $k\n" if defined($res->{$k});
            my $schema = $format->{$k};
-           die "invalid key in comma-separated list property: $k" if !$schema;
+           if (my $alias = $schema->{alias}) {
+               $k = $alias;
+               $schema = $format->{$k};
+           }
+           die "invalid key in comma-separated list property: $k\n" if !$schema;
            if ($schema->{type} && $schema->{type} eq 'boolean') {
                $v = 1 if $v =~ m/^(1|on|yes|true)$/i;
                $v = 0 if $v =~ m/^(0|off|no|false)$/i;
            }
            $res->{$k} = $v;
        } elsif ($part !~ /=/) {
-           die "duplicate key in comma-separated list property: $default_key" if $default_key;
+           die "duplicate key in comma-separated list property: $default_key\n" if $default_key;
            foreach my $key (keys %$format) {
                if ($format->{$key}->{default_key}) {
                    $default_key = $key;
@@ -515,18 +532,19 @@ sub parse_property_string {
                        $res->{$default_key} = $part;
                        last;
                    }
-                   die "duplicate key in comma-separated list property: $default_key";
+                   die "duplicate key in comma-separated list property: $default_key\n";
                }
            }
+           die "value without key, but schema does not define a default key\n" if !$default_key;
        } else {
-           die "missing key in comma-separated list property";
+           die "missing key in comma-separated list property\n";
        }
     }
 
     my $errors = {};
-    check_object($path, $format, $res, undef, $errors);
+    check_object($path, $format, $res, $additional_properties, $errors);
     if (scalar(%$errors)) {
-       raise "format error", errors => $errors;
+       raise "format error\n", errors => $errors;
     }
 
     return $res;
@@ -537,7 +555,7 @@ sub print_property_string {
 
     if (ref($format) ne 'HASH') {
        my $schema = $format_list->{$format};
-       die "not a valid format: $format" if !$schema;
+       die "not a valid format: $format\n" if !$schema;
        $format = $schema;
     }
 
@@ -553,14 +571,14 @@ sub print_property_string {
     my %required; # this is a set, all present keys are required regardless of value
     foreach my $key (keys %$format) {
        $allowed{$key} = 1;
-       if (!$format->{$key}->{optional} && !$skipped{$key}) {
+       if (!$format->{$key}->{optional} && !$format->{$key}->{alias} && !$skipped{$key}) {
            $required{$key} = 1;
        }
 
        # Skip default keys
        if ($format->{$key}->{default_key}) {
            if ($default_key) {
-               warn "multiple default keys in schema ($default_key, $key)";
+               warn "multiple default keys in schema ($default_key, $key)\n";
            } else {
                $default_key = $key;
                $skipped{$key} = 1;
@@ -578,23 +596,25 @@ sub print_property_string {
     }
 
     foreach my $key (sort keys %$data) {
-       die "invalid key: $key" if !$allowed{$key};
        delete $required{$key};
        next if $skipped{$key};
+       die "invalid key: $key\n" if !$allowed{$key};
 
-       my $type = $format->{$key}->{type};
+       my $typeformat = $format->{$key}->{format};
        my $value = $data->{$key};
+       next if !defined($value);
        $text .= $comma;
        $comma = ',';
-       if ($type eq 'disk-size') {
+       if ($typeformat && $typeformat eq 'disk-size') {
            $text .= "$key=" . format_size($value);
        } else {
+           die "illegal value with commas for $key\n" if $value =~ /,/;
            $text .= "$key=$value";
        }
     }
 
     if (my $missing = join(',', keys %required)) {
-       die "missing properties: $missing";
+       die "missing properties: $missing\n";
     }
 
     return $text;
@@ -810,7 +830,7 @@ sub check_prop {
 
     if (!defined ($value)) {
        return if $schema->{type} && $schema->{type} eq 'null';
-       if (!$schema->{optional}) {
+       if (!$schema->{optional} && !$schema->{alias}) {
            add_error($errors, $path, "property is missing and it is not optional");
        }
        return;
@@ -1048,6 +1068,11 @@ my $default_schema_noref = {
            optional => 1,
            description => "Whether this is the default key in a comma separated list property string.",
        },
+       alias => {
+           type => 'string',
+           optional => 1,
+           description => "When a key represents the same property as another it can be an alias to it, causing the parsed datastructure to use the other key to store the current value under.",
+       },
        default => {
            type => "any",
            optional => 1,