Group
Extension

WWW-Snooze/lib/WWW/Snooze/Request.pm

package WWW::Snooze::Request;

use strict;
use warnings;
use 5.010;

use WWW::Snooze::Serialize::JSON;

use URI;
use LWP::UserAgent;
use JSON;

our $AUTOLOAD;

sub new {
    my $class = shift;
    my $uri = shift;
    my %args = @_;

    bless {
        base => $uri,
        parts => [],
        args => {},
        headers => undef,
        serializer => WWW::Snooze::Serialize::JSON->new(),
        %args
    }, $class;
}

sub AUTOLOAD {
    my $self = shift;
    my $name = $AUTOLOAD;
    $name =~ s/WWW::Snooze::Request:://;
    return $self->_add_element($name, @_);
}

sub DESTROY {}

sub _add_element {
    my $self = shift;
    my $name = shift;

    # TODO parse for multiple prototypes
    my $parts;
    push(@{$parts}, $name);
    my $arg = shift;
    push(@{$parts}, $arg) if ($arg);

    # TODO Combine argument hashes? It doesn't make sense to do:
    # $api->stories(100, owner => 'foo')->tasks(52, sort_by => 1)
    my %args = @_;

    return WWW::Snooze::Request->new(
        $self->{base},
        parts => [@{$self->{parts}}, @{$parts}],
        headers => $self->_headers,
        args => \%args,
        serializer => $self->_serializer
    );
}

# Private-ish functions to avoid namespace collisions
sub _serializer { shift->{serializer}; }
sub _headers { shift->{headers}; }
sub _args { shift->{args}; }

sub _build_url {
    my $self = shift;
    my $uri = URI->new($self->{base});

    my @parts = $uri->path_segments();
    push(@parts, @{$self->{parts}});

    # Add extension to last element
    if (my $ext = $self->_serializer->extension()) {
        my $last = pop @parts;
        $last .= $ext;
        push @parts, $last;
    }

    # Rebuild parts and query string
    $uri->path_segments(@parts);
    $uri->query_form($self->_args);

    return $uri->as_string();
}

sub _request {
    my $self = shift;
    my $method = shift;
    my $data = shift;

    die 'Bad HTTP request method'
      unless (grep($_ eq $method, (qw/GET POST PUT DELETE/)));

    my $h = LWP::UserAgent->new();
    $h->agent(
        sprintf(
            'Snooze/%s',
            $WWW::Snooze::VERSION
        )
    );

    my $req = HTTP::Request->new(
        $method,
        $self->_build_url,
        $self->_headers
    );
    $req->content_type($self->_serializer->content_type());

    # Set content if available
    if (ref $data eq 'HASH') {
        $req->content(
            $self->_serializer->encode($data)
        );
    }
    return $h->request($req);
}

sub get {
    my $self = shift;
    my $res = $self->_request('GET', @_);
    given ($res->code) {
        when (200) {
            return $self->_serializer->decode(
                $res->content()
            );
        }
        when ($_ > 200 and $_ < 300) {
            return $res->content();
        }
        default { return undef; }
    }
}

sub post {
    my $self = shift;
    # TODO post to url with query string?
    my $res = $self->_request('POST', @_);
    given ($res->code) {
        when (201) {
            return $self->_serializer->decode(
                $res->content()
            );
        }
        when ($_ >= 200 and $_ < 300) {
            return $res->content();
        }
        default { return undef; }
    }
}

sub put {
    my $self = shift;
    # TODO post to url with query string?
    my $res = $self->_request('PUT', @_);
    given ($res->code) {
        when (204) {
            return 1;
        }
        when ($_ >= 200 and $_ < 300) {
            return 1;
        }
        default { return 0; }
    }
}

sub delete {
    my $self = shift;
    # TODO post to url with query string?
    my $res = $self->_request('DELETE', @_);
    given ($res->code) {
        when (204) {
            return 1;
        }
        when ($_ >= 200 and $_ < 300) {
            return 1;
        }
        default { return 0; }
    }
}

1;
=head1 NAME

WWW::Snooze::Request - Main request object featuring autoloading

=head1 METHODS


=head2 new(%args)

=over 4

=item headers

Override headers with an instance of L<HTTP::Headers>

=item serializer

Override serializer with and instance of L<WWW::Snooze::Serialize>

=back

=head2 get([\%data])

=head2 delete([\%data])

=head2 post([\%data])

=head2 put([\%data])

Perform HTTP operation on URL, %data is encoded using the serializer.


=head1 AUTOMATIC METHODS

The request object uses autoloading method names to build the request. Calling a
method on the request object will add that method name on to the URL stack and
return a new request object with the new stack.

=head2 [$element]($id, %query_string)

Automatic methods can be called with an C<id> argument, or C<undef> if there is
no id, and named parameters which are encoded to a query string

    my $r = WWW::Snooze::Request->new('http://example.com');
    
    $r->foo();
    # Request URL would be http://example.com/foo.json
    
    $r->foo(42)->bar;
    # http://example.com/foo/42/bar.json
    
    $r->foo(undef, foo => 'bar');
    # http://example.com/foo?foo=bar

=head2 _add_element($name, $id, %query_string)

Automatic methods are built using this private function, however you can also
revert to calling this directly in the case of a namespace collision with an
element or a poorly named element.

    $r->_add_element('poorly named');
    # http://example.com/poorly%20named
    
    $r->_add_element('foo', 42, foo => bar);
    # http://example.com/foo/42.json?foo=bar

=head1 ATTRIBUTES

Privately scoped to avoid namespace collision

=head2 _args()

Return query string arguments added

=head2 _serializer()

Return the serializer

=head2 _headers()

Return the HTTP::Headers object


=head1 AUTHOR

Anthony Johnson E<lt>aj@ohess.orgE<gt>


Powered by Groonga
Maintained by Kenichi Ishigaki <ishigaki@cpan.org>. If you find anything, submit it on GitHub.