diff --git a/lib/protocol/http/header/accept.rb b/lib/protocol/http/header/accept.rb index 4a02b91..358a27b 100644 --- a/lib/protocol/http/header/accept.rb +++ b/lib/protocol/http/header/accept.rb @@ -113,9 +113,7 @@ def media_ranges # # @returns [Array(MediaRange)] the preferred media ranges. def preferred_media_ranges - media_ranges.sort_by.with_index do |media_range, index| - [-media_range.quality_factor, index] - end + sort_by_quality_factor(media_ranges) end private diff --git a/lib/protocol/http/header/accept_charset.rb b/lib/protocol/http/header/accept_charset.rb index 73b8db6..890d719 100644 --- a/lib/protocol/http/header/accept_charset.rb +++ b/lib/protocol/http/header/accept_charset.rb @@ -47,6 +47,15 @@ def charsets end end end + + # Parse the `accept-charset` header and order character sets by preference. + # + # Character sets with equal quality factors retain their original relative order. + # + # @returns [Array(Charset)] the preferred character sets. + def preferred_charsets + sort_by_quality_factor(charsets) + end end end end diff --git a/lib/protocol/http/header/accept_encoding.rb b/lib/protocol/http/header/accept_encoding.rb index 4f492ac..d9fa98f 100644 --- a/lib/protocol/http/header/accept_encoding.rb +++ b/lib/protocol/http/header/accept_encoding.rb @@ -50,6 +50,15 @@ def encodings end end end + + # Parse the `accept-encoding` header and order encodings by preference. + # + # Encodings with equal quality factors retain their original relative order. + # + # @returns [Array(Encoding)] the preferred encodings. + def preferred_encodings + sort_by_quality_factor(encodings) + end end end end diff --git a/lib/protocol/http/header/accept_language.rb b/lib/protocol/http/header/accept_language.rb index 45e2ce9..ee47bfd 100644 --- a/lib/protocol/http/header/accept_language.rb +++ b/lib/protocol/http/header/accept_language.rb @@ -53,6 +53,15 @@ def languages end end end + + # Parse the `accept-language` header and order languages by preference. + # + # Languages with equal quality factors retain their original relative order. + # + # @returns [Array(Language)] the preferred languages. + def preferred_languages + sort_by_quality_factor(languages) + end end end end diff --git a/lib/protocol/http/header/split.rb b/lib/protocol/http/header/split.rb index 9def591..4195c14 100644 --- a/lib/protocol/http/header/split.rb +++ b/lib/protocol/http/header/split.rb @@ -77,6 +77,14 @@ def self.trailer? end protected + # Order weighted values by descending quality while preserving their relative order. + # @parameter values [Enumerable] The values which respond to `#quality_factor`. + # @returns [Array] The ordered values. + def sort_by_quality_factor(values) + values.sort_by.with_index do |value, index| + [-value.quality_factor, index] + end + end def reverse_find(&block) reverse_each do |value| diff --git a/lib/protocol/http/header/te.rb b/lib/protocol/http/header/te.rb index 8bd01bd..17c36a3 100644 --- a/lib/protocol/http/header/te.rb +++ b/lib/protocol/http/header/te.rb @@ -113,6 +113,15 @@ def transfer_codings end end + # Parse the `te` header and order transfer codings by preference. + # + # Transfer codings with equal quality factors retain their original relative order. + # + # @returns [Array(TransferCoding)] the preferred transfer codings. + def preferred_transfer_codings + sort_by_quality_factor(transfer_codings) + end + # @returns [Boolean] whether the `chunked` encoding is accepted. def chunked? self.any?{|value| value.start_with?(CHUNKED)} diff --git a/releases.md b/releases.md index 17417b9..3361d53 100644 --- a/releases.md +++ b/releases.md @@ -2,7 +2,7 @@ ## Unreleased - - Add stable preference ordering for `Accept` media ranges. + - Add stable preference ordering for weighted `Accept`, `Accept-Charset`, `Accept-Encoding`, `Accept-Language`, and `TE` values. ## v0.69.0 diff --git a/test/protocol/http/header/accept_charset.rb b/test/protocol/http/header/accept_charset.rb index 5d4ef95..839eb10 100644 --- a/test/protocol/http/header/accept_charset.rb +++ b/test/protocol/http/header/accept_charset.rb @@ -14,7 +14,7 @@ describe Protocol::HTTP::Header::AcceptCharset do let(:header) {subject.parse(description)} - let(:charsets) {header.charsets.sort} + let(:charsets) {header.preferred_charsets} with "utf-8, iso-8859-1;q=0.5, windows-1252;q=0.25" do it "can parse charsets" do diff --git a/test/protocol/http/header/accept_encoding.rb b/test/protocol/http/header/accept_encoding.rb index 5c666dc..6269d56 100644 --- a/test/protocol/http/header/accept_encoding.rb +++ b/test/protocol/http/header/accept_encoding.rb @@ -14,7 +14,7 @@ describe Protocol::HTTP::Header::AcceptEncoding do let(:header) {subject.parse(description)} - let(:encodings) {header.encodings.sort} + let(:encodings) {header.preferred_encodings} with "gzip, deflate;q=0.5, identity;q=0.25" do it "can parse charsets" do diff --git a/test/protocol/http/header/accept_language.rb b/test/protocol/http/header/accept_language.rb index 43bf7c5..cd657f6 100644 --- a/test/protocol/http/header/accept_language.rb +++ b/test/protocol/http/header/accept_language.rb @@ -14,7 +14,7 @@ describe Protocol::HTTP::Header::AcceptLanguage do let(:header) {subject.parse(description)} - let(:languages) {header.languages.sort} + let(:languages) {header.preferred_languages} with "da, en-gb;q=0.5, en;q=0.25" do it "can parse languages" do diff --git a/test/protocol/http/header/te.rb b/test/protocol/http/header/te.rb index 7ba434b..5dde573 100644 --- a/test/protocol/http/header/te.rb +++ b/test/protocol/http/header/te.rb @@ -62,12 +62,19 @@ with "gzip;q=0.5, deflate;q=0.8" do it "handles multiple quality factors" do - codings = header.transfer_codings.sort + codings = header.preferred_transfer_codings expect(codings[0].name).to be == "deflate" # higher quality first expect(codings[1].name).to be == "gzip" end end + with "gzip, deflate;q=0.5, compress;q=0.5" do + it "preserves relative order for equal quality factors" do + codings = header.preferred_transfer_codings + expect(codings.collect(&:name)).to be == %w{gzip deflate compress} + end + end + with "empty header value" do let(:header) {subject.new}