mirror of
https://gitea.invidious.io/iv-org/shard-athena-negotiation.git
synced 2024-08-15 00:53:23 +00:00
Add exception types
Add specs Fix some bugs
This commit is contained in:
parent
8d6d8cbf79
commit
180ba3fae4
17 changed files with 433 additions and 45 deletions
|
@ -20,3 +20,6 @@ development_dependencies:
|
|||
ameba:
|
||||
github: crystal-ameba/ameba
|
||||
version: ~> 0.13.0
|
||||
athena-spec:
|
||||
github: athena-framework/spec
|
||||
version: ~> 0.2.3
|
||||
|
|
31
spec/accept_language_spec.cr
Normal file
31
spec/accept_language_spec.cr
Normal file
|
@ -0,0 +1,31 @@
|
|||
require "./spec_helper"
|
||||
|
||||
struct AcceptLanguageTest < ASPEC::TestCase
|
||||
@[DataProvider("type_data_provider")]
|
||||
def test_get_type(header : String?, expected : String?) : Nil
|
||||
ANG::AcceptLanguage.new(header).type.should eq expected
|
||||
end
|
||||
|
||||
def type_data_provider : Tuple
|
||||
{
|
||||
{"en;q=0.7", "en"},
|
||||
{"en-GB;q=0.8", "en-gb"},
|
||||
{"da", "da"},
|
||||
{"en-gb;q=0.8", "en-gb"},
|
||||
{"es;q=0.7", "es"},
|
||||
{"fr ; q= 0.1", "fr"},
|
||||
}
|
||||
end
|
||||
|
||||
@[DataProvider("value_data_provider")]
|
||||
def test_get_value(header : String?, expected : String?) : Nil
|
||||
ANG::AcceptLanguage.new(header).value.should eq expected
|
||||
end
|
||||
|
||||
def value_data_provider : Tuple
|
||||
{
|
||||
{"en;q=0.7", "en;q=0.7"},
|
||||
{"en-GB;q=0.8", "en-GB;q=0.8"},
|
||||
}
|
||||
end
|
||||
end
|
43
spec/accept_match_spec.cr
Normal file
43
spec/accept_match_spec.cr
Normal file
|
@ -0,0 +1,43 @@
|
|||
require "./spec_helper"
|
||||
|
||||
struct AcceptMatchTest < ASPEC::TestCase
|
||||
@[DataProvider("compare_data_provider")]
|
||||
def test_compare(match1 : ANG::AcceptMatch, match2 : ANG::AcceptMatch, expected : Int32) : Nil
|
||||
(match1 <=> match2).should eq expected
|
||||
end
|
||||
|
||||
def compare_data_provider : Tuple
|
||||
{
|
||||
{ANG::AcceptMatch.new(1.0, 110, 1), ANG::AcceptMatch.new(1.0, 111, 1), 0},
|
||||
{ANG::AcceptMatch.new(0.1, 10, 1), ANG::AcceptMatch.new(0.1, 10, 2), -1},
|
||||
{ANG::AcceptMatch.new(0.5, 110, 5), ANG::AcceptMatch.new(0.5, 11, 4), 1},
|
||||
{ANG::AcceptMatch.new(0.4, 110, 1), ANG::AcceptMatch.new(0.6, 111, 3), 1},
|
||||
{ANG::AcceptMatch.new(0.6, 110, 1), ANG::AcceptMatch.new(0.4, 111, 3), -1},
|
||||
}
|
||||
end
|
||||
|
||||
@[DataProvider("reduce_data_provider")]
|
||||
def test_reduce(matches : Hash(Int32, ANG::AcceptMatch), match : ANG::AcceptMatch, expected : Hash(Int32, ANG::AcceptMatch)) : Nil
|
||||
ANG::AcceptMatch.reduce(matches, match).should eq expected
|
||||
end
|
||||
|
||||
def reduce_data_provider : Tuple
|
||||
{
|
||||
{
|
||||
{1 => ANG::AcceptMatch.new(1.0, 10, 1)},
|
||||
ANG::AcceptMatch.new(0.5, 111, 1),
|
||||
{1 => ANG::AcceptMatch.new(0.5, 111, 1)},
|
||||
},
|
||||
{
|
||||
{1 => ANG::AcceptMatch.new(1.0, 110, 1)},
|
||||
ANG::AcceptMatch.new(0.5, 11, 1),
|
||||
{1 => ANG::AcceptMatch.new(1.0, 110, 1)},
|
||||
},
|
||||
{
|
||||
{0 => ANG::AcceptMatch.new(1.0, 10, 1)},
|
||||
ANG::AcceptMatch.new(0.5, 111, 1),
|
||||
{0 => ANG::AcceptMatch.new(1.0, 10, 1), 1 => ANG::AcceptMatch.new(0.5, 111, 1)},
|
||||
},
|
||||
}
|
||||
end
|
||||
end
|
54
spec/accept_spec.cr
Normal file
54
spec/accept_spec.cr
Normal file
|
@ -0,0 +1,54 @@
|
|||
require "./spec_helper"
|
||||
|
||||
struct AcceptTest < ASPEC::TestCase
|
||||
def test_parameters : Nil
|
||||
ANG::Accept.new("foo/bar; q=1; hello=world").parameters["hello"]?.should eq "world"
|
||||
end
|
||||
|
||||
@[DataProvider("normalized_value_data_provider")]
|
||||
def test_normalized_value(header : String, expected : String) : Nil
|
||||
ANG::Accept.new(header).normalized_value.should eq expected
|
||||
end
|
||||
|
||||
def normalized_value_data_provider : Tuple
|
||||
{
|
||||
{"text/html; z=y; a=b; c=d", "text/html; z=y; a=b; c=d"},
|
||||
{"application/pdf; q=1; param=p", "application/pdf; param=p"},
|
||||
}
|
||||
end
|
||||
|
||||
@[DataProvider("type_data_provider")]
|
||||
def test_type(header : String, expected : String) : Nil
|
||||
ANG::Accept.new(header).type.should eq expected
|
||||
end
|
||||
|
||||
def type_data_provider : Tuple
|
||||
{
|
||||
{"text/html;hello=world", "text/html"},
|
||||
{"application/pdf", "application/pdf"},
|
||||
{"application/xhtml+xml;q=0.9", "application/xhtml+xml"},
|
||||
{"text/plain; q=0.5", "text/plain"},
|
||||
{"text/html;level=2;q=0.4", "text/html"},
|
||||
{"text/html ; level = 2 ; q = 0.4", "text/html"},
|
||||
{"text/*", "text/*"},
|
||||
{"text/* ;q=1 ;level=2", "text/*"},
|
||||
{"*/*", "*/*"},
|
||||
{"*", "*/*"},
|
||||
{"*/* ; param=555", "*/*"},
|
||||
{"* ; param=555", "*/*"},
|
||||
{"TEXT/hTmL;leVel=2; Q=0.4", "text/html"},
|
||||
}
|
||||
end
|
||||
|
||||
@[DataProvider("value_data_provider")]
|
||||
def test_value(header : String, expected : String) : Nil
|
||||
ANG::Accept.new(header).value.should eq expected
|
||||
end
|
||||
|
||||
def value_data_provider : Tuple
|
||||
{
|
||||
{"text/html;hello=world ;q=0.5", "text/html;hello=world ;q=0.5"},
|
||||
{"application/pdf", "application/pdf"},
|
||||
}
|
||||
end
|
||||
end
|
|
@ -1,7 +0,0 @@
|
|||
require "./spec_helper"
|
||||
|
||||
describe Athena::Negotiation do
|
||||
it "works" do
|
||||
false.should eq(true)
|
||||
end
|
||||
end
|
65
spec/base_accept_spec.cr
Normal file
65
spec/base_accept_spec.cr
Normal file
|
@ -0,0 +1,65 @@
|
|||
require "./spec_helper"
|
||||
|
||||
private struct MockAccept < ANG::BaseAccept; end
|
||||
|
||||
struct BaseAcceptTest < ASPEC::TestCase
|
||||
@[DataProvider("build_parameters_data_provider")]
|
||||
def test_build_parameters_string(header : String, expected : String) : Nil
|
||||
MockAccept.new(header).normalized_value.should eq expected
|
||||
end
|
||||
|
||||
def build_parameters_data_provider : Tuple
|
||||
{
|
||||
{"media/type; xxx = 1.0;level=2;foo=bar", "media/type; xxx=1.0; level=2; foo=bar"},
|
||||
}
|
||||
end
|
||||
|
||||
@[DataProvider("parameters_data_provider")]
|
||||
def test_parse_parameters(header : String, expected_parameters : Hash(String, String)) : Nil
|
||||
accept = MockAccept.new header
|
||||
parameters = accept.parameters
|
||||
|
||||
# TODO: Can this be improved?
|
||||
if header.includes? 'q'
|
||||
parameters["q"] = accept.quality.to_s
|
||||
end
|
||||
|
||||
expected_parameters.size.should eq parameters.size
|
||||
|
||||
expected_parameters.each do |k, v|
|
||||
parameters.has_key?(k).should be_true
|
||||
parameters[k].should eq v
|
||||
end
|
||||
end
|
||||
|
||||
def parameters_data_provider : Tuple
|
||||
{
|
||||
{
|
||||
"application/json ;q=1.0; level=2;foo= bar",
|
||||
{
|
||||
"q" => "1.0",
|
||||
"level" => "2",
|
||||
"foo" => "bar",
|
||||
},
|
||||
},
|
||||
{
|
||||
"application/json ;q = 1.0; level = 2; FOO = bAr",
|
||||
{
|
||||
"q" => "1.0",
|
||||
"level" => "2",
|
||||
"foo" => "bAr",
|
||||
},
|
||||
},
|
||||
{
|
||||
"application/json;q=1.0",
|
||||
{
|
||||
"q" => "1.0",
|
||||
},
|
||||
},
|
||||
{
|
||||
"application/json;foo",
|
||||
{} of String => String,
|
||||
},
|
||||
}
|
||||
end
|
||||
end
|
101
spec/negotiator_spec.cr
Normal file
101
spec/negotiator_spec.cr
Normal file
|
@ -0,0 +1,101 @@
|
|||
require "./spec_helper"
|
||||
|
||||
struct NegotiatorTest < ASPEC::TestCase
|
||||
@negotiator : ANG::Negotiator
|
||||
|
||||
def initialize
|
||||
@negotiator = ANG::Negotiator.new
|
||||
end
|
||||
|
||||
def test_exception_handling : Nil
|
||||
ex = expect_raises ANG::Exceptions::InvalidMediaType, "Invalid media type: '/qwer'." do
|
||||
@negotiator.best "foo/bar", {"/qwer"}
|
||||
end
|
||||
|
||||
ex.type.should eq "/qwer"
|
||||
|
||||
expect_raises ArgumentError, "priorities should not be empty." do
|
||||
@negotiator.best "foo/bar", [] of String
|
||||
end
|
||||
|
||||
expect_raises ArgumentError, "The header string should not be empty." do
|
||||
@negotiator.best "", {"text/html"}
|
||||
end
|
||||
end
|
||||
|
||||
@[DataProvider("best_data_provider")]
|
||||
def test_best(header : String, priorities : Indexable(String), expected : Tuple(String, Hash(String, String) | Nil) | Nil) : Nil
|
||||
begin
|
||||
accept_header = @negotiator.best header, priorities
|
||||
rescue ex
|
||||
ex.should eq expected
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
if accept_header.nil?
|
||||
expected.should be_nil
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
accept_header.should be_a ANG::Accept
|
||||
|
||||
expected = expected.should_not be_nil
|
||||
|
||||
accept_header.type.should eq expected[0]
|
||||
accept_header.parameters.should eq (expected[1] || Hash(String, String).new)
|
||||
end
|
||||
|
||||
def best_data_provider : Tuple
|
||||
rfc_header = "text/*;q=0.3, text/html;q=0.7, text/html;level=1, text/html;level=2;q=0.4, */*;q=0.5"
|
||||
php_pear_header = "text/html,application/xhtml+xml,application/xml;q=0.9,text/*;q=0.7,*/*,image/gif; q=0.8, image/jpeg; q=0.6, image/*"
|
||||
|
||||
{
|
||||
{"/qwer", {"f/g"}, nil},
|
||||
{"text/html", {"application/rss"}, nil},
|
||||
{rfc_header, {"text/html;q=0.4", "text/plain"}, {"text/plain", nil}},
|
||||
|
||||
# See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html.
|
||||
{rfc_header, {"text/html;level=1"}, {"text/html", {"level" => "1"}}},
|
||||
{rfc_header, {"text/html"}, {"text/html", nil}},
|
||||
{rfc_header, {"image/jpeg"}, {"image/jpeg", nil}},
|
||||
{rfc_header, {"text/html;level=2"}, {"text/html", {"level" => "2"}}},
|
||||
{rfc_header, {"text/html;level=3"}, {"text/html", {"level" => "3"}}},
|
||||
|
||||
{"text/*;q=0.7, text/html;q=0.3, */*;q=0.5, image/png;q=0.4", {"text/html", "image/png"}, {"image/png", nil}},
|
||||
{"image/png;q=0.1, text/plain, audio/ogg;q=0.9", {"image/png", "text/plain", "audio/ogg"}, {"text/plain", nil}},
|
||||
{"image/png, text/plain, audio/ogg", {"baz/asdf"}, nil},
|
||||
{"image/png, text/plain, audio/ogg", {"audio/ogg"}, {"audio/ogg", nil}},
|
||||
{"image/png, text/plain, audio/ogg", {"YO/SuP"}, nil},
|
||||
{"text/html; charset=UTF-8, application/pdf", {"text/html; charset=UTF-8"}, {"text/html", {"charset" => "UTF-8"}}},
|
||||
{"text/html; charset=UTF-8, application/pdf", {"text/html"}, nil},
|
||||
{"text/html, application/pdf", {"text/html; charset=UTF-8"}, {"text/html", {"charset" => "UTF-8"}}},
|
||||
|
||||
# PHP"s PEAR HTTP2 assertions I took from the other lib.
|
||||
{php_pear_header, {"image/gif", "image/png", "application/xhtml+xml", "application/xml", "text/html", "image/jpeg", "text/plain"}, {"image/png", nil}},
|
||||
{php_pear_header, {"image/gif", "application/xhtml+xml", "application/xml", "image/jpeg", "text/plain"}, {"application/xhtml+xml", nil}},
|
||||
{php_pear_header, {"image/gif", "application/xml", "image/jpeg", "text/plain"}, {"application/xml", nil}},
|
||||
{php_pear_header, {"image/gif", "image/jpeg", "text/plain"}, {"image/gif", nil}},
|
||||
{php_pear_header, {"text/plain", "image/png", "image/jpeg"}, {"image/png", nil}},
|
||||
{php_pear_header, {"image/jpeg", "image/gif"}, {"image/gif", nil}},
|
||||
{php_pear_header, {"image/png"}, {"image/png", nil}},
|
||||
{php_pear_header, {"audio/midi"}, {"audio/midi", nil}},
|
||||
{"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", {"application/rss+xml"}, {"application/rss+xml", nil}},
|
||||
|
||||
# Case sensitiviy
|
||||
{"text/* ; q=0.3, TEXT/html ;Q=0.7, text/html ; level=1, texT/Html ;leVel = 2 ;q=0.4, */* ; q=0.5", {"text/html; level=2"}, {"text/html", {"level" => "2"}}},
|
||||
{"text/* ; q=0.3, text/html;Q=0.7, text/html ;level=1, text/html; level=2;q=0.4, */*;q=0.5", {"text/HTML; level=3"}, {"text/html", {"level" => "3"}}},
|
||||
|
||||
# IE8
|
||||
{"image/jpeg, application/x-ms-application, image/gif, application/xaml+xml, image/pjpeg, application/x-ms-xbap, */*", {"text/html", "application/xhtml+xml"}, {"text/html", nil}},
|
||||
|
||||
# wildcards with '+'
|
||||
{"application/vnd.api+json", {"application/json", "application/*+json"}, {"application/*+json", nil}},
|
||||
{"application/json;q=0.7, application/*+json;q=0.7", {"application/hal+json", "application/problem+json"}, {"application/hal+json", nil}},
|
||||
{"application/json;q=0.7, application/problem+*;q=0.7", {"application/hal+xml", "application/problem+xml"}, {"application/problem+xml", nil}},
|
||||
{php_pear_header, {"application/*+xml"}, {"application/*+xml", nil}},
|
||||
{"application/hal+json", {"application/ld+json", "application/hal+json", "application/xml", "text/xml", "application/json", "text/html"}, {"application/hal+json", nil}},
|
||||
}
|
||||
end
|
||||
end
|
|
@ -1,2 +1,7 @@
|
|||
require "spec"
|
||||
require "athena-spec"
|
||||
require "../src/athena-negotiation"
|
||||
|
||||
include ASPEC::Methods
|
||||
|
||||
ASPEC.run_all
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
abstract class Athena::Negotiation::AbstractNegotiator
|
||||
private abstract def create_header(header : String) : ANG::BaseAccept
|
||||
|
||||
def best(header : String, priorities : Array(String), strict : Bool = false) : ANG::BaseAccept?
|
||||
raise ArgumentError.new "priorities should not be empty" if priorities.empty?
|
||||
raise ArgumentError.new "The header string should not be empty" if header.blank?
|
||||
def best(header : String, priorities : Indexable(String), strict : Bool = false) : ANG::BaseAccept?
|
||||
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
|
||||
|
||||
|
@ -17,7 +17,26 @@ abstract class Athena::Negotiation::AbstractNegotiator
|
|||
|
||||
matches = self.find_matches accepted_headers, accepted_priorties
|
||||
|
||||
pp matches
|
||||
specific_matches = matches.reduce({} of Int32 => ANG::AcceptMatch) do |matches, match|
|
||||
ANG::AcceptMatch.reduce matches, match
|
||||
end.values
|
||||
|
||||
specific_matches.sort!
|
||||
|
||||
match = specific_matches.shift?
|
||||
|
||||
match.nil? ? nil : accepted_priorties[match.index]
|
||||
end
|
||||
|
||||
protected def match(header : ANG::BaseAccept, priority : ANG::BaseAccept, index : Int32) : ANG::AcceptMatch?
|
||||
accept_type = header.type
|
||||
priority_type = priority.type
|
||||
|
||||
equal = accept_type.downcase == priority_type.downcase
|
||||
|
||||
if equal || accept_type == "*"
|
||||
return ANG::AcceptMatch.new header.quality * priority.quality, 1 * (equal ? 1 : 0), index
|
||||
end
|
||||
|
||||
nil
|
||||
end
|
||||
|
@ -28,7 +47,7 @@ abstract class Athena::Negotiation::AbstractNegotiator
|
|||
end
|
||||
end
|
||||
|
||||
private def find_matches(headers : Array(ANG::BaseAccept), priorities : Array(ANG::BaseAccept)) : Array(ANG::AcceptMatch)
|
||||
private def find_matches(headers : Array(ANG::BaseAccept), priorities : Indexable(ANG::BaseAccept)) : Array(ANG::AcceptMatch)
|
||||
matches = [] of ANG::AcceptMatch
|
||||
|
||||
priorities.each_with_index do |priority, idx|
|
||||
|
@ -41,17 +60,4 @@ abstract class Athena::Negotiation::AbstractNegotiator
|
|||
|
||||
matches
|
||||
end
|
||||
|
||||
private def match(header : ANG::BaseAccept, priority : ANG::BaseAccept, index : Int32) : ANG::AcceptMatch?
|
||||
accept_type = header.type
|
||||
priority_type = priority.type
|
||||
|
||||
equal = accept_type.downcase <=> priority_type.downcase
|
||||
|
||||
if !equal.zero? || accept_type == "*"
|
||||
return ANG::AcceptMatch.new header.quality * priority.quality, 1 * equal, index
|
||||
end
|
||||
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
|
|
@ -12,7 +12,7 @@ struct Athena::Negotiation::Accept < Athena::Negotiation::BaseAccept
|
|||
parts = @type.split '/'
|
||||
|
||||
# TODO: Use more specific exception
|
||||
raise "Invalid media type: '#{@type}'." if parts.size != 2 || !parts[0].presence || !parts[1].presence
|
||||
raise ANG::Exceptions::InvalidMediaType.new @type, "Invalid media type: '#{@type}'." if parts.size != 2 || !parts[0].presence || !parts[1].presence
|
||||
|
||||
@base_part = parts[0]
|
||||
@sub_part = parts[1]
|
||||
|
|
|
@ -10,8 +10,6 @@ struct Athena::Negotiation::AcceptLanguage < Athena::Negotiation::BaseAccept
|
|||
|
||||
parts = @value.split '-'
|
||||
|
||||
pp parts
|
||||
|
||||
case parts.size
|
||||
when 2
|
||||
@language = parts[0]
|
||||
|
|
|
@ -1,7 +1,29 @@
|
|||
struct Athena::Negotiation::AcceptMatch
|
||||
include Comparable(self)
|
||||
|
||||
getter quality : Float32
|
||||
getter score : Int32
|
||||
getter index : Int32
|
||||
|
||||
def self.reduce(matches : Hash(Int32, self), match : self) : Hash(Int32, self)
|
||||
if !matches.has_key?(match.index) || matches[match.index].score < match.score
|
||||
matches[match.index] = match
|
||||
end
|
||||
|
||||
matches
|
||||
end
|
||||
|
||||
def initialize(@quality : Float32, @score : Int32, @index : Int32); end
|
||||
|
||||
def <=>(other : self) : Int32
|
||||
if @quality != other.quality
|
||||
return @quality > other.quality ? -1 : 1
|
||||
end
|
||||
|
||||
if @index != other.index
|
||||
return @index > other.index ? 1 : -1
|
||||
end
|
||||
|
||||
0
|
||||
end
|
||||
end
|
||||
|
|
|
@ -5,22 +5,14 @@ require "./accept_encoding"
|
|||
require "./accept_language"
|
||||
require "./negotiator"
|
||||
|
||||
require "./exceptions/*"
|
||||
|
||||
# Convenience alias to make referencing `Athena::Negotiation` types easier.
|
||||
alias ANG = Athena::Negotiation
|
||||
|
||||
module Athena::Negotiation
|
||||
end
|
||||
|
||||
# pp ANG::Accept.new "application/json;q=1.0"
|
||||
# pp ANG::Accept.new "application/json ;q=1.0; level=2;foo= bar"
|
||||
# pp ANG::Accept.new "text/html ; level = 2 ; q = 0.4"
|
||||
# n = ANG::Negotiator.new
|
||||
|
||||
# puts
|
||||
# puts
|
||||
|
||||
# pp ANG::AcceptLanguage.new "en-gb;q=0.8"
|
||||
|
||||
n = ANG::Negotiator.new
|
||||
|
||||
pp n.best "text/html;level=1", ["text/html"] # text/html
|
||||
pp n.best "text/*;q=0.7, text/html;q=0.3, */*;q=0.5, image/png;q=0.4", ["text/html", "image/png"] # image/png
|
||||
# pp n.best "text/html; charset=UTF-8, application/pdf", ["text/html"]
|
||||
|
|
|
@ -2,7 +2,7 @@ abstract struct Athena::Negotiation::BaseAccept
|
|||
getter quality : Float32 = 1.0
|
||||
getter normalized_value : String
|
||||
getter value : String
|
||||
getter parameters : Hash(String, String)
|
||||
getter parameters : Hash(String, String) = Hash(String, String).new
|
||||
getter type : String
|
||||
|
||||
def initialize(@value : String)
|
||||
|
@ -10,13 +10,13 @@ abstract struct Athena::Negotiation::BaseAccept
|
|||
parts = @value.split ';'
|
||||
@type = parts.shift.strip.downcase
|
||||
|
||||
@parameters = parts.to_h do |part|
|
||||
parts.each do |part|
|
||||
part = part.split '='
|
||||
|
||||
# TODO: Use more specific exception
|
||||
raise ArgumentError.new "Invalid header: '#{@value}'." unless part.size == 2
|
||||
# Skip invalid parameters
|
||||
next unless part.size == 2
|
||||
|
||||
{part[0].strip.downcase, part[1].strip(" \"")}
|
||||
@parameters[part[0].strip.downcase] = part[1].strip(" \"")
|
||||
end
|
||||
|
||||
if quality = @parameters.delete "q"
|
||||
|
@ -28,6 +28,7 @@ abstract struct Athena::Negotiation::BaseAccept
|
|||
|
||||
unless @parameters.empty?
|
||||
io << "; "
|
||||
# TODO: Do we care the parameters aren't sorted?
|
||||
parameters.join(io, "; ") { |(k, v), io| io << "#{k}=#{v}" }
|
||||
end
|
||||
end
|
||||
|
|
9
src/exceptions/invalid_media_type.cr
Normal file
9
src/exceptions/invalid_media_type.cr
Normal file
|
@ -0,0 +1,9 @@
|
|||
require "./negotiation_exception"
|
||||
|
||||
class Athena::Negotiation::Exceptions::InvalidMediaType < Athena::Negotiation::Exceptions::Exception
|
||||
getter type : String
|
||||
|
||||
def initialize(@type : String, message : String? = nil, cause : Exception? = nil)
|
||||
super message, cause
|
||||
end
|
||||
end
|
2
src/exceptions/negotiation_exception.cr
Normal file
2
src/exceptions/negotiation_exception.cr
Normal file
|
@ -0,0 +1,2 @@
|
|||
abstract class Athena::Negotiation::Exceptions::Exception < ::Exception
|
||||
end
|
|
@ -1,6 +1,69 @@
|
|||
require "./abstract_negotiator"
|
||||
|
||||
class Athena::Negotiation::Negotiator < Athena::Negotiation::AbstractNegotiator
|
||||
protected def match(accept : ANG::BaseAccept, priority : ANG::BaseAccept, index : Int32) : ANG::AcceptMatch?
|
||||
return nil if !accept.is_a?(ANG::Accept) || !priority.is_a?(ANG::Accept)
|
||||
|
||||
accept_base = accept.base_part
|
||||
priority_base = priority.base_part
|
||||
|
||||
accept_sub = accept.sub_part
|
||||
priority_sub = priority.sub_part
|
||||
|
||||
intercection = accept.parameters.each_with_object({} of String => String) do |(k, v), params|
|
||||
priority.parameters.tap do |pp|
|
||||
params[k] = v if pp.has_key?(k) && pp[k] == v
|
||||
end
|
||||
end
|
||||
|
||||
base_equal = accept_base.downcase == priority_base.downcase
|
||||
sub_equal = accept_sub.downcase == priority_sub.downcase
|
||||
|
||||
if (
|
||||
(accept_base == "*" || base_equal) &&
|
||||
(accept_sub == "*" || sub_equal) &&
|
||||
intercection.size == accept.parameters.size
|
||||
)
|
||||
score = 100 * (base_equal ? 1 : 0) + 10 * (sub_equal ? 1 : 0) + intercection.size
|
||||
|
||||
return ANG::AcceptMatch.new accept.quality * priority.quality, score, index
|
||||
end
|
||||
|
||||
return nil if !accept_sub.includes?('+') || !priority_sub.includes?('+')
|
||||
|
||||
accept_sub, accept_plus = self.split_sub_part accept_sub
|
||||
priority_sub, priority_plus = self.split_sub_part priority_sub
|
||||
|
||||
if (
|
||||
!(accept_base == "*" || base_equal) ||
|
||||
!(accept_sub == "*" || priority_sub == "*" || accept_plus == "*" || priority_plus == "*")
|
||||
)
|
||||
return nil
|
||||
end
|
||||
|
||||
sub_equal = accept_sub.downcase == priority_sub.downcase
|
||||
plus_equal = accept_plus.downcase == priority_plus.downcase
|
||||
|
||||
if (
|
||||
(accept_sub == "*" || priority_sub == "*" || sub_equal) &&
|
||||
(accept_plus == "*" || priority_plus == '*' || plus_equal) &&
|
||||
intercection.size == accept.parameters.size
|
||||
)
|
||||
# TODO: Calculate intercection between each header's parameters
|
||||
|
||||
score = 100 * (base_equal ? 1 : 0) + 10 * (sub_equal ? 1 : 0) + (plus_equal ? 1 : 0) + intercection.size
|
||||
return ANG::AcceptMatch.new accept.quality * priority.quality, score, index
|
||||
end
|
||||
|
||||
nil
|
||||
end
|
||||
|
||||
private def split_sub_part(sub_part : String) : Array(String)
|
||||
return [sub_part, ""] unless sub_part.includes? '+'
|
||||
|
||||
sub_part.split '+', limit: 2
|
||||
end
|
||||
|
||||
private def create_header(header : String) : ANG::BaseAccept
|
||||
ANG::Accept.new header
|
||||
end
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue