From 2c40931550f6edd8641d24f74511b82a8908ab6a Mon Sep 17 00:00:00 2001 From: George Dietrich Date: Thu, 24 Dec 2020 01:05:12 -0500 Subject: [PATCH] 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 --- spec/negotiator_spec.cr | 2 ++ src/abstract_negotiator.cr | 36 ++++++++++++++++++++++-------------- src/charset_negotiator.cr | 5 +---- src/encoding_negotiator.cr | 5 +---- src/language_negotiator.cr | 6 +----- src/negotiator.cr | 6 +----- 6 files changed, 28 insertions(+), 32 deletions(-) diff --git a/spec/negotiator_spec.cr b/spec/negotiator_spec.cr index 946d3c2..2c89e54 100644 --- a/spec/negotiator_spec.cr +++ b/spec/negotiator_spec.cr @@ -112,6 +112,8 @@ struct NegotiatorTest < NegotiatorTestCase def test_ordered_elements(header : String, expected : Indexable(String)) : Nil elements = @negotiator.ordered_elements header + elements.should be_a Array(ANG::Accept) + expected.each_with_index do |element, idx| elements[idx].should be_a ANG::Accept element.should eq elements[idx].header diff --git a/src/abstract_negotiator.cr b/src/abstract_negotiator.cr index a7c47c4..6c014e2 100644 --- a/src/abstract_negotiator.cr +++ b/src/abstract_negotiator.cr @@ -1,5 +1,5 @@ # 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 include Comparable(self) @@ -9,24 +9,22 @@ abstract class Athena::Negotiation::AbstractNegotiator end end - private abstract def create_header(header : String) : ANG::BaseAccept - - # Returns the best `ANG::BaseAccept` type based on the provided *header* value and *priorities*. + # Returns the best `HeaderType` type based on the provided *header* value and *priorities*. # # 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 "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| - accepted_headers << self.create_header h + accepted_headers << HeaderType.new h rescue ex raise ex if strict end - accepted_priorties = priorities.map &->create_header(String) + accepted_priorties = priorities.map { |p| HeaderType.new p } matches = self.find_matches accepted_headers, accepted_priorties @@ -41,18 +39,28 @@ abstract class Athena::Negotiation::AbstractNegotiator match.nil? ? nil : accepted_priorties[match.index] 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? - elements = Array(ANG::BaseAccept).new + elements = Array(HeaderType).new order_keys = Array(OrderKey).new idx = 0 self.parse_header(header) do |h| - element = self.create_header h + element = HeaderType.new h elements << element order_keys << OrderKey.new element.quality, idx, element.header rescue ex @@ -85,7 +93,7 @@ abstract class Athena::Negotiation::AbstractNegotiator 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 priorities.each_with_index do |priority, idx| diff --git a/src/charset_negotiator.cr b/src/charset_negotiator.cr index 37aa4c5..9916e67 100644 --- a/src/charset_negotiator.cr +++ b/src/charset_negotiator.cr @@ -1,8 +1,5 @@ require "./abstract_negotiator" # A `ANG::AbstractNegotiator` implementation to negotiate `ANG::AcceptCharset` headers. -class Athena::Negotiation::CharsetNegotiator < Athena::Negotiation::AbstractNegotiator - private def create_header(header : String) : ANG::BaseAccept - ANG::AcceptCharset.new header - end +class Athena::Negotiation::CharsetNegotiator < Athena::Negotiation::AbstractNegotiator(Athena::Negotiation::AcceptCharset) end diff --git a/src/encoding_negotiator.cr b/src/encoding_negotiator.cr index 40ec02f..43fbe49 100644 --- a/src/encoding_negotiator.cr +++ b/src/encoding_negotiator.cr @@ -1,8 +1,5 @@ require "./abstract_negotiator" # A `ANG::AbstractNegotiator` implementation to negotiate `ANG::AcceptEncoding` headers. -class Athena::Negotiation::EncodingNegotiator < Athena::Negotiation::AbstractNegotiator - private def create_header(header : String) : ANG::BaseAccept - ANG::AcceptEncoding.new header - end +class Athena::Negotiation::EncodingNegotiator < Athena::Negotiation::AbstractNegotiator(Athena::Negotiation::AcceptEncoding) end diff --git a/src/language_negotiator.cr b/src/language_negotiator.cr index c881f6d..4d0304c 100644 --- a/src/language_negotiator.cr +++ b/src/language_negotiator.cr @@ -1,7 +1,7 @@ require "./abstract_negotiator" # 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? accept_base = accept.language priority_base = priority.language @@ -20,8 +20,4 @@ class Athena::Negotiation::LanguageNegotiator < Athena::Negotiation::AbstractNeg nil end - - private def create_header(header : String) : ANG::BaseAccept - ANG::AcceptLanguage.new header - end end diff --git a/src/negotiator.cr b/src/negotiator.cr index c76f350..18b3042 100644 --- a/src/negotiator.cr +++ b/src/negotiator.cr @@ -1,7 +1,7 @@ require "./abstract_negotiator" # 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. # # ameba:disable Metrics/CyclomaticComplexity @@ -63,8 +63,4 @@ class Athena::Negotiation::Negotiator < Athena::Negotiation::AbstractNegotiator sub_type.split '+', limit: 2 end - - private def create_header(header : String) : ANG::BaseAccept - ANG::Accept.new header - end end