]> git.proxmox.com Git - pve-common.git/blobdiff - src/PVE/JSONSchema.pm
added JSONSchema::print_property_string
[pve-common.git] / src / PVE / JSONSchema.pm
index 3afc37abd45d0d1d6dc1089d29ccc960224bdd21..0cb281d9e84ffbb2b25df4aa3e68e30e950b6035 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'
@@ -336,6 +341,16 @@ sub pve_verify_address {
     return $addr;
 }
 
+register_format('disk-size', \&pve_verify_disk_size);
+sub pve_verify_disk_size {
+    my ($size, $noerr) = @_;
+    if (!defined(parse_size($size))) {
+       return undef if $noerr;
+       die "value does not look like a valid disk size: $size\n";
+    }
+    return $size;
+}
+
 register_standard_option('spice-proxy', {
     description => "SPICE proxy server. This can be used by the client to specify the proxy server. All nodes in a cluster runs 'spiceproxy', so it is up to the client to choose one. By default, we return the node where the VM is currently running. As resonable setting is to use same node you use to connect to the API (This is window.location.hostname for the JS GUI).",
     type => 'string', format => 'address',
@@ -433,6 +448,45 @@ sub check_format {
     }
 } 
 
+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) = @_;
 
@@ -478,6 +532,74 @@ sub parse_property_string {
     return $res;
 }
 
+sub print_property_string {
+    my ($data, $format, $skip, $path) = @_;
+
+    if (ref($format) ne 'HASH') {
+       my $schema = $format_list->{$format};
+       die "not a valid format: $format" if !$schema;
+       $format = $schema;
+    }
+
+    my $errors = {};
+    check_object($path, $format, $data, undef, $errors);
+    if (scalar(%$errors)) {
+       raise "format error", errors => $errors;
+    }
+
+    my $default_key;
+    my %skipped = map { $_ => 1 } @$skip;
+    my %allowed;
+    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}) {
+           $required{$key} = 1;
+       }
+
+       # Skip default keys
+       if ($format->{$key}->{default_key}) {
+           if ($default_key) {
+               warn "multiple default keys in schema ($default_key, $key)";
+           } else {
+               $default_key = $key;
+               $skipped{$key} = 1;
+           }
+       }
+    }
+
+    my ($text, $comma);
+    if ($default_key) {
+       $text = "$data->{$default_key}";
+       $comma = ',';
+    } else {
+       $text = '';
+       $comma = '';
+    }
+
+    foreach my $key (sort keys %$data) {
+       die "invalid key: $key" if !$allowed{$key};
+       delete $required{$key};
+       next if $skipped{$key};
+
+       my $type = $format->{$key}->{type};
+       my $value = $data->{$key};
+       $text .= $comma;
+       $comma = ',';
+       if ($type eq 'disk-size') {
+           $text .= "$key=" . format_size($value);
+       } else {
+           $text .= "$key=$value";
+       }
+    }
+
+    if (my $missing = join(',', keys %required)) {
+       die "missing properties: $missing";
+    }
+
+    return $text;
+}
+
 sub add_error {
     my ($errors, $path, $msg) = @_;
 
@@ -1341,45 +1463,4 @@ sub dump_config {
     return $data;
 }
 
-sub generate_typetext {
-    my ($schema) = @_;
-    my $typetext = '';
-    my (@optional, @required);
-    foreach my $key (sort keys %$schema) {
-       next if !$schema->{$key}->{format_description} &&
-               !$schema->{$key}->{typetext};
-       if ($schema->{$key}->{optional}) {
-           push @optional, $key;
-       } else {
-           push @required, $key;
-       }
-    }
-    my ($pre, $post) = ('', '');
-    my $add = sub {
-       my ($key) = @_;
-       $typetext .= $pre;
-       my $entry = $schema->{$key};
-       if (my $desc = $entry->{format_description}) {
-           $typetext .= $entry->{default_key} ? "[$key=]" : "$key=";
-           $typetext .= "<$desc>";
-       } elsif (my $text = $entry->{typetext}) {
-           $typetext .= $text;
-       } else {
-           die "internal error: neither format_description nor typetext found";
-       }
-       $typetext .= $post;
-    };
-    foreach my $key (@required) {
-       &$add($key);
-       $pre = ', ';
-    }
-    $pre = $pre ? ' [,' : '[';
-    $post = ']';
-    foreach my $key (@optional) {
-       &$add($key);
-       $pre = ' [,';
-    }
-    return $typetext;
-}
-
 1;