]>
git.proxmox.com Git - pve-common.git/blob - data/PVE/JSONSchema.pm
4b3364635469013e009e75208427147145702482
1 package PVE
:: JSONSchema
;
5 use Storable
; # for dclone
7 use Devel
:: Cycle
- quiet
; # todo: remove?
8 use PVE
:: Tools
qw(split_list) ;
9 use PVE
:: Exception
qw(raise) ;
10 use HTTP
:: Status
qw(:constants) ;
15 register_standard_option
19 # Note: This class implements something similar to JSON schema, but it is not 100% complete.
20 # see: http://tools.ietf.org/html/draft-zyp-json-schema-02
21 # see: http://json-schema.org/
23 # the code is similar to the javascript parser from http://code.google.com/p/jsonschema/
25 my $standard_options = {};
26 sub register_standard_option
{
27 my ( $name, $schema ) = @_ ;
29 die "standard option ' $name ' already registered \n "
30 if $standard_options ->{ $name };
32 $standard_options ->{ $name } = $schema ;
35 sub get_standard_option
{
36 my ( $name, $base ) = @_ ;
38 my $std = $standard_options ->{ $name };
39 die "no such standard option \n " if ! $std ;
41 my $res = $base || {};
43 foreach my $opt ( keys %$std ) {
45 $res ->{ $opt } = $std ->{ $opt };
51 register_standard_option
( 'pve-vmid' , {
52 description
=> "The (unique) ID of the VM." ,
53 type
=> 'integer' , format
=> 'pve-vmid' ,
57 register_standard_option
( 'pve-node' , {
58 description
=> "The cluster node name." ,
59 type
=> 'string' , format
=> 'pve-node' ,
62 register_standard_option
( 'pve-node-list' , {
63 description
=> "List of cluster node names." ,
64 type
=> 'string' , format
=> 'pve-node-list' ,
67 register_standard_option
( 'pve-iface' , {
68 description
=> "Network interface name." ,
69 type
=> 'string' , format
=> 'pve-iface' ,
70 minLength
=> 2 , maxLength
=> 20 ,
73 PVE
:: JSONSchema
:: register_standard_option
( 'pve-storage-id' , {
74 description
=> "The storage identifier." ,
75 type
=> 'string' , format
=> 'pve-storage-id' ,
82 my ( $format, $code ) = @_ ;
84 die "JSON schema format ' $format ' already registered \n "
85 if $format_list ->{ $format };
87 $format_list ->{ $format } = $code ;
90 # register some common type for pve
92 register_format
( 'string' , sub {}); # allow format => 'string-list'
94 register_format
( 'pve-configid' , \
& pve_verify_configid
);
95 sub pve_verify_configid
{
96 my ( $id, $noerr ) = @_ ;
98 if ( $id !~ m/^[a-z][a-z0-9_]+$/i ) {
99 return undef if $noerr ;
100 die "invalid configuration ID ' $id ' \n " ;
105 PVE
:: JSONSchema
:: register_format
( 'pve-storage-id' , \
& parse_storage_id
);
106 sub parse_storage_id
{
107 my ( $storeid, $noerr ) = @_ ;
109 if ( $storeid !~ m/^[a-z][a-z0-9\-\_\.]*[a-z0-9]$/i ) {
110 return undef if $noerr ;
111 die "storage ID ' $storeid ' contains illegal characters \n " ;
117 register_format
( 'pve-vmid' , \
& pve_verify_vmid
);
118 sub pve_verify_vmid
{
119 my ( $vmid, $noerr ) = @_ ;
121 if ( $vmid !~ m/^[1-9][0-9]+$/ ) {
122 return undef if $noerr ;
123 die "value does not look like a valid VM ID \n " ;
128 register_format
( 'pve-node' , \
& pve_verify_node_name
);
129 sub pve_verify_node_name
{
130 my ( $node, $noerr ) = @_ ;
132 if ( $node !~ m/^([a-zA-Z0-9]([a-zA-Z0-9\-]*[a-zA-Z0-9])?)$/ ) {
133 return undef if $noerr ;
134 die "value does not look like a valid node name \n " ;
139 register_format
( 'ipv4' , \
& pve_verify_ipv4
);
140 sub pve_verify_ipv4
{
141 my ( $ipv4, $noerr ) = @_ ;
143 if ( $ipv4 !~ m/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/ ||
144 !(( $1 > 0 ) && ( $1 < 255 ) &&
145 ( $2 <= 255 ) && ( $3 <= 255 ) &&
146 ( $4 > 0 ) && ( $4 < 255 ))) {
147 return undef if $noerr ;
148 die "value does not look like a valid IP address \n " ;
153 my $ipv4_mask_hash = {
170 '255.255.128.0' => 17 ,
171 '255.255.192.0' => 18 ,
172 '255.255.224.0' => 19 ,
173 '255.255.240.0' => 20 ,
174 '255.255.248.0' => 21 ,
175 '255.255.252.0' => 22 ,
176 '255.255.254.0' => 23 ,
177 '255.255.255.0' => 24 ,
178 '255.255.255.128' => 25 ,
179 '255.255.255.192' => 26 ,
180 '255.255.255.224' => 27 ,
181 '255.255.255.240' => 28 ,
182 '255.255.255.248' => 29 ,
183 '255.255.255.252' => 30
186 register_format
( 'ipv4mask' , \
& pve_verify_ipv4mask
);
187 sub pve_verify_ipv4mask
{
188 my ( $mask, $noerr ) = @_ ;
190 if (! defined ( $ipv4_mask_hash ->{ $mask })) {
191 return undef if $noerr ;
192 die "value does not look like a valid IP netmask \n " ;
197 register_format
( 'email' , \
& pve_verify_email
);
198 sub pve_verify_email
{
199 my ( $email, $noerr ) = @_ ;
201 # we use same regex as extjs Ext.form.VTypes.email
202 if ( $email !~ /^(\w+)([\-+.][\w]+)*@(\w[\-\w]*\.){1,5}([A-Za-z]){2,6}$/ ) {
203 return undef if $noerr ;
204 die "value does not look like a valid email address \n " ;
209 register_format
( 'dns-name' , \
& pve_verify_dns_name
);
210 sub pve_verify_dns_name
{
211 my ( $name, $noerr ) = @_ ;
213 my $namere = "([a-zA-Z0-9]([a-zA-Z0-9\-]*[a-zA-Z0-9])?)" ;
215 if ( $name !~ /^(${namere}\.)*${namere}$/ ) {
216 return undef if $noerr ;
217 die "value does not look like a valid DNS name \n " ;
222 # network interface name
223 register_format
( 'pve-iface' , \
& pve_verify_iface
);
224 sub pve_verify_iface
{
225 my ( $id, $noerr ) = @_ ;
227 if ( $id !~ m/^[a-z][a-z0-9_]{1,20}([:\.]\d+)?$/i ) {
228 return undef if $noerr ;
229 die "invalid network interface name ' $id ' \n " ;
235 my ( $format, $value ) = @_ ;
237 return if $format eq 'regex' ;
239 if ( $format =~ m/^(.*)-a?list$/ ) {
241 my $code = $format_list ->{ $1 };
243 die "undefined format ' $format ' \n " if ! $code ;
245 # Note: we allow empty lists
246 foreach my $v ( split_list
( $value )) {
250 } elsif ( $format =~ m/^(.*)-opt$/ ) {
252 my $code = $format_list ->{ $1 };
254 die "undefined format ' $format ' \n " if ! $code ;
256 return if ! $value ; # allow empty string
262 my $code = $format_list ->{ $format };
264 die "undefined format ' $format ' \n " if ! $code ;
271 my ( $errors, $path, $msg ) = @_ ;
273 $path = '_root' if ! $path ;
275 if ( $errors ->{ $path }) {
276 $errors ->{ $path } = join ( ' \n ' , $errors ->{ $path }, $msg );
278 $errors ->{ $path } = $msg ;
285 # see 'man perlretut'
286 return $value =~ /^[+-]?(\d+\.\d+|\d+\.|\.\d+|\d+)([eE][+-]?\d+)?$/ ;
292 return $value =~ m/^[+-]?\d+$/ ;
296 my ( $path, $type, $value, $errors ) = @_ ;
300 if (! defined ( $value )) {
301 return 1 if $type eq 'null' ;
305 if ( my $tt = ref ( $type )) {
306 if ( $tt eq 'ARRAY' ) {
307 foreach my $t ( @$type ) {
309 check_type
( $path, $t, $value, $tmperr );
310 return 1 if ! scalar ( %$tmperr );
312 my $ttext = join ( '|' , @$type );
313 add_error
( $errors, $path, "type check (' $ttext ') failed" );
315 } elsif ( $tt eq 'HASH' ) {
317 check_prop
( $value, $type, $path, $tmperr );
318 return 1 if ! scalar ( %$tmperr );
319 add_error
( $errors, $path, "type check failed" );
322 die "internal error - got reference type ' $tt '" ;
327 return 1 if $type eq 'any' ;
329 if ( $type eq 'null' ) {
330 if ( defined ( $value )) {
331 add_error
( $errors, $path, "type check (' $type ') failed - value is not null" );
337 my $vt = ref ( $value );
339 if ( $type eq 'array' ) {
340 if (! $vt || $vt ne 'ARRAY' ) {
341 add_error
( $errors, $path, "type check (' $type ') failed" );
345 } elsif ( $type eq 'object' ) {
346 if (! $vt || $vt ne 'HASH' ) {
347 add_error
( $errors, $path, "type check (' $type ') failed" );
351 } elsif ( $type eq 'coderef' ) {
352 if (! $vt || $vt ne 'CODE' ) {
353 add_error
( $errors, $path, "type check (' $type ') failed" );
359 add_error
( $errors, $path, "type check (' $type ') failed - got $vt " );
362 if ( $type eq 'string' ) {
363 return 1 ; # nothing to check ?
364 } elsif ( $type eq 'boolean' ) {
365 #if ($value =~ m/^(1|true|yes|on)$/i) {
368 #} elsif ($value =~ m/^(0|false|no|off)$/i) {
369 } elsif ( $value eq '0' ) {
372 add_error
( $errors, $path, "type check (' $type ') failed - got ' $value '" );
375 } elsif ( $type eq 'integer' ) {
376 if (! is_integer
( $value )) {
377 add_error
( $errors, $path, "type check (' $type ') failed - got ' $value '" );
381 } elsif ( $type eq 'number' ) {
382 if (! is_number
( $value )) {
383 add_error
( $errors, $path, "type check (' $type ') failed - got ' $value '" );
388 return 1 ; # no need to verify unknown types
398 my ( $path, $schema, $value, $additional_properties, $errors ) = @_ ;
400 # print "Check Object " . Dumper($value) . "\nSchema: " . Dumper($schema);
402 my $st = ref ( $schema );
403 if (! $st || $st ne 'HASH' ) {
404 add_error
( $errors, $path, "Invalid schema definition." );
408 my $vt = ref ( $value );
409 if (! $vt || $vt ne 'HASH' ) {
410 add_error
( $errors, $path, "an object is required" );
414 foreach my $k ( keys %$schema ) {
415 check_prop
( $value ->{ $k }, $schema ->{ $k }, $path ?
" $path . $k " : $k, $errors );
418 foreach my $k ( keys %$value ) {
420 my $newpath = $path ?
" $path . $k " : $k ;
422 if ( my $subschema = $schema ->{ $k }) {
423 if ( my $requires = $subschema ->{ requires
}) {
424 if ( ref ( $requires )) {
425 #print "TEST: " . Dumper($value) . "\n", Dumper($requires) ;
426 check_prop
( $value, $requires, $path, $errors );
427 } elsif (! defined ( $value ->{ $requires })) {
428 add_error
( $errors, $path ?
" $path . $requires " : $requires,
429 "missing property - ' $newpath ' requiers this property" );
433 next ; # value is already checked above
436 if ( defined ( $additional_properties ) && ! $additional_properties ) {
437 add_error
( $errors, $newpath, "property is not defined in schema " .
438 "and the schema does not allow additional properties" );
441 check_prop
( $value ->{ $k }, $additional_properties, $newpath, $errors )
442 if ref ( $additional_properties );
447 my ( $value, $schema, $path, $errors ) = @_ ;
449 die "internal error - no schema" if ! $schema ;
450 die "internal error" if ! $errors ;
452 #print "check_prop $path\n" if $value;
454 my $st = ref ( $schema );
455 if (! $st || $st ne 'HASH' ) {
456 add_error
( $errors, $path, "Invalid schema definition." );
460 # if it extends another schema, it must pass that schema as well
461 if ( $schema ->{ extends
}) {
462 check_prop
( $value, $schema ->{ extends
}, $path, $errors );
465 if (! defined ( $value )) {
466 return if $schema ->{ type
} && $schema ->{ type
} eq 'null' ;
467 if (! $schema ->{ optional
}) {
468 add_error
( $errors, $path, "property is missing and it is not optional" );
473 return if ! check_type
( $path, $schema ->{ type
}, $value, $errors );
475 if ( $schema ->{ disallow
}) {
477 if ( check_type
( $path, $schema ->{ disallow
}, $value, $tmperr )) {
478 add_error
( $errors, $path, "disallowed value was matched" );
483 if ( my $vt = ref ( $value )) {
485 if ( $vt eq 'ARRAY' ) {
486 if ( $schema ->{ items
}) {
487 my $it = ref ( $schema ->{ items
});
488 if ( $it && $it eq 'ARRAY' ) {
489 #die "implement me $path: $vt " . Dumper($schema) ."\n". Dumper($value);
490 die "not implemented" ;
493 foreach my $el ( @$value ) {
494 check_prop
( $el, $schema ->{ items
}, "${path}[ $ind ]" , $errors );
500 } elsif ( $schema ->{ properties
} || $schema ->{ additionalProperties
}) {
501 check_object
( $path, defined ( $schema ->{ properties
}) ?
$schema ->{ properties
} : {},
502 $value, $schema ->{ additionalProperties
}, $errors );
508 if ( my $format = $schema ->{ format
}) {
509 eval { check_format
( $format, $value ); };
511 add_error
( $errors, $path, "invalid format - $@ " );
516 if ( my $pattern = $schema ->{ pattern
}) {
517 if ( $value !~ m/^$pattern$/ ) {
518 add_error
( $errors, $path, "value does not match the regex pattern" );
523 if ( defined ( my $max = $schema ->{ maxLength
})) {
524 if ( length ( $value ) > $max ) {
525 add_error
( $errors, $path, "value may only be $max characters long" );
530 if ( defined ( my $min = $schema ->{ minLength
})) {
531 if ( length ( $value ) < $min ) {
532 add_error
( $errors, $path, "value must be at least $min characters long" );
537 if ( is_number
( $value )) {
538 if ( defined ( my $max = $schema ->{ maximum
})) {
540 add_error
( $errors, $path, "value must have a maximum value of $max " );
545 if ( defined ( my $min = $schema ->{ minimum
})) {
547 add_error
( $errors, $path, "value must have a minimum value of $min " );
553 if ( my $ea = $schema ->{ enum
}) {
556 foreach my $ev ( @$ea ) {
563 add_error
( $errors, $path, "value ' $value ' does not have a value in the enumeration '" .
564 join ( ", " , @$ea ) . "'" );
571 my ( $instance, $schema, $errmsg ) = @_ ;
574 $errmsg = "Parameter verification failed. \n " if ! $errmsg ;
576 # todo: cycle detection is only needed for debugging, I guess
577 # we can disable that in the final release
578 # todo: is there a better/faster way to detect cycles?
580 find_cycle
( $instance, sub { $cycles = 1 });
582 add_error
( $errors, undef , "data structure contains recursive cycles" );
584 check_prop
( $instance, $schema, '' , $errors );
587 if ( scalar ( %$errors )) {
588 raise
$errmsg, code
=> HTTP_BAD_REQUEST
, errors
=> $errors ;
594 my $schema_valid_types = [ "string" , "object" , "coderef" , "array" , "boolean" , "number" , "integer" , "null" , "any" ];
595 my $default_schema_noref = {
596 description
=> "This is the JSON Schema for JSON Schemas." ,
597 type
=> [ "object" ],
598 additionalProperties
=> 0 ,
601 type
=> [ "string" , "array" ],
602 description
=> "This is a type definition value. This can be a simple type, or a union type" ,
607 enum
=> $schema_valid_types,
609 enum
=> $schema_valid_types,
613 description
=> "This indicates that the instance property in the instance object is not required." ,
619 description
=> "This is a definition for the properties of an object value" ,
625 description
=> "When the value is an array, this indicates the schema to use to validate each item in an array" ,
629 additionalProperties
=> {
630 type
=> [ "boolean" , "object" ],
631 description
=> "This provides a default property definition for all properties that are not explicitly defined in an object type definition." ,
638 description
=> "This indicates the minimum value for the instance property when the type of the instance value is a number." ,
643 description
=> "This indicates the maximum value for the instance property when the type of the instance value is a number." ,
647 description
=> "When the instance value is a string, this indicates minimum length of the string" ,
654 description
=> "When the instance value is a string, this indicates maximum length of the string." ,
660 description
=> "A text representation of the type (used to generate documentation)." ,
665 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." ,
673 description
=> "This provides an enumeration of possible values that are valid for the instance property." ,
678 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)." ,
683 description
=> "This provides the title of the property" ,
686 type
=> [ "string" , "object" ],
688 description
=> "indicates a required property or a schema that must be validated if this property is present" ,
693 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" ,
698 description
=> "This indicates the default for the instance property."
703 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, than this instance is not valid." ,
708 description
=> "This indicates the schema extends the given schema. All instances of this schema must be valid to by the extended schema also." ,
711 # this is from hyper schema
714 description
=> "This defines the link relations of the instance objects" ,
721 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" ,
725 description
=> "This is the name of the link relation" ,
731 description
=> "For submission links, this defines the method that should be used to access the target resource" ,
741 my $default_schema = Storable
:: dclone
( $default_schema_noref );
743 $default_schema ->{ properties
}->{ properties
}->{ additionalProperties
} = $default_schema ;
744 $default_schema ->{ properties
}->{ additionalProperties
}->{ properties
} = $default_schema ->{ properties
};
746 $default_schema ->{ properties
}->{ items
}->{ properties
} = $default_schema ->{ properties
};
747 $default_schema ->{ properties
}->{ items
}->{ additionalProperties
} = 0 ;
749 $default_schema ->{ properties
}->{ disallow
}->{ properties
} = $default_schema ->{ properties
};
750 $default_schema ->{ properties
}->{ disallow
}->{ additionalProperties
} = 0 ;
752 $default_schema ->{ properties
}->{ requires
}->{ properties
} = $default_schema ->{ properties
};
753 $default_schema ->{ properties
}->{ requires
}->{ additionalProperties
} = 0 ;
755 $default_schema ->{ properties
}->{ extends
}->{ properties
} = $default_schema ->{ properties
};
756 $default_schema ->{ properties
}->{ extends
}->{ additionalProperties
} = 0 ;
758 my $method_schema = {
760 additionalProperties
=> 0 ,
763 description
=> "This a description of the method" ,
768 description
=> "This indicates the name of the function to call." ,
771 additionalProperties
=> 1 ,
786 description
=> "The HTTP method name." ,
787 enum
=> [ 'GET' , 'POST' , 'PUT' , 'DELETE' ],
792 description
=> "Method needs special privileges - only pvedaemon can execute it" ,
797 description
=> "A parameter name. If specified, all calls to this method are proxied to the host contained in that parameter." ,
802 description
=> "Required access permissions. By default only 'root' is allowed to access this method." ,
804 additionalProperties
=> 0 ,
807 description
=> "Describe access permissions." ,
811 description
=> "A simply way to allow access for 'all' authenticated users. Value 'world' is used to allow access without credentials." ,
813 enum
=> [ 'all' , 'world' ],
817 description
=> "Array of permission checks (prefix notation)." ,
824 description
=> "Used internally" ,
828 description
=> "Used internally" ,
833 description
=> "path for URL matching (uri template)" ,
835 fragmentDelimiter
=> {
837 description
=> "A ways to override the default fragment delimiter '/'. This onyl works on a whole sub-class. You can set this to the empty string to match the whole rest of the URI." ,
842 description
=> "JSON Schema for parameters." ,
847 description
=> "JSON Schema for return value." ,
852 description
=> "method implementaion (code reference)" ,
857 description
=> "Delegate call to this class (perl class string)." ,
860 additionalProperties
=> 0 ,
866 fragmentDelimiter
=> { optional
=> 1 }
874 sub validate_schema
{
877 my $errmsg = "internal error - unable to verify schema \n " ;
878 validate
( $schema, $default_schema, $errmsg );
881 sub validate_method_info
{
884 my $errmsg = "internal error - unable to verify method info \n " ;
885 validate
( $info, $method_schema, $errmsg );
887 validate_schema
( $info ->{ parameters
}) if $info ->{ parameters
};
888 validate_schema
( $info ->{ returns
}) if $info ->{ returns
};
891 # run a self test on load
892 # make sure we can verify the default schema
893 validate_schema
( $default_schema_noref );
894 validate_schema
( $method_schema );
896 # and now some utility methods (used by pve api)
897 sub method_get_child_link
{
900 return undef if ! $info ;
902 my $schema = $info ->{ returns
};
903 return undef if ! $schema || ! $schema ->{ type
} || $schema ->{ type
} ne 'array' ;
905 my $links = $schema ->{ links
};
906 return undef if ! $links ;
909 foreach my $lnk ( @$links ) {
910 if ( $lnk ->{ href
} && $lnk ->{ rel
} && ( $lnk ->{ rel
} eq 'child' )) {
919 # a way to parse command line parameters, using a
920 # schema to configure Getopt::Long
922 my ( $schema, $args, $arg_param, $fixed_param, $pwcallback ) = @_ ;
924 if (! $schema || ! $schema ->{ properties
}) {
925 raise
( "too many arguments \n " , code
=> HTTP_BAD_REQUEST
)
926 if scalar ( @$args ) != 0 ;
931 if ( $arg_param && ! ref ( $arg_param )) {
932 my $pd = $schema ->{ properties
}->{ $arg_param };
933 die "expected list format $pd ->{format}"
934 if !( $pd && $pd ->{ format
} && $pd ->{ format
} =~ m/-list/ );
935 $list_param = $arg_param ;
939 foreach my $prop ( keys %{ $schema ->{ properties
}}) {
940 my $pd = $schema ->{ properties
}->{ $prop };
941 next if $list_param && $prop eq $list_param ;
942 next if defined ( $fixed_param ->{ $prop });
944 if ( $prop eq 'password' && $pwcallback ) {
945 # we do not accept plain password on input line, instead
946 # we turn this into a boolean option and ask for password below
947 # using $pwcallback() (for security reasons).
948 push @getopt, " $prop " ;
949 } elsif ( $pd ->{ type
} eq 'boolean' ) {
950 push @getopt, " $prop :s" ;
952 if ( $pd ->{ format
} && $pd ->{ format
} =~ m/-a?list/ ) {
953 push @getopt, " $prop =s@" ;
955 push @getopt, " $prop =s" ;
961 raise
( "unable to parse option \n " , code
=> HTTP_BAD_REQUEST
)
962 if ! Getopt
:: Long
:: GetOptionsFromArray
( $args, $opts, @getopt );
964 if ( my $acount = scalar ( @$args )) {
966 $opts ->{ $list_param } = $args ;
968 } elsif ( ref ( $arg_param )) {
969 raise
( "wrong number of arguments \n " , code
=> HTTP_BAD_REQUEST
)
970 if scalar ( @$arg_param ) != $acount ;
971 foreach my $p ( @$arg_param ) {
972 $opts ->{ $p } = shift @$args ;
975 raise
( "too many arguments \n " , code
=> HTTP_BAD_REQUEST
)
976 if scalar ( @$args ) != 0 ;
980 if ( my $pd = $schema ->{ properties
}->{ password
}) {
981 if ( $pd ->{ type
} ne 'boolean' && $pwcallback ) {
982 if ( $opts ->{ password
} || ! $pd ->{ optional
}) {
983 $opts ->{ password
} = & $pwcallback ();
988 $opts = PVE
:: Tools
:: decode_utf8_parameters
( $opts );
990 foreach my $p ( keys %$opts ) {
991 if ( my $pd = $schema ->{ properties
}->{ $p }) {
992 if ( $pd ->{ type
} eq 'boolean' ) {
993 if ( $opts ->{ $p } eq '' ) {
995 } elsif ( $opts ->{ $p } =~ m/^(1|true|yes|on)$/i ) {
997 } elsif ( $opts ->{ $p } =~ m/^(0|false|no|off)$/i ) {
1000 raise
( "unable to parse boolean option \n " , code
=> HTTP_BAD_REQUEST
);
1002 } elsif ( $pd ->{ format
}) {
1004 if ( $pd ->{ format
} =~ m/-list/ ) {
1005 # allow --vmid 100 --vmid 101 and --vmid 100,101
1006 # allow --dow mon --dow fri and --dow mon,fri
1007 $opts ->{ $p } = join ( "," , @{ $opts ->{ $p }});
1008 } elsif ( $pd ->{ format
} =~ m/-alist/ ) {
1009 # we encode array as \0 separated strings
1010 # Note: CGI.pm also use this encoding
1011 if ( scalar (@{ $opts ->{ $p }}) != 1 ) {
1012 $opts ->{ $p } = join ( "\0" , @{ $opts ->{ $p }});
1014 # st that split_list knows it is \0 terminated
1015 my $v = $opts ->{ $p }->[ 0 ];
1016 $opts ->{ $p } = " $v\0 " ;
1023 foreach my $p ( keys %$fixed_param ) {
1024 $opts ->{ $p } = $fixed_param ->{ $p };
1030 # A way to parse configuration data by giving a json schema
1032 my ( $schema, $filename, $raw ) = @_ ;
1034 # do fast check (avoid validate_schema($schema))
1035 die "got strange schema" if ! $schema ->{ type
} ||
1036 ! $schema ->{ properties
} || $schema ->{ type
} ne 'object' ;
1040 while ( $raw && $raw =~ s/^(.*?)(\n|$)// ) {
1043 next if $line =~ m/^\#/ ; # skip comment lines
1044 next if $line =~ m/^\s*$/ ; # skip empty lines
1046 if ( $line =~ m/^(\S+):\s*(\S+)\s*$/ ) {
1049 if ( $schema ->{ properties
}->{ $key } &&
1050 $schema ->{ properties
}->{ $key }->{ type
} eq 'boolean' ) {
1052 $value = 1 if $value =~ m/^(1|on|yes|true)$/i ;
1053 $value = 0 if $value =~ m/^(0|off|no|false)$/i ;
1055 $cfg ->{ $key } = $value ;
1057 warn "ignore config line: $line\n "
1062 check_prop
( $cfg, $schema, '' , $errors );
1064 foreach my $k ( keys %$errors ) {
1065 warn "parse error in ' $filename ' - ' $k ': $errors ->{ $k } \n " ;
1072 # generate simple key/value file
1074 my ( $schema, $filename, $cfg ) = @_ ;
1076 # do fast check (avoid validate_schema($schema))
1077 die "got strange schema" if ! $schema ->{ type
} ||
1078 ! $schema ->{ properties
} || $schema ->{ type
} ne 'object' ;
1080 validate
( $cfg, $schema, "validation error in ' $filename ' \n " );
1084 foreach my $k ( keys %$cfg ) {
1085 $data .= " $k : $cfg ->{ $k } \n " ;