lib/vigia/adapters/raml.rb
module Vigia
module Adapters
class Raml < Vigia::Adapter
attr_reader :raml
setup_adapter do
after_initialize do
@raml = ::Raml::parse_file(source_file)
end
group :resource,
primary: true,
children: [ :method ],
describes: -> { adapter.raml.resources.values },
description: -> { "Resource: #{ resource.name }" },
recursion: -> { resources.values }
group :method,
children: [ :response ],
describes: -> { resource.methods.values },
description: -> { "Method: #{ method.name }" }
group :response,
children: [ :body ],
describes: -> { method.responses.values },
description: -> { "Response: #{ response.name }" }
group :body,
contexts: [ :default ],
describes: -> { response.bodies.values },
description: -> { "Content type: #{ body.name }" }
context :default,
http_client_options: {
method: -> { method.name },
uri_template: -> { adapter.resource_uri_template(method) },
parameters: -> { adapter.parameters_for(method) },
headers: -> { adapter.request_headers(body) },
payload: -> { adapter.payload_for(method, body) }
},
expectations: {
code: -> { response.name.to_i },
headers: -> { adapter.expected_headers(body) },
body: -> { body.schema.value }
}
end
def resource_uri_template(method)
uri_template = method.parent.resource_path
uri_template += query_parameters(method)
end
def parameters_for(method)
format_parameters(method.query_parameters) + format_parameters(method.parent.uri_parameters)
end
def request_headers(body)
method = body.parent.parent
compile_headers(method.headers).tap do |headers|
return unless with_payload?(method.name)
return if request_body_for(method, body).name == '*/*'
headers.merge!(content_type: request_body_for(method, body).name)
end
end
def expected_headers(body)
compile_headers(body.parent.headers).tap do |headers|
# Dont add content_type header if response is 204 (nil response)
headers.merge!(content_type: body.media_type) unless body.parent.name == 204 or headers.key?(:content_type)
end
end
def payload_for(method, body)
return unless with_payload?(method.name)
payload = request_body_for(method, body)
case
when required_payload?(method.name) && payload.nil?
raise(
"An example body cannot be found for method #{ method.name } #{ method.parent.resource_path }"
)
when payload.nil?
nil
else
payload.example || payload.schema.value
end
end
private
def format_parameters(raml_hash)
raml_hash.values.each_with_object([]) do |parameter, array|
array << {
name: name_to_rfc_3986(parameter.name),
value: parameter.example,
required: !parameter.optional
}
end
end
def compile_headers(headers)
headers.each_with_object({}) do |(key, header), hash|
raise "Required header #{ key } does not have an example value" if header.example.nil? && !header.optional
hash.merge!(key.to_s.gsub('-', '_').downcase.to_sym => header.example)
end
end
def request_body_for(method, response_body)
response_body.name == '*/*' ?
method.bodies.values.first : method.bodies[response_body.name]
end
def query_parameters(method)
method.apply_traits if method.traits.any? # Does this belong here RAML?
return '' if method.query_parameters.empty?
"{?#{ query_string(method) }}"
end
# RAML specification for query parameter name must comply with RFC 3986,
# However, Vigia uses `addressable` for query parameters expansion, which
# enforces RFC 6570. These two specification understand in different ways
# what it is consider to be an unreserved character. While RFC 3986 allows
# this set: `ALPHA / DIGIT / "-" / "." / "_" / "~"`, RFC 6570 only accepts
# `ALPHA / DIGIT / "_", and encoded characters in pct-encoded format `%*`
# Thus, the transformation:
def name_to_rfc_3986(string)
return if string.nil?
string.gsub(/[-.~]/,
'-' => '%2D',
'~' => '%7E',
'.' => '%2E'
)
end
def query_string(method)
method.query_parameters.keys.map do |key|
name_to_rfc_3986(key.to_s)
end.join(',')
end
end
end
end