app/models/setup/api_spec.rb
require 'openapi3_parser'
module Setup
class ApiSpec
include SharedEditable
build_in_data_type.referenced_by(:name)
field :title, type: String
field :url, type: String
field :specification, type: String
validate :validate_specification
validates_presence_of :specification
before_save :parse_title
def spec_doc
@spec_doc ||= Openapi3Parser.load(specification)
@spec_doc
end
def parse_title
self.title = spec_doc.info.title if title.blank?
end
def validate_specification
specification = specification.to_s.strip
if url.present? && specification.blank?
begin
self.specification = Setup::Connection.get(url).submit!
rescue Exception => ex
errors.add(:base, "Unable to retrieve specification from #{specification}: #{ex.message}")
end
end
return if specification.blank?
spec = self.spec_doc
if spec.valid?
self.specification = spec.root_source.data.to_yaml
else
errors.add(:specification, "in not valid, #{spec.errors.map(&:message).uniq.join(', ')}.")
end
end
def cenit_collection_hash(options = {})
spec = self.spec_doc
c_title = self.title || spec.info.title
name = slugify(c_title)
namespace = name.camelize
current_user = ::User.current
collection = {
name: name,
shared_version: '0.1',
readme: I18n.t('cenit.api_spec.swagger_parser.readme'),
title: c_title,
summary: spec.info.description || c_title,
authors: [{ name: current_user.name, email: current_user.email }],
category: 'API Collection',
namespaces: [{ _primary: ['name'], slug: name, name: namespace }],
connections: [],
webhooks: [],
oauth_providers: [],
oauth_clients: [],
oauth2_scopes: [],
authorizations: [],
}
parse_authorizations(namespace, collection)
parse_connections(namespace, collection)
parse_webhooks(namespace, collection)
collection
end
def pull_asynchronous
true
end
protected
def parse_authorizations(namespace, collection)
spec = self.spec_doc
find_security_scheme = ->(type) { spec.components.security_schemes.detect { |_, v| v.type == type.to_s }.last }
if (security_scheme = find_security_scheme.call(:oauth2))
parse_oaut2_providers(namespace, collection, security_scheme)
parse_oaut2_clients(namespace, collection, security_scheme)
parse_oaut2_scopes(namespace, collection, security_scheme)
parse_oaut2_authorization(namespace, collection, security_scheme)
elsif (security_scheme = find_security_scheme.call(:http))
parse_basic_authorization(namespace, collection, security_scheme)
end
end
def parse_basic_authorization(namespace, collection, security_scheme)
collection[:authorizations] << {
_primary: %w[namespace name],
namespace: namespace,
name: 'default_authorization',
_type: 'Setup::BasicAuthorization'
}
end
def parse_oaut2_authorization(namespace, collection, security_scheme)
options = security_scheme.flows.authorization_code
client = collection[:oauth_clients].first
provider = client[:provider]
collection[:authorizations] << {
_primary: %w[namespace name],
namespace: namespace,
name: 'default_authorization',
_type: 'Setup::Oauth2Authorization',
token_type: 'bot',
client: { _reference: true, _type: client[:_type], name: client[:name], provider: provider },
parameters: [],
template_parameters: [{ _primary: %w[key], key: "scopes", value: options.scopes.values.join(',') }],
scopes: [{ _reference: true, name: '{{scopes}}', provider: provider }]
}
end
def parse_oaut2_providers(namespace, collection, security_scheme)
options = security_scheme.flows.authorization_code
collection[:oauth_providers] << {
_primary: %w[namespace name],
_type: 'Setup::Oauth2Provider',
origin: 'default',
namespace: namespace,
name: 'default_provider',
authorization_endpoint: options.authorization_url,
response_type: 'code',
token_endpoint: options.token_url,
token_method: 'POST',
refresh_token_strategy: 'default',
scope_separator: ','
}
end
def parse_oaut2_clients(namespace, collection, security_scheme)
provider = collection[:oauth_providers].first
provider = { _reference: true, namespace: provider[:namespace], name: provider[:name] }
collection[:oauth_clients] << {
_primary: %w[_type provider name],
_type: 'Setup::RemoteOauthClient',
name: 'default_client',
provider: provider,
request_token_parameters: [],
request_token_headers: [],
template_parameters: []
}
end
def parse_oaut2_scopes(namespace, collection, security_scheme)
provider = collection[:oauth_providers].first
provider = { _reference: true, namespace: provider[:namespace], name: provider[:name] }
collection[:oauth2_scopes] << { _primary: %w[name provider], name: "{{scopes}}", provider: provider }
end
def parse_connections(namespace, collection)
spec = self.spec_doc
auth = collection[:authorizations].first
collection[:connections] << conn = {
_primary: %w[namespace name],
namespace: namespace,
name: 'default_connection',
url: spec.servers.first.try(:url) || "http://#{collection.name}.domain.com",
headers: [],
parameters: [],
}
unless auth.nil?
conn[:authorization] = { _reference: true, namespace: auth[:namespace], name: auth[:name] }
if (auth[:_type] == 'Setup::Oauth2Authorization')
conn[:authorization_handler] = true
conn[:headers] << { key: 'Authorization', value: 'Bearer {{access_token}}' }
end
end
spec.components.security_schemes.each do |_, scheme|
next unless scheme.type == 'apiKey'
tp_name = slugify(scheme.name)
item = { key: scheme.name, value: "{{#{tp_name}}}" }
if scheme.in == 'header'
conn[:headers] << item
elsif scheme.in == 'query'
conn[:parameters] << item
end
end
end
def parse_webhooks(namespace, collection)
spec = self.spec_doc
namespace = collection[:namespaces].first[:name]
spec.paths.each do |path, path_spec|
spec.paths[path].each do |method, service_spec|
next if service_spec.nil? || method !~ /^(get|post|put|patch|delete|options)$/
headers = parse_webhook_parameters(path_spec, 'header')
headers.concat(parse_webhook_parameters(service_spec, 'header'))
parameters = parse_webhook_parameters(path_spec, 'query')
parameters.concat(parse_webhook_parameters(service_spec, 'query'))
template_parameters = parse_webhook_template_parameters(path_spec)
template_parameters.concat(parse_webhook_template_parameters(service_spec))
collection[:webhooks] << {
namespace: namespace,
name: service_spec.operation_id || "#{method}_#{slugify(path)}",
method: method,
path: path.gsub(/\{([^\}]+)\}/, '{{\1}}'),
description: "#{service_spec.summary}\n\n#{service_spec.description}".strip,
headers: headers,
parameters: parameters,
template_parameters: template_parameters,
}
end
end
end
def parse_webhook_parameters(spec, filter = 'query')
spec.parameters.select { |p| p.in == filter }.map do |p|
{ key: p.name, value: "{{#{p.name}}}", description: p.description }
end
end
def parse_webhook_template_parameters(service_spec)
service_spec.parameters.map do |p|
{ key: p.name, value: p.schema.example ? JSON.generate(p.schema.example) : '', description: p.description }
end
end
def slugify(name)
name.underscore.parameterize.underscore
end
end
end