]>
git.proxmox.com Git - pve-common.git/blob - data/PVE/JSONSchema.pm
816083a2be1fe7e2d32dae8831a742fd62cad5d9
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 cofiguration 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 # todo: use better regex ?
133 if ( $node !~ m/^[A-Za-z][[:alnum:]\-]*[[:alnum:]]+$/ ) {
134 return undef if $noerr ;
135 die "value does not look like a valid node name \n " ;
140 register_format
( 'ipv4' , \
& pve_verify_ipv4
);
141 sub pve_verify_ipv4
{
142 my ( $ipv4, $noerr ) = @_ ;
144 if ( $ipv4 !~ m/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/ ||
145 !(( $1 > 0 ) && ( $1 < 255 ) &&
146 ( $2 <= 255 ) && ( $3 <= 255 ) &&
147 ( $4 > 0 ) && ( $4 < 255 ))) {
148 return undef if $noerr ;
149 die "value does not look like a valid IP address \n " ;
153 register_format
( 'ipv4mask' , \
& pve_verify_ipv4mask
);
154 sub pve_verify_ipv4mask
{
155 my ( $mask, $noerr ) = @_ ;
157 if ( $mask !~ m/^255\.255\.(\d{1,3})\.(\d{1,3})$/ ||
158 !(( $1 <= 255 ) && ( $2 <= 255 ))) {
159 return undef if $noerr ;
160 die "value does not look like a valid IP netmask \n " ;
165 register_format
( 'email' , \
& pve_verify_email
);
166 sub pve_verify_email
{
167 my ( $email, $noerr ) = @_ ;
169 # we use same regex as extjs Ext.form.VTypes.email
170 if ( $email !~ /^(\w+)([\-+.][\w]+)*@(\w[\-\w]*\.){1,5}([A-Za-z]){2,6}$/ ) {
171 return undef if $noerr ;
172 die "value does not look like a valid email address \n " ;
177 register_format
( 'dns-name' , \
& pve_verify_dns_name
);
178 sub pve_verify_dns_name
{
179 my ( $name, $noerr ) = @_ ;
181 my $namere = "([a-zA-Z]|[a-zA-Z][a-zA-Z0-9\-]*[a-zA-Z0-9])" ;
183 if ( $name !~ /^(${namere}\.)*${namere}$/ ) {
184 return undef if $noerr ;
185 die "value does not look like a valid DNS name \n " ;
190 # network interface name
191 register_format
( 'pve-iface' , \
& pve_verify_iface
);
192 sub pve_verify_iface
{
193 my ( $id, $noerr ) = @_ ;
195 if ( $id !~ m/^[a-z][a-z0-9_]{1,20}([:\.]\d+)?$/i ) {
196 return undef if $noerr ;
197 die "invalid network interface name ' $id ' \n " ;
203 my ( $format, $value ) = @_ ;
205 return if $format eq 'regex' ;
207 if ( $format =~ m/^(.*)-a?list$/ ) {
209 my $code = $format_list ->{ $1 };
211 die "undefined format ' $format ' \n " if ! $code ;
213 # Note: we allow empty lists
214 foreach my $v ( split_list
( $value )) {
218 } elsif ( $format =~ m/^(.*)-opt$/ ) {
220 my $code = $format_list ->{ $1 };
222 die "undefined format ' $format ' \n " if ! $code ;
224 return if ! $value ; # allow empty string
230 my $code = $format_list ->{ $format };
232 die "undefined format ' $format ' \n " if ! $code ;
239 my ( $errors, $path, $msg ) = @_ ;
241 $path = '_root' if ! $path ;
243 if ( $errors ->{ $path }) {
244 $errors ->{ $path } = join ( ' \n ' , $errors ->{ $path }, $msg );
246 $errors ->{ $path } = $msg ;
253 # see 'man perlretut'
254 return $value =~ /^[+-]?(\d+\.\d+|\d+\.|\.\d+|\d+)([eE][+-]?\d+)?$/ ;
260 return $value =~ m/^[+-]?\d+$/ ;
264 my ( $path, $type, $value, $errors ) = @_ ;
268 if (! defined ( $value )) {
269 return 1 if $type eq 'null' ;
273 if ( my $tt = ref ( $type )) {
274 if ( $tt eq 'ARRAY' ) {
275 foreach my $t ( @$type ) {
277 check_type
( $path, $t, $value, $tmperr );
278 return 1 if ! scalar ( %$tmperr );
280 my $ttext = join ( '|' , @$type );
281 add_error
( $errors, $path, "type check (' $ttext ') failed" );
283 } elsif ( $tt eq 'HASH' ) {
285 check_prop
( $value, $type, $path, $tmperr );
286 return 1 if ! scalar ( %$tmperr );
287 add_error
( $errors, $path, "type check failed" );
290 die "internal error - got reference type ' $tt '" ;
295 return 1 if $type eq 'any' ;
297 if ( $type eq 'null' ) {
298 if ( defined ( $value )) {
299 add_error
( $errors, $path, "type check (' $type ') failed - value is not null" );
305 my $vt = ref ( $value );
307 if ( $type eq 'array' ) {
308 if (! $vt || $vt ne 'ARRAY' ) {
309 add_error
( $errors, $path, "type check (' $type ') failed" );
313 } elsif ( $type eq 'object' ) {
314 if (! $vt || $vt ne 'HASH' ) {
315 add_error
( $errors, $path, "type check (' $type ') failed" );
319 } elsif ( $type eq 'coderef' ) {
320 if (! $vt || $vt ne 'CODE' ) {
321 add_error
( $errors, $path, "type check (' $type ') failed" );
327 add_error
( $errors, $path, "type check (' $type ') failed - got $vt " );
330 if ( $type eq 'string' ) {
331 return 1 ; # nothing to check ?
332 } elsif ( $type eq 'boolean' ) {
333 #if ($value =~ m/^(1|true|yes|on)$/i) {
336 #} elsif ($value =~ m/^(0|false|no|off)$/i) {
337 } elsif ( $value eq '0' ) {
340 add_error
( $errors, $path, "type check (' $type ') failed - got ' $value '" );
343 } elsif ( $type eq 'integer' ) {
344 if (! is_integer
( $value )) {
345 add_error
( $errors, $path, "type check (' $type ') failed - got ' $value '" );
349 } elsif ( $type eq 'number' ) {
350 if (! is_number
( $value )) {
351 add_error
( $errors, $path, "type check (' $type ') failed - got ' $value '" );
356 return 1 ; # no need to verify unknown types
366 my ( $path, $schema, $value, $additional_properties, $errors ) = @_ ;
368 # print "Check Object " . Dumper($value) . "\nSchema: " . Dumper($schema);
370 my $st = ref ( $schema );
371 if (! $st || $st ne 'HASH' ) {
372 add_error
( $errors, $path, "Invalid schema definition." );
376 my $vt = ref ( $value );
377 if (! $vt || $vt ne 'HASH' ) {
378 add_error
( $errors, $path, "an object is required" );
382 foreach my $k ( keys %$schema ) {
383 check_prop
( $value ->{ $k }, $schema ->{ $k }, $path ?
" $path . $k " : $k, $errors );
386 foreach my $k ( keys %$value ) {
388 my $newpath = $path ?
" $path . $k " : $k ;
390 if ( my $subschema = $schema ->{ $k }) {
391 if ( my $requires = $subschema ->{ requires
}) {
392 if ( ref ( $requires )) {
393 #print "TEST: " . Dumper($value) . "\n", Dumper($requires) ;
394 check_prop
( $value, $requires, $path, $errors );
395 } elsif (! defined ( $value ->{ $requires })) {
396 add_error
( $errors, $path ?
" $path . $requires " : $requires,
397 "missing property - ' $newpath ' requiers this property" );
401 next ; # value is already checked above
404 if ( defined ( $additional_properties ) && ! $additional_properties ) {
405 add_error
( $errors, $newpath, "property is not defined in schema " .
406 "and the schema does not allow additional properties" );
409 check_prop
( $value ->{ $k }, $additional_properties, $newpath, $errors )
410 if ref ( $additional_properties );
415 my ( $value, $schema, $path, $errors ) = @_ ;
417 die "internal error - no schema" if ! $schema ;
418 die "internal error" if ! $errors ;
420 #print "check_prop $path\n" if $value;
422 my $st = ref ( $schema );
423 if (! $st || $st ne 'HASH' ) {
424 add_error
( $errors, $path, "Invalid schema definition." );
428 # if it extends another schema, it must pass that schema as well
429 if ( $schema ->{ extends
}) {
430 check_prop
( $value, $schema ->{ extends
}, $path, $errors );
433 if (! defined ( $value )) {
434 return if $schema ->{ type
} && $schema ->{ type
} eq 'null' ;
435 if (! $schema ->{ optional
}) {
436 add_error
( $errors, $path, "property is missing and it is not optional" );
441 return if ! check_type
( $path, $schema ->{ type
}, $value, $errors );
443 if ( $schema ->{ disallow
}) {
445 if ( check_type
( $path, $schema ->{ disallow
}, $value, $tmperr )) {
446 add_error
( $errors, $path, "disallowed value was matched" );
451 if ( my $vt = ref ( $value )) {
453 if ( $vt eq 'ARRAY' ) {
454 if ( $schema ->{ items
}) {
455 my $it = ref ( $schema ->{ items
});
456 if ( $it && $it eq 'ARRAY' ) {
457 #die "implement me $path: $vt " . Dumper($schema) ."\n". Dumper($value);
458 die "not implemented" ;
461 foreach my $el ( @$value ) {
462 check_prop
( $el, $schema ->{ items
}, "${path}[ $ind ]" , $errors );
468 } elsif ( $schema ->{ properties
} || $schema ->{ additionalProperties
}) {
469 check_object
( $path, defined ( $schema ->{ properties
}) ?
$schema ->{ properties
} : {},
470 $value, $schema ->{ additionalProperties
}, $errors );
476 if ( my $format = $schema ->{ format
}) {
477 eval { check_format
( $format, $value ); };
479 add_error
( $errors, $path, "invalid format - $@ " );
484 if ( my $pattern = $schema ->{ pattern
}) {
485 if ( $value !~ m/^$pattern$/ ) {
486 add_error
( $errors, $path, "value does not match the regex pattern" );
491 if ( defined ( my $max = $schema ->{ maxLength
})) {
492 if ( length ( $value ) > $max ) {
493 add_error
( $errors, $path, "value may only be $max characters long" );
498 if ( defined ( my $min = $schema ->{ minLength
})) {
499 if ( length ( $value ) < $min ) {
500 add_error
( $errors, $path, "value must be at least $min characters long" );
505 if ( is_number
( $value )) {
506 if ( defined ( my $max = $schema ->{ maximum
})) {
508 add_error
( $errors, $path, "value must have a maximum value of $max " );
513 if ( defined ( my $min = $schema ->{ minimum
})) {
515 add_error
( $errors, $path, "value must have a minimum value of $min " );
521 if ( my $ea = $schema ->{ enum
}) {
524 foreach my $ev ( @$ea ) {
531 add_error
( $errors, $path, "value ' $value ' does not have a value in the enumeration '" .
532 join ( ", " , @$ea ) . "'" );
539 my ( $instance, $schema, $errmsg ) = @_ ;
542 $errmsg = "Parameter verification failed. \n " if ! $errmsg ;
544 # todo: cycle detection is only needed for debugging, I guess
545 # we can disable that in the final release
546 # todo: is there a better/faster way to detect cycles?
548 find_cycle
( $instance, sub { $cycles = 1 });
550 add_error
( $errors, undef , "data structure contains recursive cycles" );
552 check_prop
( $instance, $schema, '' , $errors );
555 if ( scalar ( %$errors )) {
556 raise
$errmsg, code
=> HTTP_BAD_REQUEST
, errors
=> $errors ;
562 my $schema_valid_types = [ "string" , "object" , "coderef" , "array" , "boolean" , "number" , "integer" , "null" , "any" ];
563 my $default_schema_noref = {
564 description
=> "This is the JSON Schema for JSON Schemas." ,
565 type
=> [ "object" ],
566 additionalProperties
=> 0 ,
569 type
=> [ "string" , "array" ],
570 description
=> "This is a type definition value. This can be a simple type, or a union type" ,
575 enum
=> $schema_valid_types,
577 enum
=> $schema_valid_types,
581 description
=> "This indicates that the instance property in the instance object is not required." ,
587 description
=> "This is a definition for the properties of an object value" ,
593 description
=> "When the value is an array, this indicates the schema to use to validate each item in an array" ,
597 additionalProperties
=> {
598 type
=> [ "boolean" , "object" ],
599 description
=> "This provides a default property definition for all properties that are not explicitly defined in an object type definition." ,
606 description
=> "This indicates the minimum value for the instance property when the type of the instance value is a number." ,
611 description
=> "This indicates the maximum value for the instance property when the type of the instance value is a number." ,
615 description
=> "When the instance value is a string, this indicates minimum length of the string" ,
622 description
=> "When the instance value is a string, this indicates maximum length of the string." ,
628 description
=> "A text representation of the type (used to generate documentation)." ,
633 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." ,
641 description
=> "This provides an enumeration of possible values that are valid for the instance property." ,
646 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)." ,
651 description
=> "This provides the title of the property" ,
654 type
=> [ "string" , "object" ],
656 description
=> "indicates a required property or a schema that must be validated if this property is present" ,
661 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" ,
666 description
=> "This indicates the default for the instance property."
671 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." ,
676 description
=> "This indicates the schema extends the given schema. All instances of this schema must be valid to by the extended schema also." ,
679 # this is from hyper schema
682 description
=> "This defines the link relations of the instance objects" ,
689 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" ,
693 description
=> "This is the name of the link relation" ,
699 description
=> "For submission links, this defines the method that should be used to access the target resource" ,
709 my $default_schema = Storable
:: dclone
( $default_schema_noref );
711 $default_schema ->{ properties
}->{ properties
}->{ additionalProperties
} = $default_schema ;
712 $default_schema ->{ properties
}->{ additionalProperties
}->{ properties
} = $default_schema ->{ properties
};
714 $default_schema ->{ properties
}->{ items
}->{ properties
} = $default_schema ->{ properties
};
715 $default_schema ->{ properties
}->{ items
}->{ additionalProperties
} = 0 ;
717 $default_schema ->{ properties
}->{ disallow
}->{ properties
} = $default_schema ->{ properties
};
718 $default_schema ->{ properties
}->{ disallow
}->{ additionalProperties
} = 0 ;
720 $default_schema ->{ properties
}->{ requires
}->{ properties
} = $default_schema ->{ properties
};
721 $default_schema ->{ properties
}->{ requires
}->{ additionalProperties
} = 0 ;
723 $default_schema ->{ properties
}->{ extends
}->{ properties
} = $default_schema ->{ properties
};
724 $default_schema ->{ properties
}->{ extends
}->{ additionalProperties
} = 0 ;
726 my $method_schema = {
728 additionalProperties
=> 0 ,
731 description
=> "This a description of the method" ,
736 description
=> "This indicates the name of the function to call." ,
739 additionalProperties
=> 1 ,
754 description
=> "The HTTP method name." ,
755 enum
=> [ 'GET' , 'POST' , 'PUT' , 'DELETE' ],
760 description
=> "Method needs special privileges - only pvedaemon can execute it" ,
765 description
=> "A parameter name. If specified, all calls to this method are proxied to the host contained in that parameter." ,
770 description
=> "Required access permissions. By default only 'root' is allowed to access this method." ,
772 additionalProperties
=> 0 ,
775 description
=> "Describe access permissions." ,
779 description
=> "A simply way to allow access for 'all' authenticated users. Value 'world' is used to allow access without credentials." ,
781 enum
=> [ 'all' , 'world' ],
785 description
=> "Array of permission checks (prefix notation)." ,
792 description
=> "Used internally" ,
796 description
=> "Used internally" ,
801 description
=> "path for URL matching (uri template)" ,
803 fragmentDelimiter
=> {
805 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." ,
810 description
=> "JSON Schema for parameters." ,
815 description
=> "JSON Schema for return value." ,
820 description
=> "method implementaion (code reference)" ,
825 description
=> "Delegate call to this class (perl class string)." ,
828 additionalProperties
=> 0 ,
834 fragmentDelimiter
=> { optional
=> 1 }
842 sub validate_schema
{
845 my $errmsg = "internal error - unable to verify schema \n " ;
846 validate
( $schema, $default_schema, $errmsg );
849 sub validate_method_info
{
852 my $errmsg = "internal error - unable to verify method info \n " ;
853 validate
( $info, $method_schema, $errmsg );
855 validate_schema
( $info ->{ parameters
}) if $info ->{ parameters
};
856 validate_schema
( $info ->{ returns
}) if $info ->{ returns
};
859 # run a self test on load
860 # make sure we can verify the default schema
861 validate_schema
( $default_schema_noref );
862 validate_schema
( $method_schema );
864 # and now some utility methods (used by pve api)
865 sub method_get_child_link
{
868 return undef if ! $info ;
870 my $schema = $info ->{ returns
};
871 return undef if ! $schema || ! $schema ->{ type
} || $schema ->{ type
} ne 'array' ;
873 my $links = $schema ->{ links
};
874 return undef if ! $links ;
877 foreach my $lnk ( @$links ) {
878 if ( $lnk ->{ href
} && $lnk ->{ rel
} && ( $lnk ->{ rel
} eq 'child' )) {
887 # a way to parse command line parameters, using a
888 # schema to configure Getopt::Long
890 my ( $schema, $args, $arg_param, $fixed_param, $pwcallback ) = @_ ;
892 if (! $schema || ! $schema ->{ properties
}) {
893 raise
( "too many arguments \n " , code
=> HTTP_BAD_REQUEST
)
894 if scalar ( @$args ) != 0 ;
899 if ( $arg_param && ! ref ( $arg_param )) {
900 my $pd = $schema ->{ properties
}->{ $arg_param };
901 die "expected list format $pd ->{format}"
902 if !( $pd && $pd ->{ format
} && $pd ->{ format
} =~ m/-list/ );
903 $list_param = $arg_param ;
907 foreach my $prop ( keys %{ $schema ->{ properties
}}) {
908 my $pd = $schema ->{ properties
}->{ $prop };
909 next if $list_param && $prop eq $list_param ;
910 next if defined ( $fixed_param ->{ $prop });
912 if ( $prop eq 'password' && $pwcallback ) {
913 # we do not accept plain password on input line, instead
914 # we turn this into a boolean option and ask for password below
915 # using $pwcallback() (for security reasons).
916 push @getopt, " $prop " ;
917 } elsif ( $pd ->{ type
} eq 'boolean' ) {
918 push @getopt, " $prop :s" ;
920 if ( $pd ->{ format
} && $pd ->{ format
} =~ m/-a?list/ ) {
921 push @getopt, " $prop =s@" ;
923 push @getopt, " $prop =s" ;
929 raise
( "unable to parse option \n " , code
=> HTTP_BAD_REQUEST
)
930 if ! Getopt
:: Long
:: GetOptionsFromArray
( $args, $opts, @getopt );
932 if ( my $acount = scalar ( @$args )) {
934 $opts ->{ $list_param } = $args ;
936 } elsif ( ref ( $arg_param )) {
937 raise
( "wrong number of arguments \n " , code
=> HTTP_BAD_REQUEST
)
938 if scalar ( @$arg_param ) != $acount ;
939 foreach my $p ( @$arg_param ) {
940 $opts ->{ $p } = shift @$args ;
943 raise
( "too many arguments \n " , code
=> HTTP_BAD_REQUEST
)
944 if scalar ( @$args ) != 0 ;
948 if ( my $pd = $schema ->{ properties
}->{ password
}) {
949 if ( $pd ->{ type
} ne 'boolean' && $pwcallback ) {
950 if ( $opts ->{ password
} || ! $pd ->{ optional
}) {
951 $opts ->{ password
} = & $pwcallback ();
956 $opts = PVE
:: Tools
:: decode_utf8_parameters
( $opts );
957 if ( $opts ->{ description
}) {
958 print "TEST: " . PVE
:: Tools
:: encode_text
( $opts ->{ description
}) . " \n " ;
961 foreach my $p ( keys %$opts ) {
962 if ( my $pd = $schema ->{ properties
}->{ $p }) {
963 if ( $pd ->{ type
} eq 'boolean' ) {
964 if ( $opts ->{ $p } eq '' ) {
966 } elsif ( $opts ->{ $p } =~ m/^(1|true|yes|on)$/i ) {
968 } elsif ( $opts ->{ $p } =~ m/^(0|false|no|off)$/i ) {
971 raise
( "unable to parse boolean option \n " , code
=> HTTP_BAD_REQUEST
);
973 } elsif ( $pd ->{ format
}) {
975 if ( $pd ->{ format
} =~ m/-list/ ) {
976 # allow --vmid 100 --vmid 101 and --vmid 100,101
977 # allow --dow mon --dow fri and --dow mon,fri
978 $opts ->{ $p } = join ( "," , @{ $opts ->{ $p }});
979 } elsif ( $pd ->{ format
} =~ m/-alist/ ) {
980 # we encode array as \0 separated strings
981 # Note: CGI.pm also use this encoding
982 if ( scalar (@{ $opts ->{ $p }}) != 1 ) {
983 $opts ->{ $p } = join ( "\0" , @{ $opts ->{ $p }});
985 # st that split_list knows it is \0 terminated
986 my $v = $opts ->{ $p }->[ 0 ];
987 $opts ->{ $p } = " $v\0 " ;
994 foreach my $p ( keys %$fixed_param ) {
995 $opts ->{ $p } = $fixed_param ->{ $p };
1001 # A way to parse configuration data by giving a json schema
1003 my ( $schema, $filename, $raw ) = @_ ;
1005 # do fast check (avoid validate_schema($schema))
1006 die "got strange schema" if ! $schema ->{ type
} ||
1007 ! $schema ->{ properties
} || $schema ->{ type
} ne 'object' ;
1011 while ( $raw && $raw =~ s/^(.*?)(\n|$)// ) {
1014 next if $line =~ m/^\#/ ; # skip comment lines
1015 next if $line =~ m/^\s*$/ ; # skip empty lines
1017 if ( $line =~ m/^(\S+):\s*(\S+)\s*$/ ) {
1020 if ( $schema ->{ properties
}->{ $key } &&
1021 $schema ->{ properties
}->{ $key }->{ type
} eq 'boolean' ) {
1023 $value = 1 if $value =~ m/^(1|on|yes|true)$/i ;
1024 $value = 0 if $value =~ m/^(0|off|no|false)$/i ;
1026 $cfg ->{ $key } = $value ;
1028 warn "ignore config line: $line\n "
1033 check_prop
( $cfg, $schema, '' , $errors );
1035 foreach my $k ( keys %$errors ) {
1036 warn "parse error in ' $filename ' - ' $k ': $errors ->{ $k } \n " ;
1043 # generate simple key/value file
1045 my ( $schema, $filename, $cfg ) = @_ ;
1047 # do fast check (avoid validate_schema($schema))
1048 die "got strange schema" if ! $schema ->{ type
} ||
1049 ! $schema ->{ properties
} || $schema ->{ type
} ne 'object' ;
1051 validate
( $cfg, $schema, "validation error in ' $filename ' \n " );
1055 foreach my $k ( keys %$cfg ) {
1056 $data .= " $k : $cfg ->{ $k } \n " ;