mirror of
https://gitea.invidious.io/iv-org/shard-athena-negotiation.git
synced 2024-08-15 00:53:23 +00:00
Scaffold out types and start of implementation
This commit is contained in:
parent
34aeeb58c0
commit
8d6d8cbf79
9 changed files with 184 additions and 1 deletions
57
src/abstract_negotiator.cr
Normal file
57
src/abstract_negotiator.cr
Normal 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
20
src/accept.cr
Normal 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
4
src/accept_charset.cr
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
require "./base_accept"
|
||||||
|
|
||||||
|
struct Athena::Negotiation::AcceptCharset < Athena::Negotiation::BaseAccept
|
||||||
|
end
|
4
src/accept_encoding.cr
Normal file
4
src/accept_encoding.cr
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
require "./base_accept"
|
||||||
|
|
||||||
|
struct Athena::Negotiation::AcceptEncoding < Athena::Negotiation::BaseAccept
|
||||||
|
end
|
29
src/accept_language.cr
Normal file
29
src/accept_language.cr
Normal 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
7
src/accept_match.cr
Normal 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
|
|
@ -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.
|
# Convenience alias to make referencing `Athena::Negotiation` types easier.
|
||||||
alias ANG = Athena::Negotiation
|
alias ANG = Athena::Negotiation
|
||||||
|
|
||||||
module Athena::Negotiation
|
module Athena::Negotiation
|
||||||
VERSION = "0.1.0"
|
|
||||||
end
|
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
35
src/base_accept.cr
Normal 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
7
src/negotiator.cr
Normal 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
|
Loading…
Add table
Add a link
Reference in a new issue