Dancer-Plugin-Resource

 view release on metacpan or  search on metacpan

lib/Dancer/Plugin/Resource.pm  view on Meta::CPAN

                Dancer::Error->new(
                    code    => 404,
                    message => "unsupported format requested: " . $format
                )
            );
        }

        set serializer => $serializer;
        my $ct = $content_types->{$format} || setting('content_type');
        content_type $ct;
    };
}
register prepare_serializer_for_format => \&prepare_serializer_for_format;

register resource => sub {
    my ($resource, %options) = @_;

    my $params = ':id';
    my ($old_prefix, $parent_prefix);

    unless ($options{skip_prepare_serializer} || ((caller)[1] =~ /^(?:t|xt)/)) {
        prepare_serializer_for_format;
    }

    # if this resource is a nested child resource, manage the prefix
    $old_prefix = Dancer::App->current->prefix || '';
    $parent_prefix = '';

    if ($options{parent} and $routes{$options{parent}}) {
        prefix $parent_prefix = $routes{$options{parent}};
    }
    else {
        $parent_prefix = $old_prefix;
    }

    # create a default for the load funcs
    $options{$_} ||= sub { undef } for (qw/load load_all/);

    # if member => 'foo' is passed, turn it into an array
    for my $type (qw/member collection/) {
        if ($options{$type} && ref $options{$type} eq '') {
            $options{$type} = [$options{$type}];
        }
    }

    # by default take the singular resource as the param name (ie :user for users)
    my ($singular_resource, $plural_resource) = (Lingua::EN::Inflect::Number::to_S($resource), $resource);

    # or if the user wants to override to take multiple params, ie /user/:foo/:bar/:baz
    # allow it. This could be useful for composite key schemas
    if ( my $p = $options{params} ) {
        $p = ref $p ? $p : [$p];
        $params = join '/', map ":${_}", @{$p};
    }
    else {
        $params = ":${singular_resource}_id";
    }

    my ($package) = caller;

    # main resource endpoints
    # CRUD
    _post(
        _endpoint(
            path     => $plural_resource,
            params   => '',
            verbs    => [qw/POST create/],
            function => $singular_resource
        )
    );

    _get(
        _endpoint(
            path     => $plural_resource,
            params   => $params,
            verbs    => [qw/GET get read/],
            loader   => $options{load},
            function => $singular_resource
        )
    );

    _put(
        _endpoint(
            path     => $plural_resource,
            params   => $params,
            verbs    => [qw/PUT update/],
            loader   => $options{load},
            function => $singular_resource
        )
    );

    _del(
        _endpoint(
            path     => $plural_resource,
            params   => $params,
            verbs    => [qw/DELETE delete/],
            loader   => $options{load},
            function => $singular_resource
        )
    );

    _get(
        _endpoint(
            path     => $plural_resource,
            params   => '',
            verbs    => [qw/INDEX index/],
            loader   => $options{load_all},
            function => $singular_resource
        )
    );

    # member routes are actions on the given id. ie /users/:user_id/foo
    for my $member (@{$options{member}}) {
        my $path = "${plural_resource}/$params/${member}";
        my $member_param = "";

        _post(
            _endpoint(
                path     => $path,
                params   => '',
                verbs    => [qw/POST create/],
                loader   => $options{load},
                function => "${singular_resource}_${member}"
            )
        );

        _get(
            _endpoint(
                path     => $path,
                params   => $member_param,
                verbs    => [qw/GET get read/],
                loader   => $options{load},
                function => "${singular_resource}_${member}"

            )
        );

        _put(
            _endpoint(
                path     => $path,
                params   => $member_param,
                verbs    => [qw/PUT update/],
                loader   => $options{load},
                function => "${singular_resource}_${member}"

            )
        );

        _del(
            _endpoint(
                path     => $path,
                params   => $member_param,
                verbs    => [qw/DELETE delete/],
                loader   => $options{load},
                function => "${singular_resource}_${member}"

            )
        );
    }

    # collection routes are actions on the collection. ie /users/foo
    for my $collection (@{$options{collection}}) {
        my $path = "${plural_resource}/${collection}";

        _post(
            _endpoint(
                path     => $path,
                params   => '',
                verbs    => [qw/POST create/],
                loader   => $options{load_all},
                function => "${plural_resource}_${collection}"
            )
        );

        _get(
            _endpoint(
                path     => $path,
                params   => '',
                verbs    => [qw/GET get read/],
                loader   => $options{load_all},
                function => "${plural_resource}_${collection}"
            )
        );

        _put(
            _endpoint(
                path     => $path,
                params   => '',
                verbs    => [qw/PUT update/],
                loader   => $options{load_all},
                function => "${plural_resource}_${collection}"
            )
        );

        _del(
            _endpoint(
                path     => $path,
                params   => '',
                verbs    => [qw/DELETE delete/],
                loader   => $options{load_all},
                function => "${plural_resource}_${collection}"
            )
        );
    }

    # save every defined resource if it is referred as a parent in a nested child resource
    $routes{$resource} = "${parent_prefix}/${plural_resource}/${params}";

    # restore existing prefix if saved
    prefix $old_prefix if $old_prefix;
};

sub _debug { $RESOURCE_DEBUG and print @_ }

sub _post {
    my ($route, $sub) = @_;
    for ($route . '.:format', $route) {
        _debug("=> POST " .(Dancer::App->current->prefix||'').$_."\n");
        post($_ => $sub);
    }
}

sub _get {
    my ($route, $sub) = @_;
    for ($route . '.:format', $route) {
        _debug("=> GET " .(Dancer::App->current->prefix||'').$_."\n");
        get($_ => $sub);
    }
}

sub _put {
    my ($route, $sub) = @_;
    for ($route . '.:format', $route) {
        _debug("=> PUT " .(Dancer::App->current->prefix||'').$_."\n");
        put($_ => $sub);
    }
}

sub _del {
    my ($route, $sub) = @_;
    for ($route . '.:format', $route) {
        _debug("=> DEL " .(Dancer::App->current->prefix||'').$_."\n");
        del($_ => $sub);
    }
}

sub _endpoint {
    my %opts = @_;
    my ($function, $word, $params, $verbs, $load_func) = @opts{qw/function path params verbs loader/};

    my $package = caller(1);

    my $wrapped;
    for my $verb (@$verbs) {
        # allow both foo_GET and GET_foo
        my $func = _function_exists("${package}::${verb}_${function}") ||
                   _function_exists("${package}::${function}_${verb}");

        if ($func) {
            _debug("${package}::${verb}_${function} ");
            $wrapped = sub { $func->($load_func ? $load_func->() : (), @_) };

            last; # we only want to attach to the first successful verb
        }
    }

    if (not $wrapped) {
        _debug("undef ");

        # if we've gotten this far, no route exists. use a default
        $wrapped = sub { status_method_not_allowed('Method not allowed.'); };
    }

    my $route
        = $params ? "/${word}/${params}"
        :           "/${word}";

    return ($route, $wrapped);
}

register send_entity => sub {
    my ($entity, $http_code) = @_;

    $http_code ||= 200;

    status($http_code);
    $entity;
};

my %http_codes = (

    # 1xx
    100 => 'Continue',
    101 => 'Switching Protocols',
    102 => 'Processing',

    # 2xx
    200 => 'OK',
    201 => 'Created',
    202 => 'Accepted',
    203 => 'Non-Authoritative Information',
    204 => 'No Content',
    205 => 'Reset Content',
    206 => 'Partial Content',
    207 => 'Multi-Status',
    210 => 'Content Different',



( run in 1.684 second using v1.01-cache-2.11-cpan-e1769b4cff6 )