]> git.proxmox.com Git - pve-common.git/blobdiff - data/PVE/RESTHandler.pm
new helper register_page_formater
[pve-common.git] / data / PVE / RESTHandler.pm
index e68db35571798028cd0770c53014be9af6af31ab..e1fb823ac9279cafb4dcef767b04b677a4ea6b7a 100644 (file)
@@ -2,7 +2,6 @@ package PVE::RESTHandler;
 
 use strict;
 no strict 'refs'; # our autoload requires this
-
 use warnings;
 use PVE::SafeSyslog;
 use PVE::Exception qw(raise raise_param_exc);
@@ -14,6 +13,7 @@ use Storable qw(dclone);
 
 my $method_registry = {};
 my $method_by_name = {};
+my $method_path_lookup = {};
 
 our $AUTOLOAD;  # it's a package global
 
@@ -157,25 +157,76 @@ sub register_method {
     my $match_re = [];
     my $match_name = [];
 
+    my $errprefix;
+
+    my $method;
+    if ($info->{subclass}) {
+       $errprefix = "register subclass $info->{subclass} at ${self}/$info->{path} -";
+       $method = 'SUBCLASS';
+    } else {
+       $errprefix = "register method ${self}/$info->{path} -";
+       $info->{method} = 'GET' if !$info->{method};
+       $method = $info->{method};
+    }
+
+    $method_path_lookup->{$self} = {} if !defined($method_path_lookup->{$self});
+    my $path_lookup = $method_path_lookup->{$self};
+
+    die "$errprefix no path" if !defined($info->{path});
+    
     foreach my $comp (split(/\/+/, $info->{path})) {
-       die "path compoment has zero length" if $comp eq '';
+       die "$errprefix path compoment has zero length\n" if $comp eq '';
+       my ($name, $regex);
        if ($comp =~ m/^\{(\w+)(:(.*))?\}$/) {
-           my $name = $1;
-           push @$match_re, $3 ? $3 : '\S+';
-           push @$match_name,  $1;
+           $name = $1;
+           $regex = $3 ? $3 : '\S+';
+           push @$match_re, $regex;
+           push @$match_name, $name;
+       } else {
+           $name = $comp;
+           push @$match_re, $name;
+           push @$match_name, undef;
+       }
+
+       if ($regex) {
+           $path_lookup->{regex} = {} if !defined($path_lookup->{regex});      
+
+           my $old_name = $path_lookup->{regex}->{match_name};
+           die "$errprefix found changed regex match name\n"
+               if defined($old_name) && ($old_name ne $name);
+           my $old_re = $path_lookup->{regex}->{match_re};
+           die "$errprefix found changed regex\n"
+               if defined($old_re) && ($old_re ne $regex);
+           $path_lookup->{regex}->{match_name} = $name;
+           $path_lookup->{regex}->{match_re} = $regex;
+           
+           die "$errprefix path match error - regex and fixed items\n"
+               if defined($path_lookup->{folders});
+
+           $path_lookup = $path_lookup->{regex};
+           
        } else {
-           push @$match_re, $comp;
-           push @$match_name,  undef;
+           $path_lookup->{folders}->{$name} = {} if !defined($path_lookup->{folders}->{$name});        
+
+           die "$errprefix path match error - regex and fixed items\n"
+               if defined($path_lookup->{regex});
+
+           $path_lookup = $path_lookup->{folders}->{$name};
        }
     }
 
+    die "$errprefix duplicate method definition\n" 
+       if defined($path_lookup->{$method});
+
+    $path_lookup->{$method} = $info;
+
     $info->{match_re} = $match_re;
     $info->{match_name} = $match_name;
 
     $method_by_name->{$self} = {} if !defined($method_by_name->{$self});
 
     if ($info->{name}) {
-       die "method '${self}::$info->{name}' already defined\n"
+       die "$errprefix method name already defined\n"
            if defined($method_by_name->{$self}->{$info->{name}});
 
        $method_by_name->{$self}->{$info->{name}} = $info;
@@ -184,6 +235,31 @@ sub register_method {
     push @{$method_registry->{$self}}, $info;
 }
 
+sub register_page_formater {
+    my ($self, %config) = @_;
+
+    my $format = $config{format} ||
+       die "missing format";
+
+    my $path = $config{path} ||
+       die "missing path";
+
+    my $method = $config{method} ||
+       die "missing method";
+       
+    my $code = $config{code} ||
+       die "missing formater code";
+    
+    my $uri_param = {};
+    my ($handler, $info) = $self->find_handler($method, $path, $uri_param);
+    die "unabe to find handler for '$method: $path'" if !($handler && $info);
+
+    die "duplicate formater for '$method: $path'" 
+       if $info->{formater} && $info->{formater}->{$format};
+
+    $info->{formater}->{$format} = $code;
+}
+
 sub AUTOLOAD {
     my ($this) = @_;
 
@@ -218,97 +294,68 @@ sub map_method_by_name {
     return $info;
 }
 
-sub map_method {
-    my ($self, $stack, $method, $uri_param) = @_;
-
-    my $ma = $method_registry->{$self};
+sub map_path_to_methods {
+    my ($class, $stack, $uri_param) = @_;
 
-    my $stacklen = scalar(@$stack);
+    my $path_lookup = $method_path_lookup->{$class};
 
-    #syslog ('info', "MAPTEST:$method:$self: " . join ('/', @$stack));
+    while (defined(my $comp = shift @$stack)) {
+       return undef if !$path_lookup; # not registerd?
+       if ($path_lookup->{regex}) {
+           my $name = $path_lookup->{regex}->{match_name};
+           my $regex = $path_lookup->{regex}->{match_re};
 
-    foreach my $info (@$ma) {
-       #syslog ('info', "TEST0 " . Dumper($info));
-       next if !($info->{subclass} || ($info->{method} eq $method));
-       my $regexlen = scalar(@{$info->{match_re}});
-       if ($info->{subclass}) {
-           next if $stacklen < $regexlen;
+           return undef if $comp !~ m/^($regex)$/;
+           $uri_param->{$name} = $1;
+           $path_lookup = $path_lookup->{regex};
+       } elsif ($path_lookup->{folders}) {
+           $path_lookup = $path_lookup->{folders}->{$comp};
        } else {
-           next if $stacklen != $regexlen;
-       }
-
-       #syslog ('info', "TEST1 " . Dumper($info));
-
-       my $param = {};
-       my $i = 0;
-       for (; $i < $regexlen; $i++) {
-           my $comp = $stack->[$i];
-           my $re = $info->{match_re}->[$i];
-           #print "COMPARE $comp $info->{match_re}->[$i]\n";
-           my ($match) = $stack->[$i] =~ m/^($re)$/;
-           last if !defined($match);
-           if (my $name = $info->{match_name}->[$i]) {
-               $param->{$name} = $match; 
-           }
-       }
-
-       next if $i != $regexlen;
-
-       #print "MATCH $info->{name}\n";
-       
-       foreach my $p (keys %$param) {
-           $uri_param->{$p} = $param->{$p};
+           die "internal error";
        }
+       return undef if !$path_lookup;
 
-       return $info;
-    }
-}
-
-sub __find_handler_full {
-    my ($class, $method, $stack, $uri_param, $pathmatchref) = @_;
-
-    my $info;
-    eval {
-       $info = $class->map_method($stack, $method, $uri_param);
-    };
-    syslog('err', $@) if $@;
-
-    return undef if !$info;
-
-    $$pathmatchref .= '/' . $info->{path};
+       if (my $info = $path_lookup->{SUBCLASS}) {
+           $class = $info->{subclass};
 
-    if (my $subh = $info->{subclass}) {
+           my $fd = $info->{fragmentDelimiter};
 
-       my $matchlen = scalar(@{$info->{match_re}});
+           if (defined($fd)) {
+               # we only support the empty string '' (match whole URI)
+               die "unsupported fragmentDelimiter '$fd'" 
+                   if $fd ne '';
 
-       for (my $i = 0; $i < $matchlen; $i++) {
-           shift @$stack; # pop from stack
+               $stack = [ join ('/', @$stack) ] if scalar(@$stack) > 1;
+           }
+           $path_lookup = $method_path_lookup->{$class};
        }
+    }
 
-       my $fd = $info->{fragmentDelimiter};
+    return undef if !$path_lookup;
 
-       if (defined($fd)) {
+    return ($class, $path_lookup);
+}
 
-           # we only support the empty string '' (match whole URI)
-           die "unsupported fragmentDelimiter '$fd'" 
-               if $fd ne '';
+sub find_handler {
+    my ($class, $method, $path, $uri_param) = @_;
 
-           $stack = [ join ('/', @$stack) ] if scalar(@$stack) > 1;
-       }
+    my $stack = [ grep { length($_) > 0 }  split('\/+' , $path)]; # skip empty fragments
 
-       return $subh->__find_handler_full($method, $stack, $uri_param, $pathmatchref);
-    }
+    my ($handler_class, $path_info);
+    eval {
+       ($handler_class, $path_info) = $class->map_path_to_methods($stack, $uri_param);
+    };
+    my $err = $@;
+    syslog('err', $err) if $err;
 
-    return ($class, $info, $$pathmatchref);
-};
+    return undef if !($handler_class && $path_info);
 
-sub find_handler {
-    my ($class, $method, $path, $uri_param) = @_;
+    my $method_info = $path_info->{$method};
 
-    my $stack = [ grep { length($_) > 0 }  split('\/+' , $path)]; # skip empty fragments
+    return undef if !$method_info;
 
-    my $pathmatch = '';
-    return $class->__find_handler_full($method, $stack, $uri_param, \$pathmatch);
+    return ($handler_class, $method_info);
 }
 
 sub handle {
@@ -376,7 +423,7 @@ sub usage_str {
        $arg_hash->{$p} = 1;
        $args .= " " if $args;
        if ($pd->{format} && $pd->{format} =~ m/-list/) {
-           $args .= "{<vmid>}";
+           $args .= "{<$p>}";
        } else {
            $args .= $pd->{optional} ? "[<$p>]" : "<$p>";
        }
@@ -477,29 +524,9 @@ sub cli_handler {
 
     my $info = $self->map_method_by_name($name);
 
-    my $param;
-    foreach my $p (keys %$fixed_param)  {
-       $param->{$p} = $fixed_param->{$p};
-    }
-
-    my $list_param;
-    if ($arg_param) {
-       if (ref($arg_param)) {
-           foreach my $p (@$arg_param) {
-               $param->{$p} = shift @$args if $args->[0] && $args->[0] !~ m/^-\S/;
-           }
-       } else {
-           my $pd = $info->{parameters}->{properties}->{$arg_param};
-           die "expected list format $pd->{format}"
-               if !($pd && $pd->{format} && $pd->{format} =~ m/-list/);
-           $list_param = $arg_param;
-       }
-    }
-
     my $res;
     eval {
-       my $param = PVE::JSONSchema::get_options($info->{parameters}, $args, $param, $pwcallback, $list_param);
-
+       my $param = PVE::JSONSchema::get_options($info->{parameters}, $args, $arg_param, $fixed_param, $pwcallback);
        $res = $self->handle($info, $param);
     };
     if (my $err = $@) {