]> git.proxmox.com Git - pve-common.git/blobdiff - src/PVE/JSONSchema.pm
Added PVE::JSONSchema::parse_size/format_size
[pve-common.git] / src / PVE / JSONSchema.pm
index 32d461cbf6467e6ad7adbdd86b650df79e7f7228..5beebd80139a1aacbb8ce6dfc34b9bfa50417eb2 100644 (file)
@@ -101,6 +101,11 @@ sub register_format {
     $format_list->{$format} = $code;
 }
 
+sub get_format {
+    my ($format) = @_;
+    return $format_list->{$format};
+}
+
 # register some common type for pve
 
 register_format('string', sub {}); # allow format => 'string-list'
@@ -227,26 +232,70 @@ sub pve_verify_ipv4mask {
     return $mask;
 }
 
-register_format('CIDR', \&pve_verify_cidr);
-sub pve_verify_cidr {
+register_format('CIDRv6', \&pve_verify_cidrv6);
+sub pve_verify_cidrv6 {
     my ($cidr, $noerr) = @_;
 
-    if ($cidr =~ m!^(?:$IPV4RE)(?:/(\d+))$! && ($1 > 7) &&  ($1 < 32)) {
+    if ($cidr =~ m!^(?:$IPV6RE)(?:/(\d+))$! && ($1 > 7) &&  ($1 <= 120)) {
        return $cidr;
-    } elsif ($cidr =~ m!^(?:$IPV6RE)(?:/(\d+))$! && ($1 > 7) &&  ($1 <= 120)) {
+    }
+
+    return undef if $noerr;
+    die "value does not look like a valid IPv6 CIDR network\n";
+}
+
+register_format('CIDRv4', \&pve_verify_cidrv4);
+sub pve_verify_cidrv4 {
+    my ($cidr, $noerr) = @_;
+
+    if ($cidr =~ m!^(?:$IPV4RE)(?:/(\d+))$! && ($1 > 7) &&  ($1 < 32)) {
        return $cidr;
     }
 
     return undef if $noerr;
-    die "value does not look like a valid CIDR network\n";
+    die "value does not look like a valid IPv4 CIDR network\n";
+}
+
+register_format('CIDR', \&pve_verify_cidr);
+sub pve_verify_cidr {
+    my ($cidr, $noerr) = @_;
+
+    if (!(pve_verify_cidrv4($cidr, 1) ||
+         pve_verify_cidrv6($cidr, 1)))
+    {
+       return undef if $noerr;
+       die "value does not look like a valid CIDR network\n";
+    }
+
+    return $cidr;
+}
+
+register_format('pve-ipv4-config', \&pve_verify_ipv4_config);
+sub pve_verify_ipv4_config {
+    my ($config, $noerr) = @_;
+
+    return $config if $config =~ /^(?:dhcp|manual)$/ ||
+                      pve_verify_cidrv4($config, 1);
+    return undef if $noerr;
+    die "value does not look like a valid ipv4 network configuration\n";
+}
+
+register_format('pve-ipv6-config', \&pve_verify_ipv6_config);
+sub pve_verify_ipv6_config {
+    my ($config, $noerr) = @_;
+
+    return $config if $config =~ /^(?:auto|dhcp|manual)$/ ||
+                      pve_verify_cidrv6($config, 1);
+    return undef if $noerr;
+    die "value does not look like a valid ipv6 network configuration\n";
 }
 
 register_format('email', \&pve_verify_email);
 sub pve_verify_email {
     my ($email, $noerr) = @_;
 
-    # we use same regex as extjs Ext.form.VTypes.email
-    if ($email !~ /^(\w+)([\-+.][\w]+)*@(\w[\-\w]*\.){1,5}([A-Za-z]){2,6}$/) {
+    # we use same regex as in Utils.js
+    if ($email !~ /^(\w+)([\-+.][\w]+)*@(\w[\-\w]*\.){1,5}([A-Za-z]){2,63}$/) {
           return undef if $noerr;
           die "value does not look like a valid email address\n";
     }
@@ -352,8 +401,9 @@ PVE::JSONSchema::register_standard_option('pve-startup-order', {
 });
 
 sub check_format {
-    my ($format, $value) = @_;
+    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$/) {
@@ -383,10 +433,95 @@ sub check_format {
 
        die "undefined format '$format'\n" if !$code;
 
+       return parse_property_string($code, $value, $path) if ref($code) eq 'HASH';
        &$code($value);
     }
 } 
 
+sub parse_size {
+    my ($value) = @_;
+
+    return undef if $value !~ m/^(\d+(\.\d+)?)([KMGT])?$/;
+    my ($size, $unit) = ($1, $3);
+    if ($unit) {
+       if ($unit eq 'K') {
+           $size = $size * 1024;
+       } elsif ($unit eq 'M') {
+           $size = $size * 1024 * 1024;
+       } elsif ($unit eq 'G') {
+           $size = $size * 1024 * 1024 * 1024;
+       } elsif ($unit eq 'T') {
+           $size = $size * 1024 * 1024 * 1024 * 1024;
+       }
+    }
+    return int($size);
+};
+
+sub format_size {
+    my ($size) = @_;
+
+    $size = int($size);
+
+    my $kb = int($size/1024);
+    return $size if $kb*1024 != $size;
+
+    my $mb = int($kb/1024);
+    return "${kb}K" if $mb*1024 != $kb;
+
+    my $gb = int($mb/1024);
+    return "${mb}M" if $gb*1024 != $mb;
+
+    my $tb = int($gb/1024);
+    return "${gb}G" if $tb*1024 != $gb;
+
+    return "${tb}T";
+};
+
+sub parse_property_string {
+    my ($format, $data, $path) = @_;
+
+    my $default_key;
+
+    my $res = {};
+    foreach my $part (split(/,/, $data)) {
+       next if $part =~ /^\s*$/;
+
+       if ($part =~ /^([^=]+)=(.+)$/) {
+           my ($k, $v) = ($1, $2);
+           die "duplicate key in comma-separated list property: $k" if defined($res->{$k});
+           my $schema = $format->{$k};
+           die "invalid key in comma-separated list property: $k" 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;
+           foreach my $key (keys %$format) {
+               if ($format->{$key}->{default_key}) {
+                   $default_key = $key;
+                   if (!$res->{$default_key}) {
+                       $res->{$default_key} = $part;
+                       last;
+                   }
+                   die "duplicate key in comma-separated list property: $default_key";
+               }
+           }
+       } else {
+           die "missing key in comma-separated list property";
+       }
+    }
+
+    my $errors = {};
+    check_object($path, $format, $res, undef, $errors);
+    if (scalar(%$errors)) {
+       raise "format error", errors => $errors;
+    }
+
+    return $res;
+}
+
 sub add_error {
     my ($errors, $path, $msg) = @_;
 
@@ -563,6 +698,19 @@ sub check_object {
     }
 }
 
+sub check_object_warn {
+    my ($path, $schema, $value, $additional_properties) = @_;
+    my $errors = {};
+    check_object($path, $schema, $value, $additional_properties, $errors);
+    if (scalar(%$errors)) {
+       foreach my $k (keys %$errors) {
+           warn "parse error: $k: $errors->{$k}\n";
+       }
+       return 0;
+    }
+    return 1;
+}
+
 sub check_prop {
     my ($value, $schema, $path, $errors) = @_;
 
@@ -626,7 +774,7 @@ sub check_prop {
     } else {
 
        if (my $format = $schema->{format}) {
-           eval { check_format($format, $value); };
+           eval { check_format($format, $value, $path); };
            if ($@) {
                add_error($errors, $path, "invalid format - $@");
                return;
@@ -797,6 +945,11 @@ my $default_schema_noref = {
            optional => 1,
            description => "This provides a description of the purpose the instance property. The value can be a string or it can be an object with properties corresponding to various different instance languages (with an optional default property indicating the default description).",
        },
+       format_description => {
+           type => "string",
+           optional => 1,
+           description => "This provides a shorter (usually just one word) description for a property used to generate descriptions for comma separated list property strings.",
+       },
         title => {
            type => "string",
            optional => 1,
@@ -808,10 +961,15 @@ my $default_schema_noref = {
            description => "indicates a required property or a schema that must be validated if this property is present",
         },
         format => {
-           type => "string",
+           type => [ "string", "object" ],
            optional => 1,
            description => "This indicates what format the data is among some predefined formats which may include:\n\ndate - a string following the ISO format \naddress \nschema - a schema definition object \nperson \npage \nhtml - a string representing HTML",
         },
+       default_key => {
+           type => "boolean",
+           optional => 1,
+           description => "Whether this is the default key in a comma separated list property string.",
+       },
        default => {
            type => "any",
            optional => 1,
@@ -967,6 +1125,11 @@ my $method_schema = {
            description => "JSON Schema for parameters.",
            optional => 1,
        },
+        formatter => {
+           type => 'object',
+           description => "Used to store page formatter information (set by PVE::RESTHandler->register_page_formatter).",
+           optional => 1,
+        },
        returns => {
            type => 'object',
            description => "JSON Schema for return value.",