]>
git.proxmox.com Git - pve-common.git/blob - src/PVE/JSONSchema.pm
10037f50f6689b049728ec0c3195573b76cca8f9
1 package PVE
:: JSONSchema
;
5 use Storable
; # for dclone
9 use Devel
:: Cycle
- quiet
; # todo: remove?
10 use PVE
:: Tools
qw(split_list $IPV6RE $IPV4RE ) ;
11 use PVE
:: Exception
qw(raise) ;
12 use HTTP
:: Status
qw(:constants) ;
13 use Net
:: IP
qw(:PROC) ;
19 register_standard_option
23 # Note: This class implements something similar to JSON schema, but it is not 100% complete.
24 # see: http://tools.ietf.org/html/draft-zyp-json-schema-02
25 # see: http://json-schema.org/
27 # the code is similar to the javascript parser from http://code.google.com/p/jsonschema/
29 my $standard_options = {};
30 sub register_standard_option
{
31 my ( $name, $schema ) = @_ ;
33 die "standard option ' $name ' already registered \n "
34 if $standard_options ->{ $name };
36 $standard_options ->{ $name } = $schema ;
39 sub get_standard_option
{
40 my ( $name, $base ) = @_ ;
42 my $std = $standard_options ->{ $name };
43 die "no such standard option ' $name ' \n " if ! $std ;
45 my $res = $base || {};
47 foreach my $opt ( keys %$std ) {
48 next if defined ( $res ->{ $opt });
49 $res ->{ $opt } = $std ->{ $opt };
55 register_standard_option
( 'pve-vmid' , {
56 description
=> "The (unique) ID of the VM." ,
57 type
=> 'integer' , format
=> 'pve-vmid' ,
61 register_standard_option
( 'pve-node' , {
62 description
=> "The cluster node name." ,
63 type
=> 'string' , format
=> 'pve-node' ,
66 register_standard_option
( 'pve-node-list' , {
67 description
=> "List of cluster node names." ,
68 type
=> 'string' , format
=> 'pve-node-list' ,
71 register_standard_option
( 'pve-iface' , {
72 description
=> "Network interface name." ,
73 type
=> 'string' , format
=> 'pve-iface' ,
74 minLength
=> 2 , maxLength
=> 20 ,
77 register_standard_option
( 'pve-storage-id' , {
78 description
=> "The storage identifier." ,
79 type
=> 'string' , format
=> 'pve-storage-id' ,
82 register_standard_option
( 'pve-config-digest' , {
83 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.' ,
86 maxLength
=> 40 , # sha1 hex digest length is 40
89 register_standard_option
( 'skiplock' , {
90 description
=> "Ignore locks - only root is allowed to use this option." ,
95 register_standard_option
( 'extra-args' , {
96 description
=> "Extra arguments as array" ,
98 items
=> { type
=> 'string' },
102 register_standard_option
( 'fingerprint-sha256' , {
103 description
=> "Certificate SHA 256 fingerprint." ,
105 pattern
=> '([A-Fa-f0-9]{2}:){31}[A-Fa-f0-9]{2}' ,
108 register_standard_option
( 'pve-output-format' , {
110 description
=> 'Output format.' ,
111 enum
=> [ 'text' , 'json' , 'json-pretty' , 'yaml' ],
116 my $format_list = {};
118 sub register_format
{
119 my ( $format, $code ) = @_ ;
121 die "JSON schema format ' $format ' already registered \n "
122 if $format_list ->{ $format };
124 $format_list ->{ $format } = $code ;
129 return $format_list ->{ $format };
132 my $renderer_hash = {};
134 sub register_renderer
{
135 my ( $name, $code ) = @_ ;
137 die "renderer ' $name ' already registered \n "
138 if $renderer_hash ->{ $name };
140 $renderer_hash ->{ $name } = $code ;
145 return $renderer_hash ->{ $name };
148 # register some common type for pve
150 register_format
( 'string' , sub {}); # allow format => 'string-list'
152 register_format
( 'urlencoded' , \
& pve_verify_urlencoded
);
153 sub pve_verify_urlencoded
{
154 my ( $text, $noerr ) = @_ ;
155 if ( $text !~ /^[-%a-zA-Z0-9_.!~*'()]*$/ ) {
156 return undef if $noerr ;
157 die "invalid urlencoded string: $text\n " ;
162 register_format
( 'pve-configid' , \
& pve_verify_configid
);
163 sub pve_verify_configid
{
164 my ( $id, $noerr ) = @_ ;
166 if ( $id !~ m/^[a-z][a-z0-9_]+$/i ) {
167 return undef if $noerr ;
168 die "invalid configuration ID ' $id ' \n " ;
173 PVE
:: JSONSchema
:: register_format
( 'pve-storage-id' , \
& parse_storage_id
);
174 sub parse_storage_id
{
175 my ( $storeid, $noerr ) = @_ ;
177 if ( $storeid !~ m/^[a-z][a-z0-9\-\_\.]*[a-z0-9]$/i ) {
178 return undef if $noerr ;
179 die "storage ID ' $storeid ' contains illegal characters \n " ;
185 register_format
( 'pve-vmid' , \
& pve_verify_vmid
);
186 sub pve_verify_vmid
{
187 my ( $vmid, $noerr ) = @_ ;
189 if ( $vmid !~ m/^[1-9][0-9]{2,8}$/ ) {
190 return undef if $noerr ;
191 die "value does not look like a valid VM ID \n " ;
196 register_format
( 'pve-node' , \
& pve_verify_node_name
);
197 sub pve_verify_node_name
{
198 my ( $node, $noerr ) = @_ ;
200 if ( $node !~ m/^([a-zA-Z0-9]([a-zA-Z0-9\-]*[a-zA-Z0-9])?)$/ ) {
201 return undef if $noerr ;
202 die "value does not look like a valid node name \n " ;
207 register_format
( 'mac-addr' , \
& pve_verify_mac_addr
);
208 sub pve_verify_mac_addr
{
209 my ( $mac_addr, $noerr ) = @_ ;
211 if ( $mac_addr !~ m/^[a-f0-9][02468ace](?::[a-f0-9]{2}){5}$/i ) {
212 return undef if $noerr ;
213 die "value does not look like a valid unicast MAC address \n " ;
218 register_standard_option
( 'mac-addr' , {
220 description
=> 'Unicast MAC address.' ,
221 format_description
=> "XX:XX:XX:XX:XX:XX" ,
223 format
=> 'mac-addr' ,
226 register_format
( 'ipv4' , \
& pve_verify_ipv4
);
227 sub pve_verify_ipv4
{
228 my ( $ipv4, $noerr ) = @_ ;
230 if ( $ipv4 !~ m/^(?:$IPV4RE)$/ ) {
231 return undef if $noerr ;
232 die "value does not look like a valid IPv4 address \n " ;
237 register_format
( 'ipv6' , \
& pve_verify_ipv6
);
238 sub pve_verify_ipv6
{
239 my ( $ipv6, $noerr ) = @_ ;
241 if ( $ipv6 !~ m/^(?:$IPV6RE)$/ ) {
242 return undef if $noerr ;
243 die "value does not look like a valid IPv6 address \n " ;
248 register_format
( 'ip' , \
& pve_verify_ip
);
250 my ( $ip, $noerr ) = @_ ;
252 if ( $ip !~ m/^(?:(?:$IPV4RE)|(?:$IPV6RE))$/ ) {
253 return undef if $noerr ;
254 die "value does not look like a valid IP address \n " ;
259 my $ipv4_mask_hash = {
276 '255.255.128.0' => 17 ,
277 '255.255.192.0' => 18 ,
278 '255.255.224.0' => 19 ,
279 '255.255.240.0' => 20 ,
280 '255.255.248.0' => 21 ,
281 '255.255.252.0' => 22 ,
282 '255.255.254.0' => 23 ,
283 '255.255.255.0' => 24 ,
284 '255.255.255.128' => 25 ,
285 '255.255.255.192' => 26 ,
286 '255.255.255.224' => 27 ,
287 '255.255.255.240' => 28 ,
288 '255.255.255.248' => 29 ,
289 '255.255.255.252' => 30 ,
290 '255.255.255.254' => 31 ,
291 '255.255.255.255' => 32 ,
294 register_format
( 'ipv4mask' , \
& pve_verify_ipv4mask
);
295 sub pve_verify_ipv4mask
{
296 my ( $mask, $noerr ) = @_ ;
298 if (! defined ( $ipv4_mask_hash ->{ $mask })) {
299 return undef if $noerr ;
300 die "value does not look like a valid IP netmask \n " ;
305 register_format
( 'CIDRv6' , \
& pve_verify_cidrv6
);
306 sub pve_verify_cidrv6
{
307 my ( $cidr, $noerr ) = @_ ;
309 if ( $cidr =~ m!^(?:$IPV6RE)(?:/(\d+))$! && ( $1 > 7 ) && ( $1 <= 128 )) {
313 return undef if $noerr ;
314 die "value does not look like a valid IPv6 CIDR network \n " ;
317 register_format
( 'CIDRv4' , \
& pve_verify_cidrv4
);
318 sub pve_verify_cidrv4
{
319 my ( $cidr, $noerr ) = @_ ;
321 if ( $cidr =~ m!^(?:$IPV4RE)(?:/(\d+))$! && ( $1 > 7 ) && ( $1 <= 32 )) {
325 return undef if $noerr ;
326 die "value does not look like a valid IPv4 CIDR network \n " ;
329 register_format
( 'CIDR' , \
& pve_verify_cidr
);
330 sub pve_verify_cidr
{
331 my ( $cidr, $noerr ) = @_ ;
333 if (!( pve_verify_cidrv4
( $cidr, 1 ) ||
334 pve_verify_cidrv6
( $cidr, 1 )))
336 return undef if $noerr ;
337 die "value does not look like a valid CIDR network \n " ;
343 register_format
( 'pve-ipv4-config' , \
& pve_verify_ipv4_config
);
344 sub pve_verify_ipv4_config
{
345 my ( $config, $noerr ) = @_ ;
347 return $config if $config =~ /^(?:dhcp|manual)$/ ||
348 pve_verify_cidrv4
( $config, 1 );
349 return undef if $noerr ;
350 die "value does not look like a valid ipv4 network configuration \n " ;
353 register_format
( 'pve-ipv6-config' , \
& pve_verify_ipv6_config
);
354 sub pve_verify_ipv6_config
{
355 my ( $config, $noerr ) = @_ ;
357 return $config if $config =~ /^(?:auto|dhcp|manual)$/ ||
358 pve_verify_cidrv6
( $config, 1 );
359 return undef if $noerr ;
360 die "value does not look like a valid ipv6 network configuration \n " ;
363 register_format
( 'email' , \
& pve_verify_email
);
364 sub pve_verify_email
{
365 my ( $email, $noerr ) = @_ ;
367 if ( $email !~ /^[\w\+\-\~]+(\.[\w\+\-\~]+)*@[a-zA-Z0-9\-]+(\.[a-zA-Z0-9\-]+)*$/ ) {
368 return undef if $noerr ;
369 die "value does not look like a valid email address \n " ;
374 register_format
( 'dns-name' , \
& pve_verify_dns_name
);
375 sub pve_verify_dns_name
{
376 my ( $name, $noerr ) = @_ ;
378 my $namere = "([a-zA-Z0-9]([a-zA-Z0-9\-]*[a-zA-Z0-9])?)" ;
380 if ( $name !~ /^(${namere}\.)*${namere}$/ ) {
381 return undef if $noerr ;
382 die "value does not look like a valid DNS name \n " ;
387 # network interface name
388 register_format
( 'pve-iface' , \
& pve_verify_iface
);
389 sub pve_verify_iface
{
390 my ( $id, $noerr ) = @_ ;
392 if ( $id !~ m/^[a-z][a-z0-9_]{1,20}([:\.]\d+)?$/i ) {
393 return undef if $noerr ;
394 die "invalid network interface name ' $id ' \n " ;
399 # general addresses by name or IP
400 register_format
( 'address' , \
& pve_verify_address
);
401 sub pve_verify_address
{
402 my ( $addr, $noerr ) = @_ ;
404 if (!( pve_verify_ip
( $addr, 1 ) ||
405 pve_verify_dns_name
( $addr, 1 )))
407 return undef if $noerr ;
408 die "value does not look like a valid address: $addr\n " ;
413 register_format
( 'disk-size' , \
& pve_verify_disk_size
);
414 sub pve_verify_disk_size
{
415 my ( $size, $noerr ) = @_ ;
416 if (! defined ( parse_size
( $size ))) {
417 return undef if $noerr ;
418 die "value does not look like a valid disk size: $size\n " ;
423 register_standard_option
( 'spice-proxy' , {
424 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 reasonable setting is to use same node you use to connect to the API (This is window.location.hostname for the JS GUI)." ,
425 type
=> 'string' , format
=> 'address' ,
428 register_standard_option
( 'remote-viewer-config' , {
429 description
=> "Returned values can be directly passed to the 'remote-viewer' application." ,
430 additionalProperties
=> 1 ,
432 type
=> { type
=> 'string' },
433 password
=> { type
=> 'string' },
434 proxy
=> { type
=> 'string' },
435 host
=> { type
=> 'string' },
436 'tls-port' => { type
=> 'integer' },
440 register_format
( 'pve-startup-order' , \
& pve_verify_startup_order
);
441 sub pve_verify_startup_order
{
442 my ( $value, $noerr ) = @_ ;
444 return $value if pve_parse_startup_order
( $value );
446 return undef if $noerr ;
448 die "unable to parse startup options \n " ;
453 type
=> 'number' , minimum
=> '0' ,
454 format_description
=> 'LIMIT' ,
457 my $bwlimit_format = {
460 description
=> 'default bandwidth limit in MiB/s' ,
464 description
=> 'bandwidth limit in MiB/s for restoring guests from backups' ,
468 description
=> 'bandwidth limit in MiB/s for migrating guests' ,
472 description
=> 'bandwidth limit in MiB/s for cloning disks' ,
476 description
=> 'bandwidth limit in MiB/s for moving disks' ,
479 register_format
( 'bwlimit' , $bwlimit_format );
480 register_standard_option
( 'bwlimit' , {
481 description
=> "Set bandwidth/io limits various operations." ,
484 format
=> $bwlimit_format,
487 sub pve_parse_startup_order
{
490 return undef if ! $value ;
494 foreach my $p ( split ( /,/ , $value )) {
495 next if $p =~ m/^\s*$/ ;
497 if ( $p =~ m/^(order=)?(\d+)$/ ) {
499 } elsif ( $p =~ m/^up=(\d+)$/ ) {
501 } elsif ( $p =~ m/^down=(\d+)$/ ) {
511 PVE
:: JSONSchema
:: register_standard_option
( 'pve-startup-order' , {
512 description
=> "Startup and shutdown behavior. Order is a non-negative number defining the general startup order. Shutdown in done with reverse ordering. Additionally you can set the 'up' or 'down' delay in seconds, which specifies a delay to wait before the next VM is started or stopped." ,
514 type
=> 'string' , format
=> 'pve-startup-order' ,
515 typetext
=> '[[order=]\d+] [,up=\d+] [,down=\d+] ' ,
519 my ( $format, $value, $path ) = @_ ;
521 return parse_property_string
( $format, $value, $path ) if ref ( $format ) eq 'HASH' ;
522 return if $format eq 'regex' ;
524 if ( $format =~ m/^(.*)-a?list$/ ) {
526 my $code = $format_list ->{ $1 };
528 die "undefined format ' $format ' \n " if ! $code ;
530 # Note: we allow empty lists
531 foreach my $v ( split_list
( $value )) {
535 } elsif ( $format =~ m/^(.*)-opt$/ ) {
537 my $code = $format_list ->{ $1 };
539 die "undefined format ' $format ' \n " if ! $code ;
541 return if ! $value ; # allow empty string
547 my $code = $format_list ->{ $format };
549 die "undefined format ' $format ' \n " if ! $code ;
551 return parse_property_string
( $code, $value, $path ) if ref ( $code ) eq 'HASH' ;
559 return undef if $value !~ m/^(\d+(\.\d+)?)([KMGT])?$/ ;
560 my ( $size, $unit ) = ( $1, $3 );
563 $size = $size * 1024 ;
564 } elsif ( $unit eq 'M' ) {
565 $size = $size * 1024 * 1024 ;
566 } elsif ( $unit eq 'G' ) {
567 $size = $size * 1024 * 1024 * 1024 ;
568 } elsif ( $unit eq 'T' ) {
569 $size = $size * 1024 * 1024 * 1024 * 1024 ;
580 my $kb = int ( $size/1024 );
581 return $size if $kb*1024 != $size ;
583 my $mb = int ( $kb/1024 );
584 return "${kb}K" if $mb*1024 != $kb ;
586 my $gb = int ( $mb/1024 );
587 return "${mb}M" if $gb*1024 != $mb ;
589 my $tb = int ( $gb/1024 );
590 return "${gb}G" if $tb*1024 != $gb ;
597 return 1 if $bool =~ m/^(1|on|yes|true)$/i ;
598 return 0 if $bool =~ m/^(0|off|no|false)$/i ;
602 sub parse_property_string
{
603 my ( $format, $data, $path, $additional_properties ) = @_ ;
605 # In property strings we default to not allowing additional properties
606 $additional_properties = 0 if ! defined ( $additional_properties );
608 # Support named formats here, too:
610 if ( my $desc = $format_list ->{ $format }) {
613 die "unknown format: $format\n " ;
615 } elsif ( ref ( $format ) ne 'HASH' ) {
616 die "unexpected format value of type " . ref ( $format ). " \n " ;
622 foreach my $part ( split ( /,/ , $data )) {
623 next if $part =~ /^\s*$/ ;
625 if ( $part =~ /^([^=]+)=(.+)$/ ) {
626 my ( $k, $v ) = ( $1, $2 );
627 die "duplicate key in comma-separated list property: $k\n " if defined ( $res ->{ $k });
628 my $schema = $format ->{ $k };
629 if ( my $alias = $schema ->{ alias
}) {
630 if ( my $key_alias = $schema ->{ keyAlias
}) {
631 die "key alias ' $key_alias ' is already defined \n " if defined ( $res ->{ $key_alias });
632 $res ->{ $key_alias } = $k ;
635 $schema = $format ->{ $k };
638 die "invalid key in comma-separated list property: $k\n " if ! $schema ;
639 if ( $schema ->{ type
} && $schema ->{ type
} eq 'boolean' ) {
640 $v = parse_boolean
( $v ) // $v ;
643 } elsif ( $part !~ /=/ ) {
644 die "duplicate key in comma-separated list property: $default_key\n " if $default_key ;
645 foreach my $key ( keys %$format ) {
646 if ( $format ->{ $key }->{ default_key
}) {
648 if (! $res ->{ $default_key }) {
649 $res ->{ $default_key } = $part ;
652 die "duplicate key in comma-separated list property: $default_key\n " ;
655 die "value without key, but schema does not define a default key \n " if ! $default_key ;
657 die "missing key in comma-separated list property \n " ;
662 check_object
( $path, $format, $res, $additional_properties, $errors );
663 if ( scalar ( %$errors )) {
664 raise
"format error \n " , errors
=> $errors ;
671 my ( $errors, $path, $msg ) = @_ ;
673 $path = '_root' if ! $path ;
675 if ( $errors ->{ $path }) {
676 $errors ->{ $path } = join ( ' \n ' , $errors ->{ $path }, $msg );
678 $errors ->{ $path } = $msg ;
685 # see 'man perlretut'
686 return $value =~ /^[+-]?(\d+\.\d+|\d+\.|\.\d+|\d+)([eE][+-]?\d+)?$/ ;
692 return $value =~ m/^[+-]?\d+$/ ;
696 my ( $path, $type, $value, $errors ) = @_ ;
700 if (! defined ( $value )) {
701 return 1 if $type eq 'null' ;
705 if ( my $tt = ref ( $type )) {
706 if ( $tt eq 'ARRAY' ) {
707 foreach my $t ( @$type ) {
709 check_type
( $path, $t, $value, $tmperr );
710 return 1 if ! scalar ( %$tmperr );
712 my $ttext = join ( '|' , @$type );
713 add_error
( $errors, $path, "type check (' $ttext ') failed" );
715 } elsif ( $tt eq 'HASH' ) {
717 check_prop
( $value, $type, $path, $tmperr );
718 return 1 if ! scalar ( %$tmperr );
719 add_error
( $errors, $path, "type check failed" );
722 die "internal error - got reference type ' $tt '" ;
727 return 1 if $type eq 'any' ;
729 if ( $type eq 'null' ) {
730 if ( defined ( $value )) {
731 add_error
( $errors, $path, "type check (' $type ') failed - value is not null" );
737 my $vt = ref ( $value );
739 if ( $type eq 'array' ) {
740 if (! $vt || $vt ne 'ARRAY' ) {
741 add_error
( $errors, $path, "type check (' $type ') failed" );
745 } elsif ( $type eq 'object' ) {
746 if (! $vt || $vt ne 'HASH' ) {
747 add_error
( $errors, $path, "type check (' $type ') failed" );
751 } elsif ( $type eq 'coderef' ) {
752 if (! $vt || $vt ne 'CODE' ) {
753 add_error
( $errors, $path, "type check (' $type ') failed" );
757 } elsif ( $type eq 'string' && $vt eq 'Regexp' ) {
758 # qr// regexes can be used as strings and make sense for format=regex
762 add_error
( $errors, $path, "type check (' $type ') failed - got $vt " );
765 if ( $type eq 'string' ) {
766 return 1 ; # nothing to check ?
767 } elsif ( $type eq 'boolean' ) {
768 #if ($value =~ m/^(1|true|yes|on)$/i) {
771 #} elsif ($value =~ m/^(0|false|no|off)$/i) {
772 } elsif ( $value eq '0' ) {
773 return 1 ; # return success (not value)
775 add_error
( $errors, $path, "type check (' $type ') failed - got ' $value '" );
778 } elsif ( $type eq 'integer' ) {
779 if (! is_integer
( $value )) {
780 add_error
( $errors, $path, "type check (' $type ') failed - got ' $value '" );
784 } elsif ( $type eq 'number' ) {
785 if (! is_number
( $value )) {
786 add_error
( $errors, $path, "type check (' $type ') failed - got ' $value '" );
791 return 1 ; # no need to verify unknown types
801 my ( $path, $schema, $value, $additional_properties, $errors ) = @_ ;
803 # print "Check Object " . Dumper($value) . "\nSchema: " . Dumper($schema);
805 my $st = ref ( $schema );
806 if (! $st || $st ne 'HASH' ) {
807 add_error
( $errors, $path, "Invalid schema definition." );
811 my $vt = ref ( $value );
812 if (! $vt || $vt ne 'HASH' ) {
813 add_error
( $errors, $path, "an object is required" );
817 foreach my $k ( keys %$schema ) {
818 check_prop
( $value ->{ $k }, $schema ->{ $k }, $path ?
" $path . $k " : $k, $errors );
821 foreach my $k ( keys %$value ) {
823 my $newpath = $path ?
" $path . $k " : $k ;
825 if ( my $subschema = $schema ->{ $k }) {
826 if ( my $requires = $subschema ->{ requires
}) {
827 if ( ref ( $requires )) {
828 #print "TEST: " . Dumper($value) . "\n", Dumper($requires) ;
829 check_prop
( $value, $requires, $path, $errors );
830 } elsif (! defined ( $value ->{ $requires })) {
831 add_error
( $errors, $path ?
" $path . $requires " : $requires,
832 "missing property - ' $newpath ' requires this property" );
836 next ; # value is already checked above
839 if ( defined ( $additional_properties ) && ! $additional_properties ) {
840 add_error
( $errors, $newpath, "property is not defined in schema " .
841 "and the schema does not allow additional properties" );
844 check_prop
( $value ->{ $k }, $additional_properties, $newpath, $errors )
845 if ref ( $additional_properties );
849 sub check_object_warn
{
850 my ( $path, $schema, $value, $additional_properties ) = @_ ;
852 check_object
( $path, $schema, $value, $additional_properties, $errors );
853 if ( scalar ( %$errors )) {
854 foreach my $k ( keys %$errors ) {
855 warn "parse error: $k : $errors ->{ $k } \n " ;
863 my ( $value, $schema, $path, $errors ) = @_ ;
865 die "internal error - no schema" if ! $schema ;
866 die "internal error" if ! $errors ;
868 #print "check_prop $path\n" if $value;
870 my $st = ref ( $schema );
871 if (! $st || $st ne 'HASH' ) {
872 add_error
( $errors, $path, "Invalid schema definition." );
876 # if it extends another schema, it must pass that schema as well
877 if ( $schema ->{ extends
}) {
878 check_prop
( $value, $schema ->{ extends
}, $path, $errors );
881 if (! defined ( $value )) {
882 return if $schema ->{ type
} && $schema ->{ type
} eq 'null' ;
883 if (! $schema ->{ optional
} && ! $schema ->{ alias
} && ! $schema ->{ group
}) {
884 add_error
( $errors, $path, "property is missing and it is not optional" );
889 return if ! check_type
( $path, $schema ->{ type
}, $value, $errors );
891 if ( $schema ->{ disallow
}) {
893 if ( check_type
( $path, $schema ->{ disallow
}, $value, $tmperr )) {
894 add_error
( $errors, $path, "disallowed value was matched" );
899 if ( my $vt = ref ( $value )) {
901 if ( $vt eq 'ARRAY' ) {
902 if ( $schema ->{ items
}) {
903 my $it = ref ( $schema ->{ items
});
904 if ( $it && $it eq 'ARRAY' ) {
905 #die "implement me $path: $vt " . Dumper($schema) ."\n". Dumper($value);
906 die "not implemented" ;
909 foreach my $el ( @$value ) {
910 check_prop
( $el, $schema ->{ items
}, "${path}[ $ind ]" , $errors );
916 } elsif ( $schema ->{ properties
} || $schema ->{ additionalProperties
}) {
917 check_object
( $path, defined ( $schema ->{ properties
}) ?
$schema ->{ properties
} : {},
918 $value, $schema ->{ additionalProperties
}, $errors );
924 if ( my $format = $schema ->{ format
}) {
925 eval { check_format
( $format, $value, $path ); };
927 add_error
( $errors, $path, "invalid format - $@ " );
932 if ( my $pattern = $schema ->{ pattern
}) {
933 if ( $value !~ m/^$pattern$/ ) {
934 add_error
( $errors, $path, "value does not match the regex pattern" );
939 if ( defined ( my $max = $schema ->{ maxLength
})) {
940 if ( length ( $value ) > $max ) {
941 add_error
( $errors, $path, "value may only be $max characters long" );
946 if ( defined ( my $min = $schema ->{ minLength
})) {
947 if ( length ( $value ) < $min ) {
948 add_error
( $errors, $path, "value must be at least $min characters long" );
953 if ( is_number
( $value )) {
954 if ( defined ( my $max = $schema ->{ maximum
})) {
956 add_error
( $errors, $path, "value must have a maximum value of $max " );
961 if ( defined ( my $min = $schema ->{ minimum
})) {
963 add_error
( $errors, $path, "value must have a minimum value of $min " );
969 if ( my $ea = $schema ->{ enum
}) {
972 foreach my $ev ( @$ea ) {
979 add_error
( $errors, $path, "value ' $value ' does not have a value in the enumeration '" .
980 join ( ", " , @$ea ) . "'" );
987 my ( $instance, $schema, $errmsg ) = @_ ;
990 $errmsg = "Parameter verification failed. \n " if ! $errmsg ;
992 # todo: cycle detection is only needed for debugging, I guess
993 # we can disable that in the final release
994 # todo: is there a better/faster way to detect cycles?
996 find_cycle
( $instance, sub { $cycles = 1 });
998 add_error
( $errors, undef , "data structure contains recursive cycles" );
1000 check_prop
( $instance, $schema, '' , $errors );
1003 if ( scalar ( %$errors )) {
1004 raise
$errmsg, code
=> HTTP_BAD_REQUEST
, errors
=> $errors ;
1010 my $schema_valid_types = [ "string" , "object" , "coderef" , "array" , "boolean" , "number" , "integer" , "null" , "any" ];
1011 my $default_schema_noref = {
1012 description
=> "This is the JSON Schema for JSON Schemas." ,
1013 type
=> [ "object" ],
1014 additionalProperties
=> 0 ,
1017 type
=> [ "string" , "array" ],
1018 description
=> "This is a type definition value. This can be a simple type, or a union type" ,
1023 enum
=> $schema_valid_types,
1025 enum
=> $schema_valid_types,
1029 description
=> "This indicates that the instance property in the instance object is not required." ,
1035 description
=> "This is a definition for the properties of an object value" ,
1041 description
=> "When the value is an array, this indicates the schema to use to validate each item in an array" ,
1045 additionalProperties
=> {
1046 type
=> [ "boolean" , "object" ],
1047 description
=> "This provides a default property definition for all properties that are not explicitly defined in an object type definition." ,
1054 description
=> "This indicates the minimum value for the instance property when the type of the instance value is a number." ,
1059 description
=> "This indicates the maximum value for the instance property when the type of the instance value is a number." ,
1063 description
=> "When the instance value is a string, this indicates minimum length of the string" ,
1070 description
=> "When the instance value is a string, this indicates maximum length of the string." ,
1076 description
=> "A text representation of the type (used to generate documentation)." ,
1081 description
=> "When the instance value is a string, this provides a regular expression that a instance string value should match in order to be valid." ,
1088 description
=> "This provides an enumeration of possible values that are valid for the instance property." ,
1093 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)." ,
1095 verbose_description
=> {
1098 description
=> "This provides a more verbose description." ,
1100 format_description
=> {
1103 description
=> "This provides a shorter (usually just one word) description for a property used to generate descriptions for comma separated list property strings." ,
1108 description
=> "This provides the title of the property" ,
1113 description
=> "This is used to provide rendering hints to format cli command output." ,
1116 type
=> [ "string" , "object" ],
1118 description
=> "indicates a required property or a schema that must be validated if this property is present" ,
1121 type
=> [ "string" , "object" ],
1123 description
=> "This indicates what format the data is among some predefined formats which may include: \n\n date - a string following the ISO format \n address \n schema - a schema definition object \n person \n page \n html - a string representing HTML" ,
1128 description
=> "Whether this is the default key in a comma separated list property string." ,
1133 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." ,
1138 description
=> "Allows to store the current 'key' as value of another property. Only valid if used together with 'alias'." ,
1139 requires
=> 'alias' ,
1144 description
=> "This indicates the default for the instance property."
1148 description
=> "Bash completion function. This function should return a list of possible values." ,
1154 description
=> "This attribute may take the same values as the \" type \" attribute, however if the instance matches the type or if this value is an array and the instance matches any type or schema in the array, then this instance is not valid." ,
1159 description
=> "This indicates the schema extends the given schema. All instances of this schema must be valid to by the extended schema also." ,
1162 # this is from hyper schema
1165 description
=> "This defines the link relations of the instance objects" ,
1172 description
=> "This defines the target URL for the relation and can be parameterized using {propertyName} notation. It should be resolved as a URI-reference relative to the URI that was used to retrieve the instance document" ,
1176 description
=> "This is the name of the link relation" ,
1182 description
=> "For submission links, this defines the method that should be used to access the target resource" ,
1191 description
=> "For CLI context, this defines the maximal width to print before truncating" ,
1197 my $default_schema = Storable
:: dclone
( $default_schema_noref );
1199 $default_schema ->{ properties
}->{ properties
}->{ additionalProperties
} = $default_schema ;
1200 $default_schema ->{ properties
}->{ additionalProperties
}->{ properties
} = $default_schema ->{ properties
};
1202 $default_schema ->{ properties
}->{ items
}->{ properties
} = $default_schema ->{ properties
};
1203 $default_schema ->{ properties
}->{ items
}->{ additionalProperties
} = 0 ;
1205 $default_schema ->{ properties
}->{ disallow
}->{ properties
} = $default_schema ->{ properties
};
1206 $default_schema ->{ properties
}->{ disallow
}->{ additionalProperties
} = 0 ;
1208 $default_schema ->{ properties
}->{ requires
}->{ properties
} = $default_schema ->{ properties
};
1209 $default_schema ->{ properties
}->{ requires
}->{ additionalProperties
} = 0 ;
1211 $default_schema ->{ properties
}->{ extends
}->{ properties
} = $default_schema ->{ properties
};
1212 $default_schema ->{ properties
}->{ extends
}->{ additionalProperties
} = 0 ;
1214 my $method_schema = {
1216 additionalProperties
=> 0 ,
1219 description
=> "This a description of the method" ,
1224 description
=> "This indicates the name of the function to call." ,
1227 additionalProperties
=> 1 ,
1242 description
=> "The HTTP method name." ,
1243 enum
=> [ 'GET' , 'POST' , 'PUT' , 'DELETE' ],
1248 description
=> "Method needs special privileges - only pvedaemon can execute it" ,
1253 description
=> "Method downloads the file content (filename is the return value of the method)." ,
1258 description
=> "A parameter name. If specified, all calls to this method are proxied to the host contained in that parameter." ,
1261 proxyto_callback
=> {
1263 description
=> "A function which is called to resolve the proxyto attribute. The default implementation returns the value of the 'proxyto' parameter." ,
1268 description
=> "Required access permissions. By default only 'root' is allowed to access this method." ,
1270 additionalProperties
=> 0 ,
1273 description
=> "Describe access permissions." ,
1277 description
=> "A simply way to allow access for 'all' authenticated users. Value 'world' is used to allow access without credentials." ,
1279 enum
=> [ 'all' , 'world' ],
1283 description
=> "Array of permission checks (prefix notation)." ,
1290 description
=> "Used internally" ,
1294 description
=> "Used internally" ,
1299 description
=> "path for URL matching (uri template)" ,
1301 fragmentDelimiter
=> {
1303 description
=> "A way to override the default fragment delimiter '/'. This only works on a whole sub-class. You can set this to the empty string to match the whole rest of the URI." ,
1308 description
=> "JSON Schema for parameters." ,
1313 description
=> "JSON Schema for return value." ,
1318 description
=> "method implementation (code reference)" ,
1323 description
=> "Delegate call to this class (perl class string)." ,
1326 additionalProperties
=> 0 ,
1332 fragmentDelimiter
=> { optional
=> 1 }
1340 sub validate_schema
{
1343 my $errmsg = "internal error - unable to verify schema \n " ;
1344 validate
( $schema, $default_schema, $errmsg );
1347 sub validate_method_info
{
1350 my $errmsg = "internal error - unable to verify method info \n " ;
1351 validate
( $info, $method_schema, $errmsg );
1353 validate_schema
( $info ->{ parameters
}) if $info ->{ parameters
};
1354 validate_schema
( $info ->{ returns
}) if $info ->{ returns
};
1357 # run a self test on load
1358 # make sure we can verify the default schema
1359 validate_schema
( $default_schema_noref );
1360 validate_schema
( $method_schema );
1362 # and now some utility methods (used by pve api)
1363 sub method_get_child_link
{
1366 return undef if ! $info ;
1368 my $schema = $info ->{ returns
};
1369 return undef if ! $schema || ! $schema ->{ type
} || $schema ->{ type
} ne 'array' ;
1371 my $links = $schema ->{ links
};
1372 return undef if ! $links ;
1375 foreach my $lnk ( @$links ) {
1376 if ( $lnk ->{ href
} && $lnk ->{ rel
} && ( $lnk ->{ rel
} eq 'child' )) {
1385 # a way to parse command line parameters, using a
1386 # schema to configure Getopt::Long
1388 my ( $schema, $args, $arg_param, $fixed_param, $param_mapping_hash ) = @_ ;
1390 if (! $schema || ! $schema ->{ properties
}) {
1391 raise
( "too many arguments \n " , code
=> HTTP_BAD_REQUEST
)
1392 if scalar ( @$args ) != 0 ;
1397 if ( $arg_param && ! ref ( $arg_param )) {
1398 my $pd = $schema ->{ properties
}->{ $arg_param };
1399 die "expected list format $pd ->{format}"
1400 if !( $pd && $pd ->{ format
} && $pd ->{ format
} =~ m/-list/ );
1401 $list_param = $arg_param ;
1404 my @interactive = ();
1406 foreach my $prop ( keys %{ $schema ->{ properties
}}) {
1407 my $pd = $schema ->{ properties
}->{ $prop };
1408 next if $list_param && $prop eq $list_param ;
1409 next if defined ( $fixed_param ->{ $prop });
1411 my $mapping = $param_mapping_hash ->{ $prop };
1412 if ( $mapping && $mapping ->{ interactive
}) {
1413 # interactive parameters such as passwords: make the argument
1414 # optional and call the mapping function afterwards.
1415 push @getopt, " $prop :s" ;
1416 push @interactive, [ $prop, $mapping ->{ func
}];
1417 } elsif ( $pd ->{ type
} eq 'boolean' ) {
1418 push @getopt, " $prop :s" ;
1420 if ( $pd ->{ format
} && $pd ->{ format
} =~ m/-a?list/ ) {
1421 push @getopt, " $prop =s@" ;
1423 push @getopt, " $prop =s" ;
1428 Getopt
:: Long
:: Configure
( 'prefix_pattern=(--|-)' );
1431 raise
( "unable to parse option \n " , code
=> HTTP_BAD_REQUEST
)
1432 if ! Getopt
:: Long
:: GetOptionsFromArray
( $args, $opts, @getopt );
1436 $opts ->{ $list_param } = $args ;
1438 } elsif ( ref ( $arg_param )) {
1439 foreach my $arg_name ( @$arg_param ) {
1440 if ( $opts ->{ 'extra-args' }) {
1441 raise
( "internal error: extra-args must be the last argument \n " , code
=> HTTP_BAD_REQUEST
);
1443 if ( $arg_name eq 'extra-args' ) {
1444 $opts ->{ 'extra-args' } = $args ;
1448 raise
( "not enough arguments \n " , code
=> HTTP_BAD_REQUEST
) if ! @$args ;
1449 $opts ->{ $arg_name } = shift @$args ;
1451 raise
( "too many arguments \n " , code
=> HTTP_BAD_REQUEST
) if @$args ;
1453 raise
( "too many arguments \n " , code
=> HTTP_BAD_REQUEST
)
1454 if scalar ( @$args ) != 0 ;
1457 if ( ref ( $arg_param )) {
1458 foreach my $arg_name ( @$arg_param ) {
1459 if ( $arg_name eq 'extra-args' ) {
1460 $opts ->{ 'extra-args' } = [];
1462 raise
( "not enough arguments \n " , code
=> HTTP_BAD_REQUEST
);
1468 foreach my $entry ( @interactive ) {
1469 my ( $opt, $func ) = @$entry ;
1470 my $pd = $schema ->{ properties
}->{ $opt };
1471 my $value = $opts ->{ $opt };
1472 if ( defined ( $value ) || ! $pd ->{ optional
}) {
1473 $opts ->{ $opt } = $func ->( $value );
1477 # decode after Getopt as we are not sure how well it handles unicode
1478 foreach my $p ( keys %$opts ) {
1479 if (! ref ( $opts ->{ $p })) {
1480 $opts ->{ $p } = decode
( 'locale' , $opts ->{ $p });
1481 } elsif ( ref ( $opts ->{ $p }) eq 'ARRAY' ) {
1483 foreach my $v (@{ $opts ->{ $p }}) {
1484 push @$tmp, decode
( 'locale' , $v );
1487 } elsif ( ref ( $opts ->{ $p }) eq 'SCALAR' ) {
1488 $opts ->{ $p } = decode
( 'locale' , $$opts ->{ $p });
1490 raise
( "decoding options failed, unknown reference \n " , code
=> HTTP_BAD_REQUEST
);
1494 foreach my $p ( keys %$opts ) {
1495 if ( my $pd = $schema ->{ properties
}->{ $p }) {
1496 if ( $pd ->{ type
} eq 'boolean' ) {
1497 if ( $opts ->{ $p } eq '' ) {
1499 } elsif ( defined ( my $bool = parse_boolean
( $opts ->{ $p }))) {
1500 $opts ->{ $p } = $bool ;
1502 raise
( "unable to parse boolean option \n " , code
=> HTTP_BAD_REQUEST
);
1504 } elsif ( $pd ->{ format
}) {
1506 if ( $pd ->{ format
} =~ m/-list/ ) {
1507 # allow --vmid 100 --vmid 101 and --vmid 100,101
1508 # allow --dow mon --dow fri and --dow mon,fri
1509 $opts ->{ $p } = join ( "," , @{ $opts ->{ $p }}) if ref ( $opts ->{ $p }) eq 'ARRAY' ;
1510 } elsif ( $pd ->{ format
} =~ m/-alist/ ) {
1511 # we encode array as \0 separated strings
1512 # Note: CGI.pm also use this encoding
1513 if ( scalar (@{ $opts ->{ $p }}) != 1 ) {
1514 $opts ->{ $p } = join ( "\0" , @{ $opts ->{ $p }});
1516 # st that split_list knows it is \0 terminated
1517 my $v = $opts ->{ $p }->[ 0 ];
1518 $opts ->{ $p } = " $v\0 " ;
1525 foreach my $p ( keys %$fixed_param ) {
1526 $opts ->{ $p } = $fixed_param ->{ $p };
1532 # A way to parse configuration data by giving a json schema
1534 my ( $schema, $filename, $raw ) = @_ ;
1536 # do fast check (avoid validate_schema($schema))
1537 die "got strange schema" if ! $schema ->{ type
} ||
1538 ! $schema ->{ properties
} || $schema ->{ type
} ne 'object' ;
1542 while ( $raw =~ /^\s*(.+?)\s*$/gm ) {
1545 next if $line =~ /^#/ ;
1547 if ( $line =~ m/^(\S+?):\s*(.*)$/ ) {
1550 if ( $schema ->{ properties
}->{ $key } &&
1551 $schema ->{ properties
}->{ $key }->{ type
} eq 'boolean' ) {
1553 $value = parse_boolean
( $value ) // $value ;
1555 $cfg ->{ $key } = $value ;
1557 warn "ignore config line: $line\n "
1562 check_prop
( $cfg, $schema, '' , $errors );
1564 foreach my $k ( keys %$errors ) {
1565 warn "parse error in ' $filename ' - ' $k ': $errors ->{ $k } \n " ;
1572 # generate simple key/value file
1574 my ( $schema, $filename, $cfg ) = @_ ;
1576 # do fast check (avoid validate_schema($schema))
1577 die "got strange schema" if ! $schema ->{ type
} ||
1578 ! $schema ->{ properties
} || $schema ->{ type
} ne 'object' ;
1580 validate
( $cfg, $schema, "validation error in ' $filename ' \n " );
1584 foreach my $k ( keys %$cfg ) {
1585 $data .= " $k : $cfg ->{ $k } \n " ;
1591 # helpers used to generate our manual pages
1593 my $find_schema_default_key = sub {
1597 my $keyAliasProps = {};
1599 foreach my $key ( keys %$format ) {
1600 my $phash = $format ->{ $key };
1601 if ( $phash ->{ default_key
}) {
1602 die "multiple default keys in schema ( $default_key, $key ) \n "
1603 if defined ( $default_key );
1604 die "default key ' $key ' is an alias - this is not allowed \n "
1605 if defined ( $phash ->{ alias
});
1606 die "default key ' $key ' with keyAlias attribute is not allowed \n "
1607 if $phash ->{ keyAlias
};
1608 $default_key = $key ;
1610 my $key_alias = $phash ->{ keyAlias
};
1611 die "found keyAlias without 'alias definition for ' $key ' \n "
1612 if $key_alias && ! $phash ->{ alias
};
1614 if ( $phash ->{ alias
} && $key_alias ) {
1615 die "inconsistent keyAlias ' $key_alias ' definition"
1616 if defined ( $keyAliasProps ->{ $key_alias }) &&
1617 $keyAliasProps ->{ $key_alias } ne $phash ->{ alias
};
1618 $keyAliasProps ->{ $key_alias } = $phash ->{ alias
};
1622 return wantarray ?
( $default_key, $keyAliasProps ) : $default_key ;
1625 sub generate_typetext
{
1626 my ( $format, $list_enums ) = @_ ;
1628 my ( $default_key, $keyAliasProps ) = & $find_schema_default_key ( $format );
1633 my $add_option_string = sub {
1634 my ( $text, $optional ) = @_ ;
1640 $text = "[ $text ]" if $optional ;
1645 my $format_key_value = sub {
1646 my ( $key, $phash ) = @_ ;
1648 die "internal error" if defined ( $phash ->{ alias
});
1654 if ( my $desc = $phash ->{ format_description
}) {
1655 $typetext .= "< $desc >" ;
1656 } elsif ( my $text = $phash ->{ typetext
}) {
1658 } elsif ( my $enum = $phash ->{ enum
}) {
1659 if ( $list_enums || ( scalar ( @$enum ) <= 3 )) {
1660 $typetext .= '<' . join ( '|' , @$enum ) . '>' ;
1662 $typetext .= '<enum>' ;
1664 } elsif ( $phash ->{ type
} eq 'boolean' ) {
1665 $typetext .= '<1|0>' ;
1666 } elsif ( $phash ->{ type
} eq 'integer' ) {
1667 $typetext .= '<integer>' ;
1668 } elsif ( $phash ->{ type
} eq 'number' ) {
1669 $typetext .= '<number>' ;
1671 die "internal error: neither format_description nor typetext found for option ' $key '" ;
1674 if ( defined ( $default_key ) && ( $default_key eq $key )) {
1675 & $add_option_string ( "[ $keytext =] $typetext " , $phash ->{ optional
});
1677 & $add_option_string ( " $keytext = $typetext " , $phash ->{ optional
});
1683 my $cond_add_key = sub {
1686 return if $done ->{ $key }; # avoid duplicates
1690 my $phash = $format ->{ $key };
1692 return if ! $phash ; # should not happen
1694 return if $phash ->{ alias
};
1696 & $format_key_value ( $key, $phash );
1700 & $cond_add_key ( $default_key ) if defined ( $default_key );
1702 # add required keys first
1703 foreach my $key ( sort keys %$format ) {
1704 my $phash = $format ->{ $key };
1705 & $cond_add_key ( $key ) if $phash && ! $phash ->{ optional
};
1709 foreach my $key ( sort keys %$format ) {
1710 & $cond_add_key ( $key );
1713 foreach my $keyAlias ( sort keys %$keyAliasProps ) {
1714 & $add_option_string ( "< $keyAlias >=< $keyAliasProps ->{ $keyAlias }>" , 1 );
1720 sub print_property_string
{
1721 my ( $data, $format, $skip, $path ) = @_ ;
1723 if ( ref ( $format ) ne 'HASH' ) {
1724 my $schema = get_format
( $format );
1725 die "not a valid format: $format\n " if ! $schema ;
1730 check_object
( $path, $format, $data, undef , $errors );
1731 if ( scalar ( %$errors )) {
1732 raise
"format error" , errors
=> $errors ;
1735 my ( $default_key, $keyAliasProps ) = & $find_schema_default_key ( $format );
1740 my $add_option_string = sub {
1743 $res .= ',' if $add_sep ;
1748 my $format_value = sub {
1749 my ( $key, $value, $format ) = @_ ;
1751 if ( defined ( $format ) && ( $format eq 'disk-size' )) {
1752 return format_size
( $value );
1754 die "illegal value with commas for $key\n " if $value =~ /,/ ;
1759 my $done = { map { $_ => 1 } @$skip };
1761 my $cond_add_key = sub {
1762 my ( $key, $isdefault ) = @_ ;
1764 return if $done ->{ $key }; # avoid duplicates
1768 my $value = $data ->{ $key };
1770 return if ! defined ( $value );
1772 my $phash = $format ->{ $key };
1774 # try to combine values if we have key aliases
1775 if ( my $combine = $keyAliasProps ->{ $key }) {
1776 if ( defined ( my $combine_value = $data ->{ $combine })) {
1777 my $combine_format = $format ->{ $combine }->{ format
};
1778 my $value_str = & $format_value ( $key, $value, $phash ->{ format
});
1779 my $combine_str = & $format_value ( $combine, $combine_value, $combine_format );
1780 & $add_option_string ( "${value_str}=${combine_str}" );
1781 $done ->{ $combine } = 1 ;
1786 if ( $phash && $phash ->{ alias
}) {
1787 $phash = $format ->{ $phash ->{ alias
}};
1790 die "invalid key ' $key ' \n " if ! $phash ;
1791 die "internal error" if defined ( $phash ->{ alias
});
1793 my $value_str = & $format_value ( $key, $value, $phash ->{ format
});
1795 & $add_option_string ( $value_str );
1797 & $add_option_string ( " $key =${value_str}" );
1801 # add default key first
1802 & $cond_add_key ( $default_key, 1 ) if defined ( $default_key );
1804 # add required keys first
1805 foreach my $key ( sort keys %$data ) {
1806 my $phash = $format ->{ $key };
1807 & $cond_add_key ( $key ) if $phash && ! $phash ->{ optional
};
1811 foreach my $key ( sort keys %$data ) {
1812 & $cond_add_key ( $key );
1818 sub schema_get_type_text
{
1819 my ( $phash, $style ) = @_ ;
1821 my $type = $phash ->{ type
} || 'string' ;
1823 if ( $phash ->{ typetext
}) {
1824 return $phash ->{ typetext
};
1825 } elsif ( $phash ->{ format_description
}) {
1826 return "< $phash ->{format_description}>" ;
1827 } elsif ( $phash ->{ enum
}) {
1828 return "<" . join ( ' | ' , sort @{ $phash ->{ enum
}}) . ">" ;
1829 } elsif ( $phash ->{ pattern
}) {
1830 return $phash ->{ pattern
};
1831 } elsif ( $type eq 'integer' || $type eq 'number' ) {
1832 # NOTE: always access values as number (avoid converion to string)
1833 if ( defined ( $phash ->{ minimum
}) && defined ( $phash ->{ maximum
})) {
1834 return "< $type > (" . ( $phash ->{ minimum
} + 0 ) . " - " .
1835 ( $phash ->{ maximum
} + 0 ) . ")" ;
1836 } elsif ( defined ( $phash ->{ minimum
})) {
1837 return "< $type > (" . ( $phash ->{ minimum
} + 0 ) . " - N)" ;
1838 } elsif ( defined ( $phash ->{ maximum
})) {
1839 return "< $type > (-N - " . ( $phash ->{ maximum
} + 0 ) . ")" ;
1841 } elsif ( $type eq 'string' ) {
1842 if ( my $format = $phash ->{ format
}) {
1843 $format = get_format
( $format ) if ref ( $format ) ne 'HASH' ;
1844 if ( ref ( $format ) eq 'HASH' ) {
1846 $list_enums = 1 if $style && $style eq 'config-sub' ;
1847 return generate_typetext
( $format, $list_enums );