libwww-perl / HTTP-Message

The HTTP-Message distribution contains classes useful for representing the messages passed in HTTP style communication.

Home Page:https://metacpan.org/pod/HTTP::Message

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

[feature request] Support: RFC 8941 Structured Field Values for HTTP

karupanerura opened this issue · comments

For example, I could be happy if the following interface was provided:

use HTTP::Headers;
use HTTP::Headers::StructuredFieldValue qw/
    type_integer type_decimal type_string type_token type_bytes type_boolean
    type_list type_inner_list type_dictionary type_key
/;

my $headers = HTTP::Headers->new();

#
# setter interfaces
#
$headers->struct('Example-Integer', type_integer(42)); # => Example-Integer: 42
$headers->struct('Example-Decimal', type_decimal(4.5)); # => Example-Decimal: 4.5
$headers->struct('Example-String', type_string('hello world')); # => Example-String: "hello world"
$headers->struct('Example-Token', type_token('foo123/456')); # => Example-Token: foo123/456
$headers->struct('Example-ByteSequence', type_bytes('pretend this is binary content.')); # => Example-ByteSequence: :cHJldGVuZCB0aGlzIGlzIGJpbmFyeSBjb250ZW50Lg==:
$headers->struct('Example-Boolean', type_boolean(1));# => Example-Boolean: ?1
# $headers->struct('Example-Boolean', !!1); # => Example-Boolean: ?1 (should it be OK? refs. https://github.com/Perl/perl5/pull/19040 ) 
$headers->struct('Example-Integer-WithParameters', type_integer(1)->with_parameters(type_key('a'), b => type_boolean(0))); # => Example-Integer-WithParameters: 1; a; b=?0 (NOTE: Parameters are an ordered map of key-value pairs)
$headers->struct('Example-List', type_list(
    type_inner_list(
        type_string('foo')->with_parameters(a => type_integer(1), b => type_integer(2))
    )->with_parameters(lvl => type_integer(5)),
    type_inner_list(type_string('bar'), type_string('baz'))->with_parameters(lvl => type_integer(1)),
)); # => Example-List: ("foo"; a=1;b=2);lvl=5, ("bar" "baz");lvl=1
$headers->struct('Example-Dict', type_dictionary(
    a => type_boolean(0),
    type_key('b'),
    type_key('c')->with_parameters(foo => type_token('bar')),
))# => Example-Dict: a=?0, b, c; foo=bar

#
# getter interfaces
#

# Example-Integer: 42
$headers->struct('Example-Integer')->value; # => 42
$headers->struct('Example-Integer')->type; # => "integer"

# Example-Decimal: 4.5
$headers->struct('Example-Decimal')->value; # => 4.5
$headers->struct('Example-Decimal')->type; # => "decimal"

# Example-String: "hello world"
$headers->struct('Example-String')->value; # => "hello world"
$headers->struct('Example-String')->type; # => "string"

# Example-Token: foo123/456
$headers->struct('Example-Token')->value; # => "foo123/456"
$headers->struct('Example-Token')->type; # => "token"

# Example-ByteSequence: :cHJldGVuZCB0aGlzIGlzIGJpbmFyeSBjb250ZW50Lg==:
$headers->struct('Example-ByteSequence')->value; # => "pretend this is binary content."
$headers->struct('Example-ByteSequence')->type; # => "byte_sequence"

# Example-Boolean: ?1
$headers->struct('Example-Boolean')->value; # => !!1 (true/PL_sv_yes)
$headers->struct('Example-Boolean')->type; # => "boolean"

# Example-Integer-WithParameters: 1; a; b=?0 (NOTE: Parameters are an ordered map of key-value pairs)
$headers->struct('Example-Integer-WithParameters')->value; # => 1
$headers->struct('Example-Integer-WithParameters')->type; # => "integer"
$headers->struct('Example-Integer-WithParameters')->has_parameters;  # => !!1 (true/PL_sv_yes)
$headers->struct('Example-Integer-WithParameters')->parameters->keys;  # =>("a", "b")
$headers->struct('Example-Integer-WithParameters')->parameters->length;  # => 2
$headers->struct('Example-Integer-WithParameters')->parameters->has_key('a'); # => !!1 (true/PL_sv_yes)
$headers->struct('Example-Integer-WithParameters')->parameters->key('a')->has_item; # => !!0 (false/PL_sv_no)
$headers->struct('Example-Integer-WithParameters')->parameters->has_key('b'); # => !!1 (true/PL_sv_yes)
$headers->struct('Example-Integer-WithParameters')->parameters->key('b')->has_item; # => !!1 (true/PL_sv_yes)
$headers->struct('Example-Integer-WithParameters')->parameters->key('b')->item->value; # => !!1 (true/PL_sv_yes)
$headers->struct('Example-Integer-WithParameters')->parameters->key('b')->item->type; # => "boolean"
$headers->struct('Example-Integer-WithParameters')->parameters->item('b')->value; # => !!1 (true/PL_sv_yes)
$headers->struct('Example-Integer-WithParameters')->parameters->item('b')->type; # => "boolean"
$headers->struct('Example-Integer-WithParameters')->parameters->has_key('c'); # =>  !!0 (false/PL_sv_no)

# Example-List: ("foo"; a=1;b=2);lvl=5, ("bar" "baz");lvl=1
$headers->struct('Example-List')->type; # => "list"
$headers->struct('Example-List')->length; # => 2
$headers->struct('Example-List')->item(0)->type; # => "inner_list"
$headers->struct('Example-List')->item(0)->length; # => 1
$headers->struct('Example-List')->item(0)->item(0)->type; # => "string"
$headers->struct('Example-List')->item(0)->item(0)->value; # => "foo"
$headers->struct('Example-List')->item(0)->item(0)->has_parameters;  # => !!1 (true/PL_sv_yes)
$headers->struct('Example-List')->item(0)->item(0)->parameters->item('a')->value; # => 1
$headers->struct('Example-List')->item(0)->item(0)->parameters->item('b')->value; # => 2
$headers->struct('Example-List')->item(0)->has_parameters;  # => !!1 (true/PL_sv_yes)
$headers->struct('Example-List')->item(0)->parameters->item('lvl')->value; # => 5
$headers->struct('Example-List')->item(1)->type; # => "inner_list"
$headers->struct('Example-List')->item(1)->length; # => 2
$headers->struct('Example-List')->item(1)->item(0)->type; # => "string"
$headers->struct('Example-List')->item(1)->item(0)->value; # => "bar"
$headers->struct('Example-List')->item(1)->item(1)->type; # => "string"
$headers->struct('Example-List')->item(1)->item(1)->value; # => "baz"
$headers->struct('Example-List')->item(1)->has_parameters;  # => !!1 (true/PL_sv_yes)
$headers->struct('Example-List')->item(1)->parameters->item('lvl')->value; # => 1

# Example-Dict: a=?0, b, c; foo=bar
$headers->struct('Example-Dict')->type; # => "dictionary"
$headers->struct('Example-Dict')->keys; # => ("a", "b", "c")
$headers->struct('Example-Dict')->length; # => 3
$headers->struct('Example-Dict')->has_key('a');  # => !!1 (true/PL_sv_yes)
$headers->struct('Example-Dict')->key('a')->has_item;  # => !!1 (true/PL_sv_yes)
$headers->struct('Example-Dict')->key('a')->has_parameters;  # => !!0 (false/PL_sv_no)
$headers->struct('Example-Dict')->key('a')->item->type; # => "boolean"
$headers->struct('Example-Dict')->key('a')->item->value; # => !!0 (false/PL_sv_no)
$headers->struct('Example-Dict')->item('a')->type; # => "boolean"
$headers->struct('Example-Dict')->item('a')->value; # => !!0 (false/PL_sv_no)
$headers->struct('Example-Dict')->has_key('b');  # => !!1 (true/PL_sv_yes)
$headers->struct('Example-Dict')->key('b')->has_item;  # => !!0 (false/PL_sv_no)
$headers->struct('Example-Dict')->key('b')->has_parameters;  # => !!0 (false/PL_sv_no)
$headers->struct('Example-Dict')->has_key('c');  # => !!1 (true/PL_sv_yes)
$headers->struct('Example-Dict')->key('c')->has_item;  # => !!0 (false/PL_sv_no)
$headers->struct('Example-Dict')->key('c')->has_parameters;  # => !!1 (true/PL_sv_yes)
$headers->struct('Example-Dict')->key('c')->parameters->item('foo')->type;  # => "token"
$headers->struct('Example-Dict')->key('c')->parameters->item('foo')->value;  # => "bar"
$headers->struct('Example-Dict')->has_key('d');  # => !!0 (false/PL_sv_no)

This looks like something that should go in its own distribution, say HTTP::Headers::WithStructuredFieldValues that adds read/write sugar via a subclass of HTTP::Headers. We would be reluctant to add a lot of code to HTTP::Headers directly that isn't expected to be heavily used or have wide-reaching value.

I have to agree with @karenetheridge about the fact that we shouldn't add too much inside HTTP::Headers.

The way I read it is that structured fields just another interesting way to add a HTTP header that has some special semantics on the string being used as its value. Parsing of that (large) string should ensure integrity of the data being passed, and either that field should be accepted, or rejected completely. It does not affect the processing of the other HTTP headers or its usage of the incoming or outgoing message itself.

Having said that, This wouldn't even require a full subclassing, just something that can handle that string.

@karupanerura , I'm willing to come up with a more easy Perl-like solution.