From 144ca83dff443539f84b10c4571de3354c1fd65b Mon Sep 17 00:00:00 2001 From: dakkar Date: Fri, 24 Mar 2017 17:56:22 +0000 Subject: documentation and tests for HeaderURI --- lib/Sietima.pm | 6 +- lib/Sietima/HeaderURI.pm | 169 ++++++++++++++++++++++++++++++++++++++------ lib/Sietima/Role/Headers.pm | 25 +++++-- t/tests/sietima/headeruri.t | 80 +++++++++++++++++++++ 4 files changed, 253 insertions(+), 27 deletions(-) create mode 100644 t/tests/sietima/headeruri.t diff --git a/lib/Sietima.pm b/lib/Sietima.pm index 98859ab..9115c05 100644 --- a/lib/Sietima.pm +++ b/lib/Sietima.pm @@ -249,8 +249,10 @@ sub _trait_namespace { 'Sietima::Role' } ## no critic(ProhibitUnusedPrivateSubro my $addresses_href = $sietima->list_addresses; -Returns a hashref of L<< C >> instances or strings, -that declare various addresses related to this list. +Returns a hashref of L<< C >> instances (or things +that can be passed to its constructor, like L<< C >>, +L<< C >>, or strings), that declare various addresses related to +this list. This base class declares only the L<< /C >>, and does not use this method at all. diff --git a/lib/Sietima/HeaderURI.pm b/lib/Sietima/HeaderURI.pm index d9c1bb0..347ca77 100644 --- a/lib/Sietima/HeaderURI.pm +++ b/lib/Sietima/HeaderURI.pm @@ -2,11 +2,52 @@ package Sietima::HeaderURI; use Moo; use Sietima::Policy; use Sietima::Types qw(Address AddressFromStr is_Address); -use Types::Standard qw(Str ClassName HashRef Optional); +use Types::Standard qw(Str is_Str ClassName HashRef Optional); use Type::Params qw(compile); use Types::URI qw(Uri is_Uri); +use Email::Address; use namespace::clean; +# VERSION +# ABSTRACT: annotated URI for list headers + +=head1 SYNOPSIS + + around list_addresses => sub($orig,$self) { + return +{ + $self->$orig->%*, + one => Sietima::HeaderURI->new({ + uri => 'http://foo/', + comment => 'a thing', + }), + two => Sietima::HeaderURI->new_from_address( + $self->owner, + { subject => 'Hello' }, + ), + three => Sietima::HeaderURI->new('http://some/url'), + four => Sietima::HeaderURI->new('(comment) address@example.com'), + }; + } + +=head1 DESCRIPTION + +This class pairs a L<< C >> with a comment, and knows how to +render itself as a string that can be used in a list management header +(see L<< C >>). + +=head1 ATTRIBUTES + +All attributes are read-only. + +=attr C + +Required L<< C >> object, coercible from a string or a hashref +(see L<< C >> for the details). This is the URI that users +should follow to perform the action implied by the list management +header. + +=cut + has uri => ( is => 'ro', isa => Uri, @@ -14,49 +55,108 @@ has uri => ( coerce => 1, ); +=attr C + +Optional string, will be added to the list management header as a +comment (in parentheses). + +=cut + has comment => ( is => 'ro', isa => Str, ); -# if it's not an Email::Address obect, we'll just take it as a string: -# it could be a non-mailto URI, see RFC 2369 "The Use of URLs as -# Meta-Syntax for Core Mail List Commands and their Transport through -# Message Header Fields" +=method C + + Sietima::HeaderURI->new({ + uri => 'http://foo/', comment => 'a thing', + }); + + Sietima::HeaderURI->new( + Email::Address->parse('(comment) address@example.com'), + ); + + Sietima::HeaderURI->new( '(comment) address@example.com' ); + + Sietima::HeaderURI->new( + URI->new('http://some/url'), + ); + + Sietima::HeaderURI->new( 'http://some/url' ); + +Objects of this class can be constructed in several ways. + +You can pass a hashref with URI (or something that L<< C +>> can coerce into a URI) and a comment string, as in the first +example. + +Or you can pass a single value that can be (or can be coerced into) +either a L<< C >> or a L<< C >>. + +Email addresse became C URIs, and the optional comment is +preserved. + +=for Pod::Coverage BUILDARGS + +=cut + +sub _args_from_address { + my ($address, $query) = @_; + $query ||= {}; + + my $uri = URI->new($address->address,'mailto'); + $uri->query_form($query->%*); + + my $comment = $address->comment; + # Email::Address::comment always returns a string in paretheses, + # but we don't want that, since we add them back in as_header_raw + $comment =~ s{\A\((.*)\)\z}{$1} if $comment; + + return { + uri => $uri, + comment => $comment, + }; +} around BUILDARGS => sub { my ($orig, $class, @args) = @_; - if (@args != 1 or ref($args[0]) eq 'HASH') { + if (@args != 1 or ref($args[0]) eq 'HASH' and $args[0]->{uri}) { return $class->$orig(@args); } my $item = $args[0]; if (is_Address($item)) { - return Sietima::HeaderURI->_args_from_address($item); + return _args_from_address($item); } elsif (is_Uri($item)) { return { uri => $item }; } - elsif (my $address = AddressFromStr->coerce($item)) { - return Sietima::HeaderURI->_args_from_address($address); + elsif (is_Str($item) and my $address = AddressFromStr->coerce($item)) { + return _args_from_address($address); } else { return { uri => $item }; }; }; -sub _args_from_address { - my ($class, $address, $query) = @_; - $query ||= {}; +=method C - my $uri = URI->new($address->address,'mailto'); - $uri->query_form($query->%*); + Sietima::HeaderURI->new_from_address( + $email_address, + \%query, + ); - return { - uri => $uri, - comment => $address->comment, - }; -} +This constructor builds a complex C URI with the query hash +you provide. It's a shortcut for: + + my $uri = URI->new("mailto:$email_address"); + $uri->query_form(\%query); + +Common query keys are C and C. See RFC 6068 ("The +'mailto' URI Scheme") for details. + +=cut sub new_from_address { state $check = compile( @@ -66,15 +166,42 @@ sub new_from_address { ); my ($class, $address, $query) = $check->(@_); - return $class->new($class->_args_from_address($address,$query)); + return $class->new(_args_from_address($address,$query)); } +=method C + + $mail->header_raw_set('List-Thing' => $headeruri->as_header_raw); + +This method returns a string representation of the L and +L in the format specified by RFC 2369 ("The Use of URLs as +Meta-Syntax for Core Mail List Commands and their Transport through +Message Header Fields"). + +For example: + + Sietima::HeaderURI->new({ + uri => 'http://foo/', comment => 'a thing', + })->as_header_raw eq ' (a thing)'; + + Sietima::HeaderURI->new( '(comment) address@example.com' ) + ->as_header_raw eq ' (comment)'; + + Sietima::HeaderURI->new( 'http://some/url' ) + ->as_header_raw eq ''; + +Notice that, since the list management headers are I, they +should always be set with L<< +C|Email::Simple::Header/header_raw_set >>. + +=cut + sub as_header_raw { my ($self) = @_; my $str = sprintf '<%s>',$self->uri; if (my $c = $self->comment) { - $str .= ' '.$c; + $str .= sprintf ' (%s)',$c; } return $str; diff --git a/lib/Sietima/Role/Headers.pm b/lib/Sietima/Role/Headers.pm index 5ab0d58..794a5e9 100644 --- a/lib/Sietima/Role/Headers.pm +++ b/lib/Sietima/Role/Headers.pm @@ -32,10 +32,27 @@ $self->list_addresses->{return_path} >> value (which is normally the same as the L<< C|Sietima/return_path >> attribute). Other C headers are built from the other values in the -C hashref. Those values can either be L<< -C >> objects (in which case the header will have a -C URI as value) or strings (which will be used literally for -the value of the header). +C hashref. Each of those values can be: + +=begin :list + +* an L<< C >> object + +* a thing that can be passed to that class's constructor: + +=for :list +* an L<< C >> object +* a L<< C >> object +* a string parseable as either + +* an arrayref containing any mix of the above + +=end :list + +As a special case, if C<< $self->list_addresses->{post} >> exists and +is false, the C header will have the value C to +indicate that the list does not accept incoming messages (e.g. it's an +announcement list). =attr C diff --git a/t/tests/sietima/headeruri.t b/t/tests/sietima/headeruri.t new file mode 100644 index 0000000..c158f3d --- /dev/null +++ b/t/tests/sietima/headeruri.t @@ -0,0 +1,80 @@ +#!perl +use lib 't/lib'; +use Test::Sietima; +use Email::Address; +use URI; +use Sietima::HeaderURI; + +subtest 'new' => sub { + is( + Sietima::HeaderURI->new({ + uri => 'http://foo/', + comment => 'a thing', + })->as_header_raw, + ' (a thing)', + 'normal constructor call', + ); + + is( + Sietima::HeaderURI->new( + '(comment) address@example.com', + )->as_header_raw, + ' (comment)', + 'string, address+comment', + ); + + is( + Sietima::HeaderURI->new( + 'http://some/url' + )->as_header_raw, + '', + 'string, URI', + ); + + is( + Sietima::HeaderURI->new( + { scheme => 'https', host => 'foo', path => [1,2,3] } + )->as_header_raw, + '', + 'hashref, URI::FromHash', + ); + + is( + Sietima::HeaderURI->new( + URI->new('http://bar') + )->as_header_raw, + '', + 'URI object', + ); + + is( + Sietima::HeaderURI->new( + Email::Address->parse('(comment) address@example.com'), + )->as_header_raw, + ' (comment)', + 'Email::Address object', + ); +}; + + +subtest 'new_from_address' => sub { + + is( + Sietima::HeaderURI->new_from_address( + '(comment) address@example.com', + )->as_header_raw, + ' (comment)', + 'string', + ); + + is( + Sietima::HeaderURI->new_from_address( + '(comment) address@example.com', + { subject => 'test me' }, + )->as_header_raw, + ' (comment)', + 'string and hashref', + ); +}; + +done_testing; -- cgit v1.2.3