Spec::Matchers.create(:be_successful, :respond_successfully) do
  matches do |rack|
    @status = rack.respond_to?(:status) ? rack.status : rack
    @inspect = describe_input(rack)

    (200..207).include?(@status)
  end
  
  message do |not_string, rack|
    if @inspect.is_a?(Numeric)
      "Expected status code#{not_string} to be successful, " \
      "but it was #{@inspect}"
    else
      "Expected #{@inspect}#{not_string} " \
      "to be successful, but it returned a #{@status}"
    end
  end
end

Spec::Matchers.create(:be_missing, :be_client_error) do
  matches do |rack|
    @status = rack.respond_to?(:status) ? rack.status : rack
    @inspect = describe_input(rack)

    (400..417).include?(@status)
  end
  
  message do |not_string, rack|
    unless @inspect.is_a?(Numeric)
      "Expected #{@inspect}#{not_string} " \
      "to be missing, but it returned a #{@status}"
    else
      "Expected #{not_string ? "not to get " : ""}a missing error code, " \
      "but got #{@inspect}"
    end
  end
end

Spec::Matchers.create(:have_body) do
  matches do |rack, body|
    @actual = if rack.respond_to?(:body)
      rack.body.to_s
    else
      rack.to_s
    end
    
    @actual == body
  end
  
  negative_failure_message do |rack, body|
    "Expected the response not to match:\n    #{body}\nActual response was:\n    #{@actual}" 
  end
  
  failure_message do |rack, body|
    "Expected the response to match:\n    #{body}\nActual response was:\n    #{@actual}" 
  end
end

Spec::Matchers.create(:have_content_type) do
  matches do |rack, mime_symbol|
    content_type = rack.headers["Content-Type"].split("; ").first
    if registered_mime = Merb.available_mime_types[mime_symbol]
      registered_mime[:accepts].include?(content_type)
    else
      @error = "Mime #{mime_symbol.inspect} was not registered"
      false
    end
  end
  
  failure_message do |rack, mime_symbol|
    if @error
      @error
    else
      ret = "Expected your response to be of the #{mime_symbol} type, "
      if mime = Merb.available_accepts[rack.headers["Content-Type"]]
        ret << "but it was #{mime}"
      else
        ret << "but it was #{rack.headers["Content-Type"]}, which was " \
               "not a registered Merb content type."
      end
    end
  end
end

Spec::Matchers.create(:redirect) do
  matches do |rack|
    @inspect = describe_input(rack)
    @status_code = status_code(rack)
    (300..399).include?(@status_code)
  end
  
  message do |not_string, rack|
    "Expected #{@inspect}#{not_string} to be a redirect, but the " \
    "status code was #{@status_code}"
  end
end

Spec::Matchers.create(:redirect_to) do
  expected_value do |location|
    url(location) if location.is_a?(Symbol)
  end
  
  matches do |rack, location|
    @inspect = describe_input(rack)
    
    return false unless rack.headers["Location"]
    @location, @query = rack.headers["Location"].split("?")
    @status_code = status_code(rack)
    @status_code.in?(300..399) && @location == location
  end
  
  negative_failure_message do |rack, location|
    "Expected #{@inspect} not to redirect to " \
    "<#{location}> but it did."
  end
  
  failure_message do |rack, location|
    if !rack.status.in?(300..399)
      "Expected #{@inspect} to be a redirect, but " \
      "it returned status code #{rack.status}."
    elsif rack.headers["Location"] != location
      "Expected #{@inspect} to redirect to " \
      "<#{location}>, but it redirected to <#{rack.headers["Location"]}>"
    end
  end
end