Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Add ->decoded_content and a few utility methods. #3

Open
wants to merge 3 commits into from

2 participants

Kent Fredric David Golden
Kent Fredric
  ->decoded_content(?forced_contenttype) => scalar
  ->content_type()   => scalar or undef
  ->content_type_params => arrayref

decoded_content optionally takes a encoding field that overrides
autodetection logic and ultimately serves as a shorthand that means

  ->decoded_content('utf-8')

Yields

  Encode::decode('utf-8', $result->content, Encode::FB_CROAK );

Removal of this optional parameter will revert to using content_type and
content_type_params to determine the decoding:

  1. If there is no content-type, there is no decoding.
  2. If the content type is not text/* , there is no decoding.
  3. If the content type contains no parameters after a ';' , there is no decoding.
  4. If a parameter in the parameter list matches charset=(.*), $1 is picked as an encoding.
  5. If there are multiple such parameters, the rightmost one takes precedence.
  6. If there is no such parameter, there is no encoding.

The design of the above is also designed to make it easy for people who
are working with content-types that don't qualify as "text" as per
IANA/W3C, and may have non-standard parameters that are also not
supported in the spec.

For instance, myself, I'm dealing with some upstream declaring
'application/json;charset=utf-8', and I could potentially map that
locally now as:

  if ( $result->content_type eq 'application/json' ) {
    my $charset = 'utf-8'; # Spec says to assume utf8 anyway
    for my $param (@{ $result->content_params }) {
      if ( $param =~ /^charset=(.*)$/ ) {
        $charset = $1;
      }
    }
    return $json->decode( $result->decoded_content( $charset ) );
  }

And that would theoretically work if upstream went insane and changed
'=utf-8' to '=iso-8859-15'.

David Golden
Owner

Thanks! FYI, I probably won't get a chance to review it until later this week. If you haven't heard from me by next week, feel free to remind me.

David Golden
Owner

Sorry to take so long.

Overall it seems useful and well designed. I wonder about Encode::FB_CROAK as a default. At the least, it needs to be documented. I wonder whether decoded_content should take a optional hashref of options to control such things

$response->decoded_content({ encoding => 'utf8', fallback => Encode::FB_DEFAULT})

I also wonder whether you want to differ between a forced content type and a default one. Might someone want to say "assume UTF-8 unless specified explicitly"?

Kent Fredric

DDDing to see how this looks:

$response->decoded_content(\%opts);

encoding

The decoding type to use if none is specified

Default is none: Returns bytes off the wire.

force_encoding

Use encoding regardless of detected encoding

fallback

How to handle errors in decoding.

Defaults to Encode::FB_CROAK,

Just the question is about sane defaults:

Should fallback be FB_DEFAULT or FB_CROAK by default?

Should specifying an encoding imply force_encoding by default or not?

And it should be clear from reading above that:

$response->decoded_content({ encoding => undef, force_encoding => 1 });

Should function the same as

$response->content();
David Golden
Owner

Bikeshedding, I'd say just force rather than force_encoding since it's more clearly binary and not an alternate encoding.

I agree this:

$response->decoded_content({ encoding => undef, force => 1 });

Would be equivalent to $response->content (bytes on wire).

I tend to think FB_DEFAULT is a better default under the "liberal in what we accept" principle. Otherwise, every ->decoded_content needs to wrapped in eval and that gets ugly quick. Someone who doesn't mind adding all the error handling code won't mind adding a fallback argument, either.

It means this:

$response->decoded_content()

will just DWIM and give a reasonably sane best-efforts answer.

What do you think?

Kent Fredric

Seems reasonable.

Kent Fredric kentfredric Add ->decoded_content and a few utility methods.
  ->decoded_content(?forced_contenttype) => scalar
  ->content_type()   => scalar or undef
  ->content_type_params => arrayref

decoded_content optionally takes a encoding field that overrides
autodetection logic and ultimately serves as a shorthand that means

  ->decoded_content('utf-8')

Yields

  Encode::decode('utf-8', $result->content, Encode::FB_CROAK );

Removal of this optional parameter will revert to using content_type and
content_type_params to determine the decoding:

  1. If there is no content-type, there is no decoding.
  2. If the content type is not text/* , there is no decoding.
  3. If the content type contains no parameters after a ';' , there is
     no decoding.
  4. If a parameter in the parameter list matches `charset=(.*)`, $1 is
     picked as an encoding.
  5. If there are multiple such parameters, the rightmost one takes
     precedence.
  6. If there is no such parameter, there is no encoding.

The design of the above is also designed to make it easy for people who
are working with content-types that don't qualify as "text" as per
IANA/W3C, and may have non-standard parameters that are also not
supported in the spec.

For instance, myself, I'm dealing with some upstream declaring
'application/json;charset=utf-8', and I could potentially map that
locally now as:

  if ( $result->content_type eq 'application/json' ) {
    my $charset = 'utf-8'; # Spec says to assume utf8 anyway
    for my $param (@{ $result->content_params }) {
      if ( $param =~ /^charset=(.*)$/ ) {
        $charset = $1;
      }
    }
    return $json->decode( $result->decoded_content( $charset ) );
  }

And that would theoretically work if upstream went insane and changed
'=utf-8' to '=iso-8859-15'.
f01b465
Kent Fredric kentfredric referenced this pull request from a commit in kentfredric/HTTP-Tiny-UA
Kent Fredric kentfredric Reimplement parts of decoded_content(%opts) as discussed in #3.
opts = {
  encoding => ?
  force    => ?
  fallback => ?
}

`encoding` sets the default encoding in cases where one cannot be
detected.

`force` overrides detection and uses the specified encoding.

`fallback` takes an Encoding.pm fallback value and defaults to
FB_DEFAULT

{ encoding => undef , force => 1 } and { force => 1 } both presently
revert `decoded_content` to work the same as `content`.
7c607f9
Kent Fredric

Wow. This bug I had in my code: this is somehow legal:

perl -MEncode -E 'say Encode::decode(q[charset=utf-8], q[hello], Encode::FB_DEFAULT )'
hello

I'm scared:

perl -MEncode -E 'say Encode::find_encoding(q[charset=utf8])->name'
Can't call method "name" on an undefined value at -e line 1.
perl -MEncode -E 'say Encode::find_encoding(q[utf8])->name'
utf8
perl -MEncode -E 'say Encode::find_encoding(q[charset=utf-8])->name'
utf-8-strict
perl -MEncode -E 'say Encode::find_encoding(q[utf-8])->name'
utf-8-strict
kentfredric added some commits
Kent Fredric kentfredric Reimplement parts of decoded_content(%opts) as discussed in #3.
opts = {
  encoding => ?
  force    => ?
  fallback => ?
}

`encoding` sets the default encoding in cases where one cannot be
detected.

`force` overrides detection and uses the specified encoding.

`fallback` takes an Encoding.pm fallback value and defaults to
FB_DEFAULT

{ encoding => undef , force => 1 } and { force => 1 } both presently
revert `decoded_content` to work the same as `content`.

Fixup code
36f94aa
Kent Fredric kentfredric Update test with new syntax, and cover the 3 kinds of defaulting/forcing 14219b4
David Golden
Owner
Kent Fredric

Just a reminder as per requested, does this need anything else done to it?

Kent Fredric kentfredric referenced this pull request in kentnl/HTTP-Tiny-Mech
Open

Return decoded_content in _unwrap_response(). #2

Kent Fredric

Ugh. Its probably worth mentioning in LWP, the nightmare is that decoded_content() seems to not pertain to the encoding of the content itself in terms of character sets, but the encoding of the content in terms of bit packing format ( ie: gzip ).

In the aforementioned pull req, it seems WWW::Mechanize does something very daft:

  • It sets a custom Accept-Encoding header without the user asking it to
  • It sets it to gzip automatically if the right IO:: stuff exists.
  • But returns ->content undecoded
  • And returns headers indicating encoded content.

Which strikes me as horrible horrible horrible design, and I'd hate to either

  • confuse LWP/WWW::Mech users thinking that method and this method do the same thing.
  • make the same mistake of making users responsible for decoding data they never requested encoded in the first place.

^ If you have strong opinions on what WWW::Mechanize should be doing here such that you agree with me, please comment on that thread, but I'm not sure we can change what WWW::Mech does :(

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Mar 23, 2014
  1. Kent Fredric

    Add ->decoded_content and a few utility methods.

    kentfredric authored
      ->decoded_content(?forced_contenttype) => scalar
      ->content_type()   => scalar or undef
      ->content_type_params => arrayref
    
    decoded_content optionally takes a encoding field that overrides
    autodetection logic and ultimately serves as a shorthand that means
    
      ->decoded_content('utf-8')
    
    Yields
    
      Encode::decode('utf-8', $result->content, Encode::FB_CROAK );
    
    Removal of this optional parameter will revert to using content_type and
    content_type_params to determine the decoding:
    
      1. If there is no content-type, there is no decoding.
      2. If the content type is not text/* , there is no decoding.
      3. If the content type contains no parameters after a ';' , there is
         no decoding.
      4. If a parameter in the parameter list matches `charset=(.*)`, $1 is
         picked as an encoding.
      5. If there are multiple such parameters, the rightmost one takes
         precedence.
      6. If there is no such parameter, there is no encoding.
    
    The design of the above is also designed to make it easy for people who
    are working with content-types that don't qualify as "text" as per
    IANA/W3C, and may have non-standard parameters that are also not
    supported in the spec.
    
    For instance, myself, I'm dealing with some upstream declaring
    'application/json;charset=utf-8', and I could potentially map that
    locally now as:
    
      if ( $result->content_type eq 'application/json' ) {
        my $charset = 'utf-8'; # Spec says to assume utf8 anyway
        for my $param (@{ $result->content_params }) {
          if ( $param =~ /^charset=(.*)$/ ) {
            $charset = $1;
          }
        }
        return $json->decode( $result->decoded_content( $charset ) );
      }
    
    And that would theoretically work if upstream went insane and changed
    '=utf-8' to '=iso-8859-15'.
  2. Kent Fredric

    Reimplement parts of decoded_content(%opts) as discussed in #3.

    kentfredric authored
    opts = {
      encoding => ?
      force    => ?
      fallback => ?
    }
    
    `encoding` sets the default encoding in cases where one cannot be
    detected.
    
    `force` overrides detection and uses the specified encoding.
    
    `fallback` takes an Encoding.pm fallback value and defaults to
    FB_DEFAULT
    
    { encoding => undef , force => 1 } and { force => 1 } both presently
    revert `decoded_content` to work the same as `content`.
    
    Fixup code
  3. Kent Fredric
This page is out of date. Refresh to see the latest.
Showing with 428 additions and 0 deletions.
  1. +96 −0 lib/HTTP/Tiny/UA/Response.pm
  2. +332 −0 t/300_decode_content.t
96 lib/HTTP/Tiny/UA/Response.pm
View
@@ -7,6 +7,7 @@ package HTTP::Tiny::UA::Response;
# VERSION
use Class::Tiny qw( success url status reason content headers protocol );
+use Encode qw();
=attr success
@@ -37,6 +38,101 @@ sub header {
return $self->headers->{ lc $field };
}
+=method content_type
+
+Returns the L<< C<type/subtype>|http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7 >> portion of the C<content-type> header.
+
+Returns C<undef> if there was no C<content-type> header.
+
+ if ( $result->content_type eq 'application/json' ) {
+ ...
+ }
+
+=cut
+
+sub content_type {
+ my ($self) = @_;
+ return unless exists $self->headers->{'content-type'};
+ return
+ unless my ($type) =
+ $self->headers->{'content-type'} =~ qr{ \A ( [^/]+ / [^;]+ ) }msx;
+ return $type;
+}
+
+=method content_type_params
+
+Returns all L<< C<parameter>|http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7 >> parts of the C<content-type> header
+as an C<ArrayRef>.
+
+Returns an empty C<ArrayRef> if no such parameters were sent in the C<content-type> header, or there was no C<content-type> header.
+
+ for my $header ( @{ $result->content_type_params } ) {
+ if ( $header =~ /^charset=(.+)/ ) {
+ print "A charset of $1 was specified! :D";
+ }
+ }
+
+=cut
+
+sub content_type_params {
+ my ($self) = @_;
+ return [] unless exists $self->headers->{'content-type'};
+ return []
+ unless my (@params) = $self->headers->{'content-type'} =~ qr{ (?:;([^;]+))+ }msx;
+ return [@params];
+}
+
+=method decoded_content
+
+ ->decoded_content(\%opts);
+
+Returns L<< C<< ->content >>|/content >> after applying type specific decoding.
+
+At present, this means everything that is not C<text/*> will simply yield C<< ->content >>
+
+And everything that is C<text/*> without a C<text/*;charset=someencoding> will simply yield C<< ->content >>
+
+ my $foo = $result->decoded_content(); # text/* with a specified encoding interpreted properly.
+
+Optionally, you can pass a default encoding to apply if none is specified: and override smart detection.
+
+ my $foo = $result->decoded_content({ encoding => 'utf-8' }); # utf8 assumed if none is specified
+
+And, you can force an encoding to apply to override smart detection.
+
+ my $foo = $result->decoded_content({ encoding => 'utf-8', force => 1 }); # type specific encodings ignored, utf-8 forced.
+
+By default, decoding is I<best effort>, using C<Encoding::FB_DEFAULT> to handle unusual cases.
+
+This can be overridden:
+
+ my $foo = $result->decoded_content({ encoding => 'utf-8', force => 1, fallback => Encoding::FB_CROAK }); # Bad utf8 == die
+
+=cut
+
+sub decoded_content {
+ my ( $self, $opts ) = @_;
+
+ $opts = {} unless $opts;
+
+ my $encoding = $opts->{encoding};
+
+ my $fallback = exists $opts->{fallback} ? $opts->{fallback} : Encode::FB_DEFAULT;
+
+ encodingsniff: {
+ last if $opts->{force};
+ last if not my $type = $self->content_type;
+ last unless $type =~ qr{ \Atext/ }msx;
+ for my $param ( @{ $self->content_type_params } ) {
+ if ( $param =~ qr{ \Acharset=(.+)\z }msx ) {
+ $encoding = $1;
+ }
+ }
+ }
+ return $self->content if not defined $encoding;
+ return Encode::decode( $encoding, $self->content, $fallback );
+}
+
1;
=for Pod::Coverage BUILD
332 t/300_decode_content.t
View
@@ -0,0 +1,332 @@
+use strict;
+use warnings;
+
+use Test::More;
+
+# ABSTRACT: Simulate decoding content
+
+use HTTP::Tiny::UA::Response;
+use Test::Fatal qw( exception );
+
+my $utf8_string = "\x{100}\x{2192}\x{2193}";
+utf8::encode( my $encoded_string = $utf8_string );
+
+isnt( $utf8_string, $encoded_string,
+ "Character String and Byte String should be different" );
+
+can_ok( 'HTTP::Tiny::UA::Response',
+ qw( content decoded_content content_type content_type_params) );
+
+subtest 'No Defaults' => sub {
+ my $args_hash = {};
+
+ subtest 'No Hints' => sub {
+ my $response = HTTP::Tiny::UA::Response->new(
+ success => 1,
+ protocol => 'HTTP/1.1',
+ status => 200,
+ url => 'synthetic://HTTP.Tiny.UA/300_decode_content.t',
+ content => $encoded_string,
+ headers => {},
+ );
+ my $e;
+ is(
+ $e = exception {
+ is( $response->content, $encoded_string, 'content is not decoded' );
+ is( $response->decoded_content($args_hash),
+ $encoded_string, 'encoded_content is same as content here' );
+ is( $response->content_type, undef, 'no content type available' );
+ is_deeply( $response->content_type_params, [], 'no content type params' );
+ },
+ undef,
+ 'All expected methods defined and not throwing exceptions'
+ ) or diag $e ;
+ };
+
+ subtest 'simple text/plain, No Hints' => sub {
+ my $response = HTTP::Tiny::UA::Response->new(
+ success => 1,
+ protocol => 'HTTP/1.1',
+ status => 200,
+ url => 'synthetic://HTTP.Tiny.UA/300_decode_content.t',
+ content => $encoded_string,
+ headers => { 'content-type' => 'text/plain', }
+ );
+ my $e;
+ is(
+ $e = exception {
+
+ is( $response->content, $encoded_string, 'content is not decoded' );
+ is( $response->decoded_content($args_hash),
+ $encoded_string, 'encoded_content is the same as content here' );
+ is( $response->content_type, 'text/plain', 'content type is text/plain' );
+ is_deeply( $response->content_type_params, [], 'no content type params' );
+
+ },
+ undef,
+ 'All expected methods defined and not throwing exceptions'
+ ) or diag $e;
+ };
+
+ subtest 'text/plain;charset=utf-8' => sub {
+ my $response = HTTP::Tiny::UA::Response->new(
+ success => 1,
+ protocol => 'HTTP/1.1',
+ status => 200,
+ url => 'synthetic://HTTP.Tiny.UA/300_decode_content.t',
+ content => $encoded_string,
+ headers => { 'content-type' => 'text/plain;charset=utf-8', },
+ );
+ my $e;
+ is(
+ $e = exception {
+ is( $response->content, $encoded_string, 'content is not decoded' );
+ is( $response->decoded_content($args_hash),
+ $utf8_string, 'encoded_content decoded as utf8' );
+ is( $response->content_type, 'text/plain', 'content type is text/plain' );
+ is_deeply( $response->content_type_params,
+ ['charset=utf-8'], 'content type params says charset=utf8' );
+ },
+ undef,
+ 'All expected methods defined and not throwing exceptions'
+ ) or diag $e;
+ };
+
+};
+
+subtest 'Default = utf-8' => sub {
+
+ my $args_hash = { encoding => 'utf-8' };
+
+ subtest 'No Hints' => sub {
+ my $response = HTTP::Tiny::UA::Response->new(
+ success => 1,
+ protocol => 'HTTP/1.1',
+ status => 200,
+ url => 'synthetic://HTTP.Tiny.UA/300_decode_content.t',
+ content => $encoded_string,
+ headers => {},
+ );
+ my $e;
+ is(
+ $e = exception {
+ is( $response->content, $encoded_string, 'content is not decoded' );
+ is( $response->decoded_content($args_hash),
+ $utf8_string, 'decoded_content decodes as utf8' );
+ is( $response->content_type, undef, 'no content type available' );
+ is_deeply( $response->content_type_params, [], 'no content type params' );
+ },
+ undef,
+ 'All expected methods defined and not throwing exceptions'
+ ) or diag $e ;
+ };
+
+ subtest 'simple text/plain, No Hints' => sub {
+ my $response = HTTP::Tiny::UA::Response->new(
+ success => 1,
+ protocol => 'HTTP/1.1',
+ status => 200,
+ url => 'synthetic://HTTP.Tiny.UA/300_decode_content.t',
+ content => $encoded_string,
+ headers => { 'content-type' => 'text/plain', }
+ );
+ my $e;
+ is(
+ $e = exception {
+
+ is( $response->content, $encoded_string, 'content is not decoded' );
+ is( $response->decoded_content($args_hash),
+ $utf8_string, 'decoded_content decodes as utf8' );
+ is( $response->content_type, 'text/plain', 'content type is text/plain' );
+ is_deeply( $response->content_type_params, [], 'no content type params' );
+
+ },
+ undef,
+ 'All expected methods defined and not throwing exceptions'
+ ) or diag $e;
+ };
+
+ subtest 'text/plain;charset=utf-8' => sub {
+ my $response = HTTP::Tiny::UA::Response->new(
+ success => 1,
+ protocol => 'HTTP/1.1',
+ status => 200,
+ url => 'synthetic://HTTP.Tiny.UA/300_decode_content.t',
+ content => $encoded_string,
+ headers => { 'content-type' => 'text/plain;charset=utf-8', },
+ );
+ my $e;
+ is(
+ $e = exception {
+ is( $response->content, $encoded_string, 'content is not decoded' );
+ is( $response->decoded_content($args_hash),
+ $utf8_string, 'decoded_content decodes as utf8' );
+ is( $response->content_type, 'text/plain', 'content type is text/plain' );
+ is_deeply( $response->content_type_params,
+ ['charset=utf-8'], 'content type params says charset=utf8' );
+ },
+ undef,
+ 'All expected methods defined and not throwing exceptions'
+ ) or diag $e;
+ };
+
+};
+
+subtest 'Default = utf-8 + force ' => sub {
+
+ my $args_hash = { encoding => 'utf-8', force => 1 };
+
+ subtest 'No Hints' => sub {
+ my $response = HTTP::Tiny::UA::Response->new(
+ success => 1,
+ protocol => 'HTTP/1.1',
+ status => 200,
+ url => 'synthetic://HTTP.Tiny.UA/300_decode_content.t',
+ content => $encoded_string,
+ headers => {},
+ );
+ my $e;
+ is(
+ $e = exception {
+ is( $response->content, $encoded_string, 'content is not decoded' );
+ is( $response->decoded_content($args_hash),
+ $utf8_string, 'decoded_content decodes as utf8' );
+ is( $response->content_type, undef, 'no content type available' );
+ is_deeply( $response->content_type_params, [], 'no content type params' );
+ },
+ undef,
+ 'All expected methods defined and not throwing exceptions'
+ ) or diag $e ;
+ };
+
+ subtest 'simple text/plain, No Hints' => sub {
+ my $response = HTTP::Tiny::UA::Response->new(
+ success => 1,
+ protocol => 'HTTP/1.1',
+ status => 200,
+ url => 'synthetic://HTTP.Tiny.UA/300_decode_content.t',
+ content => $encoded_string,
+ headers => { 'content-type' => 'text/plain', }
+ );
+ my $e;
+ is(
+ $e = exception {
+
+ is( $response->content, $encoded_string, 'content is not decoded' );
+ is( $response->decoded_content($args_hash),
+ $utf8_string, 'decoded_content decodes as utf8' );
+ is( $response->content_type, 'text/plain', 'content type is text/plain' );
+ is_deeply( $response->content_type_params, [], 'no content type params' );
+
+ },
+ undef,
+ 'All expected methods defined and not throwing exceptions'
+ ) or diag $e;
+ };
+
+ subtest 'text/plain;charset=utf-8' => sub {
+ my $response = HTTP::Tiny::UA::Response->new(
+ success => 1,
+ protocol => 'HTTP/1.1',
+ status => 200,
+ url => 'synthetic://HTTP.Tiny.UA/300_decode_content.t',
+ content => $encoded_string,
+ headers => { 'content-type' => 'text/plain;charset=utf-8', },
+ );
+ my $e;
+ is(
+ $e = exception {
+ is( $response->content, $encoded_string, 'content is not decoded' );
+ is( $response->decoded_content($args_hash),
+ $utf8_string, 'decoded_content decodes as utf8' );
+ is( $response->content_type, 'text/plain', 'content type is text/plain' );
+ is_deeply( $response->content_type_params,
+ ['charset=utf-8'], 'content type params says charset=utf8' );
+ },
+ undef,
+ 'All expected methods defined and not throwing exceptions'
+ ) or diag $e;
+ };
+
+};
+
+subtest 'Default = undef + force ' => sub {
+
+ my $args_hash = { encoding => undef, force => 1 };
+
+ subtest 'No Hints' => sub {
+ my $response = HTTP::Tiny::UA::Response->new(
+ success => 1,
+ protocol => 'HTTP/1.1',
+ status => 200,
+ url => 'synthetic://HTTP.Tiny.UA/300_decode_content.t',
+ content => $encoded_string,
+ headers => {},
+ );
+ my $e;
+ is(
+ $e = exception {
+ is( $response->content, $encoded_string, 'content is not decoded' );
+ is( $response->decoded_content($args_hash),
+ $encoded_string, 'decoded_content does not decode' );
+ is( $response->content_type, undef, 'no content type available' );
+ is_deeply( $response->content_type_params, [], 'no content type params' );
+ },
+ undef,
+ 'All expected methods defined and not throwing exceptions'
+ ) or diag $e ;
+ };
+
+ subtest 'simple text/plain, No Hints' => sub {
+ my $response = HTTP::Tiny::UA::Response->new(
+ success => 1,
+ protocol => 'HTTP/1.1',
+ status => 200,
+ url => 'synthetic://HTTP.Tiny.UA/300_decode_content.t',
+ content => $encoded_string,
+ headers => { 'content-type' => 'text/plain', }
+ );
+ my $e;
+ is(
+ $e = exception {
+
+ is( $response->content, $encoded_string, 'content is not decoded' );
+ is( $response->decoded_content($args_hash),
+ $encoded_string, 'decoded_content does not decode' );
+ is( $response->content_type, 'text/plain', 'content type is text/plain' );
+ is_deeply( $response->content_type_params, [], 'no content type params' );
+
+ },
+ undef,
+ 'All expected methods defined and not throwing exceptions'
+ ) or diag $e;
+ };
+
+ subtest 'text/plain;charset=utf-8' => sub {
+ my $response = HTTP::Tiny::UA::Response->new(
+ success => 1,
+ protocol => 'HTTP/1.1',
+ status => 200,
+ url => 'synthetic://HTTP.Tiny.UA/300_decode_content.t',
+ content => $encoded_string,
+ headers => { 'content-type' => 'text/plain;charset=utf-8', },
+ );
+ my $e;
+ is(
+ $e = exception {
+ is( $response->content, $encoded_string, 'content is not decoded' );
+ is( $response->decoded_content($args_hash),
+ $encoded_string, 'decoded_content does not decode' );
+ is( $response->content_type, 'text/plain', 'content type is text/plain' );
+ is_deeply( $response->content_type_params,
+ ['charset=utf-8'], 'content type params says charset=utf8' );
+ },
+ undef,
+ 'All expected methods defined and not throwing exceptions'
+ ) or diag $e;
+ };
+
+};
+
+done_testing;
+
Something went wrong with that request. Please try again.