Make use of generics instead of a method

Allows Crystal to more accurately know the return type of `ordered_elements`
Add example of `ordered_elements` usage
This commit is contained in:
George Dietrich 2020-12-24 01:05:12 -05:00
parent 83eda1298c
commit 2c40931550
6 changed files with 28 additions and 32 deletions

View file

@ -112,6 +112,8 @@ struct NegotiatorTest < NegotiatorTestCase
def test_ordered_elements(header : String, expected : Indexable(String)) : Nil def test_ordered_elements(header : String, expected : Indexable(String)) : Nil
elements = @negotiator.ordered_elements header elements = @negotiator.ordered_elements header
elements.should be_a Array(ANG::Accept)
expected.each_with_index do |element, idx| expected.each_with_index do |element, idx|
elements[idx].should be_a ANG::Accept elements[idx].should be_a ANG::Accept
element.should eq elements[idx].header element.should eq elements[idx].header

View file

@ -1,5 +1,5 @@
# Base negotiator type. Implements logic common to all negotiators. # Base negotiator type. Implements logic common to all negotiators.
abstract class Athena::Negotiation::AbstractNegotiator abstract class Athena::Negotiation::AbstractNegotiator(HeaderType)
private record OrderKey, quality : Float32, index : Int32, value : String do private record OrderKey, quality : Float32, index : Int32, value : String do
include Comparable(self) include Comparable(self)
@ -9,24 +9,22 @@ abstract class Athena::Negotiation::AbstractNegotiator
end end
end end
private abstract def create_header(header : String) : ANG::BaseAccept # Returns the best `HeaderType` type based on the provided *header* value and *priorities*.
# Returns the best `ANG::BaseAccept` type based on the provided *header* value and *priorities*.
# #
# See `Athena::Negotiation` for examples. # See `Athena::Negotiation` for examples.
def best(header : String, priorities : Indexable(String), strict : Bool = false) : ANG::BaseAccept? def best(header : String, priorities : Indexable(String), strict : Bool = false) : HeaderType?
raise ArgumentError.new "priorities should not be empty." if priorities.empty? raise ArgumentError.new "priorities should not be empty." if priorities.empty?
raise ArgumentError.new "The header string should not be empty." if header.blank? raise ArgumentError.new "The header string should not be empty." if header.blank?
accepted_headers = Array(ANG::BaseAccept).new accepted_headers = Array(HeaderType).new
self.parse_header(header) do |h| self.parse_header(header) do |h|
accepted_headers << self.create_header h accepted_headers << HeaderType.new h
rescue ex rescue ex
raise ex if strict raise ex if strict
end end
accepted_priorties = priorities.map &->create_header(String) accepted_priorties = priorities.map { |p| HeaderType.new p }
matches = self.find_matches accepted_headers, accepted_priorties matches = self.find_matches accepted_headers, accepted_priorties
@ -41,18 +39,28 @@ abstract class Athena::Negotiation::AbstractNegotiator
match.nil? ? nil : accepted_priorties[match.index] match.nil? ? nil : accepted_priorties[match.index]
end end
# Returns an array of `ANG::BaseAccept` types that the provided *header* allows, ordered so that the `#best` match is first. # Returns an array of `HeaderType` types that the provided *header* allows, ordered so that the `#best` match is first.
# #
# See `Athena::Negotiation` for examples. # ```
def ordered_elements(header : String) : Array(ANG::BaseAccept) # header = "text/*;q=0.3, text/html;q=0.7, text/html;level=1, text/html;level=2;q=0.4, */*;q=0.5"
#
# ordered_elements = ANG.negotiator.ordered_elements header
#
# ordered_elements[0].media_range # => "text/html"
# ordered_elements[1].media_range # => "text/html"
# ordered_elements[2].media_range # => "*/*"
# ordered_elements[3].media_range # => "text/html"
# ordered_elements[4].media_range # => "text/*"
# ```
def ordered_elements(header : String) : Array(HeaderType)
raise ArgumentError.new "The header string should not be empty." if header.blank? raise ArgumentError.new "The header string should not be empty." if header.blank?
elements = Array(ANG::BaseAccept).new elements = Array(HeaderType).new
order_keys = Array(OrderKey).new order_keys = Array(OrderKey).new
idx = 0 idx = 0
self.parse_header(header) do |h| self.parse_header(header) do |h|
element = self.create_header h element = HeaderType.new h
elements << element elements << element
order_keys << OrderKey.new element.quality, idx, element.header order_keys << OrderKey.new element.quality, idx, element.header
rescue ex rescue ex
@ -85,7 +93,7 @@ abstract class Athena::Negotiation::AbstractNegotiator
end end
end end
private def find_matches(headers : Array(ANG::BaseAccept), priorities : Indexable(ANG::BaseAccept)) : Array(ANG::AcceptMatch) private def find_matches(headers : Array(HeaderType), priorities : Indexable(HeaderType)) : Array(ANG::AcceptMatch)
matches = [] of ANG::AcceptMatch matches = [] of ANG::AcceptMatch
priorities.each_with_index do |priority, idx| priorities.each_with_index do |priority, idx|

View file

@ -1,8 +1,5 @@
require "./abstract_negotiator" require "./abstract_negotiator"
# A `ANG::AbstractNegotiator` implementation to negotiate `ANG::AcceptCharset` headers. # A `ANG::AbstractNegotiator` implementation to negotiate `ANG::AcceptCharset` headers.
class Athena::Negotiation::CharsetNegotiator < Athena::Negotiation::AbstractNegotiator class Athena::Negotiation::CharsetNegotiator < Athena::Negotiation::AbstractNegotiator(Athena::Negotiation::AcceptCharset)
private def create_header(header : String) : ANG::BaseAccept
ANG::AcceptCharset.new header
end
end end

View file

@ -1,8 +1,5 @@
require "./abstract_negotiator" require "./abstract_negotiator"
# A `ANG::AbstractNegotiator` implementation to negotiate `ANG::AcceptEncoding` headers. # A `ANG::AbstractNegotiator` implementation to negotiate `ANG::AcceptEncoding` headers.
class Athena::Negotiation::EncodingNegotiator < Athena::Negotiation::AbstractNegotiator class Athena::Negotiation::EncodingNegotiator < Athena::Negotiation::AbstractNegotiator(Athena::Negotiation::AcceptEncoding)
private def create_header(header : String) : ANG::BaseAccept
ANG::AcceptEncoding.new header
end
end end

View file

@ -1,7 +1,7 @@
require "./abstract_negotiator" require "./abstract_negotiator"
# A `ANG::AbstractNegotiator` implementation to negotiate `ANG::AcceptLanguage` headers. # A `ANG::AbstractNegotiator` implementation to negotiate `ANG::AcceptLanguage` headers.
class Athena::Negotiation::LanguageNegotiator < Athena::Negotiation::AbstractNegotiator class Athena::Negotiation::LanguageNegotiator < Athena::Negotiation::AbstractNegotiator(Athena::Negotiation::AcceptLanguage)
protected def match(accept : ANG::AcceptLanguage, priority : ANG::AcceptLanguage, index : Int32) : ANG::AcceptMatch? protected def match(accept : ANG::AcceptLanguage, priority : ANG::AcceptLanguage, index : Int32) : ANG::AcceptMatch?
accept_base = accept.language accept_base = accept.language
priority_base = priority.language priority_base = priority.language
@ -20,8 +20,4 @@ class Athena::Negotiation::LanguageNegotiator < Athena::Negotiation::AbstractNeg
nil nil
end end
private def create_header(header : String) : ANG::BaseAccept
ANG::AcceptLanguage.new header
end
end end

View file

@ -1,7 +1,7 @@
require "./abstract_negotiator" require "./abstract_negotiator"
# A `ANG::AbstractNegotiator` implementation to negotiate `ANG::Accept` headers. # A `ANG::AbstractNegotiator` implementation to negotiate `ANG::Accept` headers.
class Athena::Negotiation::Negotiator < Athena::Negotiation::AbstractNegotiator class Athena::Negotiation::Negotiator < Athena::Negotiation::AbstractNegotiator(Athena::Negotiation::Accept)
# TODO: Make this method less complex. # TODO: Make this method less complex.
# #
# ameba:disable Metrics/CyclomaticComplexity # ameba:disable Metrics/CyclomaticComplexity
@ -63,8 +63,4 @@ class Athena::Negotiation::Negotiator < Athena::Negotiation::AbstractNegotiator
sub_type.split '+', limit: 2 sub_type.split '+', limit: 2
end end
private def create_header(header : String) : ANG::BaseAccept
ANG::Accept.new header
end
end end