Scaffold out types and start of implementation

This commit is contained in:
George Dietrich 2020-12-22 00:39:12 -05:00
parent 34aeeb58c0
commit 8d6d8cbf79
9 changed files with 184 additions and 1 deletions

View file

@ -0,0 +1,57 @@
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?
accepted_headers = Array(ANG::BaseAccept).new
self.parse_header(header) do |h|
accepted_headers << self.create_header h
rescue ex
raise ex if strict
end
accepted_priorties = priorities.map &->create_header(String)
matches = self.find_matches accepted_headers, accepted_priorties
pp matches
nil
end
private def parse_header(header : String, & : String ->) : Nil
header.scan /(?:[^,\"]*+(?:"[^"]*+\")?)+[^,\"]*+/ do |match|
yield match[0] unless match[0].blank?
end
end
private def find_matches(headers : Array(ANG::BaseAccept), priorities : Array(ANG::BaseAccept)) : Array(ANG::AcceptMatch)
matches = [] of ANG::AcceptMatch
priorities.each_with_index do |priority, idx|
headers.each do |header|
if match = self.match(header, priority, idx)
matches << match
end
end
end
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

20
src/accept.cr Normal file
View file

@ -0,0 +1,20 @@
require "./base_accept"
struct Athena::Negotiation::Accept < Athena::Negotiation::BaseAccept
getter base_part : String
getter sub_part : String
def initialize(value : String)
super value
@type = "*/*" if @type == "*"
parts = @type.split '/'
# TODO: Use more specific exception
raise "Invalid media type: '#{@type}'." if parts.size != 2 || !parts[0].presence || !parts[1].presence
@base_part = parts[0]
@sub_part = parts[1]
end
end

4
src/accept_charset.cr Normal file
View file

@ -0,0 +1,4 @@
require "./base_accept"
struct Athena::Negotiation::AcceptCharset < Athena::Negotiation::BaseAccept
end

4
src/accept_encoding.cr Normal file
View file

@ -0,0 +1,4 @@
require "./base_accept"
struct Athena::Negotiation::AcceptEncoding < Athena::Negotiation::BaseAccept
end

29
src/accept_language.cr Normal file
View file

@ -0,0 +1,29 @@
require "./base_accept"
struct Athena::Negotiation::AcceptLanguage < Athena::Negotiation::BaseAccept
getter language : String
getter script : String? = nil
getter region : String? = nil
def initialize(value : String)
super value
parts = @value.split '-'
pp parts
case parts.size
when 2
@language = parts[0]
@region = parts[1]
when 1
@language = parts[0]
when 3
@language = parts[0]
@script = parts[1]
@region = parts[2]
else
raise "Invalid language: '#{@value}'."
end
end
end

7
src/accept_match.cr Normal file
View file

@ -0,0 +1,7 @@
struct Athena::Negotiation::AcceptMatch
getter quality : Float32
getter score : Int32
getter index : Int32
def initialize(@quality : Float32, @score : Int32, @index : Int32); end
end

View file

@ -1,6 +1,26 @@
require "./accept"
require "./accept_match"
require "./accept_charset"
require "./accept_encoding"
require "./accept_language"
require "./negotiator"
# Convenience alias to make referencing `Athena::Negotiation` types easier.
alias ANG = Athena::Negotiation
module Athena::Negotiation
VERSION = "0.1.0"
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"
# 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

35
src/base_accept.cr Normal file
View file

@ -0,0 +1,35 @@
abstract struct Athena::Negotiation::BaseAccept
getter quality : Float32 = 1.0
getter normalized_value : String
getter value : String
getter parameters : Hash(String, String)
getter type : String
def initialize(@value : String)
# type, parameters = self.parse_accept_value value
parts = @value.split ';'
@type = parts.shift.strip.downcase
@parameters = parts.to_h do |part|
part = part.split '='
# TODO: Use more specific exception
raise ArgumentError.new "Invalid header: '#{@value}'." unless part.size == 2
{part[0].strip.downcase, part[1].strip(" \"")}
end
if quality = @parameters.delete "q"
@quality = quality.to_f32
end
@normalized_value = String.build do |io|
io << @type
unless @parameters.empty?
io << "; "
parameters.join(io, "; ") { |(k, v), io| io << "#{k}=#{v}" }
end
end
end
end

7
src/negotiator.cr Normal file
View file

@ -0,0 +1,7 @@
require "./abstract_negotiator"
class Athena::Negotiation::Negotiator < Athena::Negotiation::AbstractNegotiator
private def create_header(header : String) : ANG::BaseAccept
ANG::Accept.new header
end
end