Skip to content

Commit

Permalink
Add specs and fix some bugs on MediaType. See webmachine#1.
Browse files Browse the repository at this point in the history
  • Loading branch information
seancribbs committed Sep 1, 2011
1 parent adac09b commit 622badf
Show file tree
Hide file tree
Showing 4 changed files with 99 additions and 14 deletions.
7 changes: 4 additions & 3 deletions lib/webmachine/decision/conneg.rb
Expand Up @@ -15,7 +15,7 @@ module Conneg
def choose_media_type(provided, header)
requested = MediaTypeList.build(header.split(/\s*,\s*/))
provided = provided.map do |p| # normalize_provided
MediaType.new(*Array(p))
MediaType.parse(p)
end
# choose_media_type1
chosen = nil
Expand Down Expand Up @@ -224,10 +224,11 @@ class MediaTypeList < PriorityList
# {MediaType} items instead of Strings.
# @see PriorityList#add_header_val
def add_header_val(c)
if mt = MediaType.parse(c)
begin
mt = MediaType.parse(c)
q = mt.params.delete('q') || 1.0
add(q.to_f, mt)
else
rescue ArgumentError
raise MalformedRequest, t('invalid_media_type', :type => c)
end
end
Expand Down
4 changes: 2 additions & 2 deletions lib/webmachine/locale/en.yml
Expand Up @@ -19,10 +19,10 @@ en:
message: "The server does not support the %{method} method."
"503":
title: 503 Service Unavailable
message: The server is currently unable to handl the request due to a temporary overloading or maintenance of the server.
message: The server is currently unable to handle the request due to a temporary overloading or maintenance of the server.
create_path_nil: "post_is_create? returned true but create_path is nil! Define the create_path method in %{class}"
do_redirect: "Response had do_redirect but no Location header."
fsm_broke: "Decision FSM returned an unexpected value %{result} from decision %{state}."
invalid_media_type: "Invalid media type specified in Accept header: %{type}"
invalid_media_type: "Invalid media type: %{type}"
not_resource_class: "%{class} is not a subclass of Webmachine::Resource"
process_post_invalid: "process_post returned %{result}"
36 changes: 27 additions & 9 deletions lib/webmachine/media_type.rb
@@ -1,28 +1,35 @@
require 'webmachine/translation'

module Webmachine
# Encapsulates a MIME media type, with logic for matching types.
class MediaType
extend Translation
# Matches valid media types
MEDIA_TYPE_REGEX = /^\s*([^;\s]+)\s*((?:;\S+\s*)*)\s*$/

# Matches sub-type parameters
PARAMS_REGEX = /;([^=]+)=([^;=\s]+)/

# Creates a new MediaType by parsing its string representation.
# Creates a new MediaType by parsing an alternate representation.
# @param [MediaType, String, Array<String,Hash>] obj the raw type
# to be parsed
# @return [MediaType] the parsed media type
# @raise [ArgumentError] when the type could not be parsed
def self.parse(obj)
case obj
when MediaType
obj
when Array
unless String == obj[0] && Hash === obj[1]
raise ArgumentError, t('invalid_media_type', :type => obj.inspect)
end
type = parse(obj)
type.params.merge! obj[1]
type
when MEDIA_TYPE_REGEX
type, raw_params = $1, $2
params = Hash[raw_params.scan(PARAMS_REGEX)]
new(type, params)
else
unless Array === obj && String === obj[0] && Hash === obj[1]
raise ArgumentError, t('invalid_media_type', :type => obj.inspect)
end
type = parse(obj[0])
type.params.merge!(obj[1])
type
end
end

Expand All @@ -32,6 +39,8 @@ def self.parse(obj)
# @return [Hash] any type parameters, e.g. charset
attr_accessor :params

# @param [String] type the main media type, e.g. application/json
# @param [Hash] params the media type parameters
def initialize(type, params={})
@type, @params = type, params
end
Expand All @@ -43,17 +52,22 @@ def matches_all?
end

def ==(other)
other = self.class.parse(other) if String === other
other = self.class.parse(other)
other.type == type && other.params == params
end

# Detects whether this {MediaType} matches the other {MediaType},
# taking into account wildcards.
# @param [MediaType, String, Array<String,Hash>] other the other
# type
# @return [true,false] whether it is an acceptable match
def match?(other)
other = self.class.parse(other)
type_matches?(other) && other.params == params
end

# Reconstitutes the type into a String
# @return [String] the type as a String
def to_s
[type, *params.map {|k,v| "#{k}=#{v}" }].join(";")
end
Expand All @@ -68,7 +82,11 @@ def minor
type.split("/").last
end

# @param [MediaType] other the other type
# @return [true,false] whether the main media type is acceptable,
# ignoring params and taking into account wildcards
def type_matches?(other)
other = self.class.parse(other)
if ["*", "*/*", type].include?(other.type)
true
else
Expand Down
66 changes: 66 additions & 0 deletions spec/webmachine/media_type_spec.rb
@@ -0,0 +1,66 @@
require 'spec_helper'

describe Webmachine::MediaType do
let(:raw_type){ "application/xml;charset=UTF-8" }
subject { described_class.new("application/xml", {"charset" => "UTF-8"}) }

context "equivalence" do
it { should == raw_type }
it { should == described_class.parse(raw_type) }
end

context "when it is the wildcard type" do
subject { described_class.new("*/*") }
it { should be_matches_all }
end

context "parsing a type" do
it "should return MediaTypes untouched" do
described_class.parse(subject).should equal(subject)
end

it "should parse a String" do
type = described_class.parse(raw_type)
type.should be_kind_of(described_class)
type.type.should == "application/xml"
type.params.should == {"charset" => "UTF-8"}
end

it "should parse a type/params pair" do
type = described_class.parse(["application/xml", {"charset" => "UTF-8"}])
type.should be_kind_of(described_class)
type.type.should == "application/xml"
type.params.should == {"charset" => "UTF-8"}
end

it "should parse a type/params pair where the type has some params in the string" do
type = described_class.parse(["application/xml;version=1", {"charset" => "UTF-8"}])
type.should be_kind_of(described_class)
type.type.should == "application/xml"
type.params.should == {"charset" => "UTF-8", "version" => "1"}
end

it "should raise an error when given an invalid type/params pair" do
expect {
described_class.parse([false, "blah"])
}.to raise_error(ArgumentError)
end
end

describe "matching a requested type" do
it { should be_match("application/xml;charset=UTF-8") }
it { should be_match("application/*;charset=UTF-8") }
it { should be_match("*/*;charset=UTF-8") }
it { should be_match("*;charset=UTF-8") }
it { should_not be_match("text/xml") }
it { should_not be_match("application/xml") }
it { should_not be_match("application/xml;version=1") }

it { should be_type_matches("application/xml") }
it { should be_type_matches("application/*") }
it { should be_type_matches("*/*") }
it { should be_type_matches("*") }
it { should_not be_type_matches("text/xml") }
it { should_not be_type_matches("text/*") }
end
end

0 comments on commit 622badf

Please sign in to comment.