Class: YANAPI::Query

Inherits:
Object
  • Object
show all
Defined in:
lib/yanapi/query.rb

Direct Known Subclasses

CategoryQuery, QuestionQuery, TermQuery, UserQuery

Constant Summary collapse

HOST =
'http://answers.yahooapis.com'
SERVICE =
'AnswersService'
SERVICE_VERSION =
'V1'
VALID_PARAMS =
[:appid, :output, :callback]
MULTIVALUED_PARAMS =
[:region, :category_id, :category_name]
REQUIRED_PARAMS =
[:appid]
VALID_OUTPUT_FORMATS =
[nil, 'xml', 'php', 'rss', 'json']

Instance Method Summary collapse

Constructor Details

#initialize(params) ⇒ Query

It accepts a two dimensional hash:

{:method => 'questionSearch', :query_params =>
  {:appid => 'YahooDemo', :query => 'Haus'}}


24
25
26
27
28
29
# File 'lib/yanapi/query.rb', line 24

def initialize(params)
  @method = params[:method]
  @params = check_params(params[:query_params])
  @url = build_url(@params)
  @output = @params[:output] || 'xml'            
end

Instance Method Details

#build_url(params) ⇒ Object (protected)

It returns an URI::HTTP object containing the complete url for the request. Use <CGI>, not <URI>, the latter is somehow buggy.



95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
# File 'lib/yanapi/query.rb', line 95

def build_url(params)
  expanded_params = expand_params(params) # It is an array now! 
  escaped_params = expanded_params.map do |k, v|
    k = CGI.escape(k.to_s)
    v = CGI.escape(v.to_s)
    
    "#{k}=#{v}"
  end
  
  query = escaped_params.join('&')
  base_url = [HOST, SERVICE, SERVICE_VERSION, @method].join('/')
  url = base_url + '?' + query
  
  URI.parse(url)
end

#check_params(params) ⇒ Object (protected)

It checks params and returns a validated params hash.



54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
# File 'lib/yanapi/query.rb', line 54

def check_params(params)
  # It requires <:appid> to be present.
  unless params.has_key?(:appid)
    fail UserError, 'APPID is missing!'
  end

  # It accepts only valid output formats.
  unless VALID_OUTPUT_FORMATS.include?(params[:output])
    fail UserError, "The output type <#{params[:output]}> is not supported!"
  end

  # It rejects <:callback> for all output formats but <json>.
  if params[:output] != 'json' && params[:callback]
    fail UserError,
    "Output type #{params[:output]} is incompatible with <:callback>!"
  end

  # It accepts only unique values for every parameter other than:
  # * :category_id;
  # * :category_name;
  # * :region.
  # They can be strings, arrays or numbers:
  #   :category_id => [123, 456, 789].
  params.each_pair do |k, v|
    unless (v.kind_of?(String) || v.kind_of?(Fixnum) || v.kind_of?(Array))
      fail(UserError,
           "The value type <#{v.class}> for the key <:#{k}> is wrong!")
    end
    
    if v.kind_of?(Array)
      unless MULTIVALUED_PARAMS.include?(k)
        fail UserError, "The value of <:#{k}> must, but is not unique!"
      end
    end
  end

  params
end

#expand_params(params) ⇒ Object (protected)

It expands multiple values to key-val pairs and returns a params array.



112
113
114
115
116
117
118
119
120
121
122
123
124
# File 'lib/yanapi/query.rb', line 112

def expand_params(params)
  expanded_params = []
  
  params.each_pair do |k, v|
    if v.instance_of?(Array)
      v.each { |val| expanded_params << [k, val] }
    else
      expanded_params << [k, v]
    end
  end
  
  expanded_params.sort_by { |k, v| [k.to_s, v.to_s] }
end

#getObject

This is the main method. It gets the body of the HTTP response and invokes the respective check. It returns the response or <nil> if the response is emtpy.



34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# File 'lib/yanapi/query.rb', line 34

def get

  http_response = get_response(@url)
  
  case @output
  when 'xml'
    prove_xml(http_response)
  when 'json'
    fail NotImplementedError, 'We do not handle JSON yet!'
  when 'php'
    fail NotImplementedError, 'We do not handle PHP yet!'
  when 'rss'
    fail NotImplementedError, 'We do not handle RSS yet!'
  end

end

#get_response(url) ⇒ Object (protected)

It sends a HTTP request, rescues any external errors issuing an YANAPI::ExternalError, checks the HTTP code and fails if it is not 200. Otherwise it returns the body of the HTTP response.



129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
# File 'lib/yanapi/query.rb', line 129

def get_response(url)
  begin
    http_response = Net::HTTP.get_response(url)
  rescue Net::HTTPError, SocketError, Timeout::Error, IOError => e
    fail ExternalError, e
  end
  
  case http_response
  when Net::HTTPSuccess
    http_response.body
  when Net::HTTPBadRequest,
       Net::HTTPForbidden,
       Net::HTTPServiceUnavailable
    # These errors are documented by Yahoo!.
    fail(ExternalError, http_response.header) 
  else
    fail(ExternalError, "Unexpected HTTP response: #{http_response.header}.")
  end
  
end

#prove_xml(xml_as_str) ⇒ Object (protected)

It parses the input, checks for xml validity and proves if it is empty. It returns then <xml> as string or <nil> if the answer is emtpy.



152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
# File 'lib/yanapi/query.rb', line 152

def prove_xml(xml_as_str)
  begin
    # Set the value to STRICT (0), otherwise no errors will be raised!
    xml = Nokogiri::XML::Document.parse(xml_as_str, nil, nil, 0)
  rescue Nokogiri::XML::SyntaxError
    fail(ContentError, 'Erroneous XML response!')
  end
  
  error = xml.at_xpath('/xmlns:Error', xml.root.namespaces)
  if error
    message = 'The following errors were detected:'
    error.xpath('//xmlns:Message', xml.root.namespaces).each do |msg|
      message << "\n" + msg.content
    end
    fail(ContentError, message)
  end

  question = xml.at_xpath('//xmlns:Question', xml.root.namespaces)

  question ? xml_as_str : nil
end