Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Markdown (APIBlueprint) Support #535

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
18 changes: 18 additions & 0 deletions app/views/apipie/apipies/_action.md.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<%-
first_api = action[:apis].try(:first) || {}
-%>
## <%= action[:name] == "index" ? "List" : action[:name].capitalize %> [<%= [first_api[:http_method], first_api[:api_url]].join(' ') %>]
<%= first_api[:short_description] %>

+ Request (application/json)
+ Attributes
<%-
action[:params].each do |p| -%>
<%= render(:partial => "param", :locals => {:param => p, :action => action}) %>
<%-
end
-%>

<%- action[:responses].each do |r| -%>
<%= render(:partial => 'response', :locals => {:response => r, :action => action}) %>
<%- end -%>
Empty file.
5 changes: 5 additions & 0 deletions app/views/apipie/apipies/_param.md.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<%-
meta = [param[:expected_type], param[:required] ? 'required' : 'optional'].compact.join(', ')
allowed_params = param[:allowed] ? ':' + param[:allowed].join(', ') : nil
-%>
- <%= ["`#{param[:full_name]}`", allowed_params, "(#{meta})", param[:description]].compact.join(' ') %>
8 changes: 8 additions & 0 deletions app/views/apipie/apipies/_resource.md.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Group <%= resource[:name].titlecase %>
<%= raw resource[:short_description] %>
<%= raw resource[:full_description] unless resource[:full_description].blank? %>
<%= render(:partial => 'headers', :locals => { headers: resource[:headers], h_level: 2 }) %>
<%- resource[:methods].each do |a| -%>
<% next if !a[:show] %>
<%= render(:partial => "action", :locals => {:action => a, :resource => resource.except(:methods)}) -%>
<%- end -%>
11 changes: 11 additions & 0 deletions app/views/apipie/apipies/_response.md.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
+ Response <%= response[:code] %> (application/vnd.api+json)

<%=
if response[:body]
begin
JSON.pretty_generate(response[:body]).gsub(/^/, ' ')
rescue JSON::GeneratorError => e
response[:body].html_safe
end
end
-%>
11 changes: 11 additions & 0 deletions app/views/apipie/apipies/static.md.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
FORMAT: 1A
DOC_HOST: <%= @doc[:doc_url].html_safe %>
API_HOST: <%= @doc[:api_url].html_safe %>

# <%= @doc[:name].html_safe %>
<%= @doc[:info].html_safe %>
<%= @doc[:copyright].html_safe %>

<%- @doc[:resources].sort_by(&:first).each do |key, resource| -%>
<%= render(partial: "resource", locals: {resource: resource}) -%>
<%- end -%>
1 change: 1 addition & 0 deletions app/views/layouts/apipie/apipie.md.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<%= yield %>
12 changes: 10 additions & 2 deletions lib/apipie-rails.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@
require "apipie/routing"
require "apipie/markup"
require "apipie/apipie_module"
require "apipie/dsl_definition"
require "apipie/dsl/definition"
require "apipie/configuration"
require "apipie/method_description"
require "apipie/resource_description"
require "apipie/param_description"
require "apipie/errors"
require "apipie/error_description"
require "apipie/response_description"
require "apipie/see_description"
require "apipie/validator"
require "apipie/railtie"
Expand All @@ -21,3 +21,11 @@
if Rails.version.start_with?("3.0")
warn 'Warning: apipie-rails is not going to support Rails 3.0 anymore in future versions'
end

module Apipie

def self.root
@root ||= Pathname.new(File.dirname(File.expand_path(File.dirname(__FILE__), '/../')))
end

end
2 changes: 1 addition & 1 deletion lib/apipie/client/generator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ def substituted_url(method)
end

def transformation_hash(method)
method[:params].find_all { |p| p[:expected_type] == "hash" && !p[:params].nil? }.reduce({ }) do |h, p|
method[:params].find_all { |p| p[:expected_type] == "object" && !p[:params].nil? }.reduce({ }) do |h, p|
h.update(p[:name] => p[:params].map { |pp| pp[:name] })
end
end
Expand Down
77 changes: 77 additions & 0 deletions lib/apipie/dsl/action.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
module Apipie

# DSL is a module that provides #api, #error, #param, #error.
module DSL

module Action

def def_param_group(name, &block)
Apipie.add_param_group(self, name, &block)
end

#
# # load paths from routes and don't provide description
# api
#
def api(method, path, desc = nil, options={}) #:doc:
return unless Apipie.active_dsl?
_apipie_dsl_data[:api] = true
_apipie_dsl_data[:api_args] << [method, path, desc, options]
end

# # load paths from routes
# api! "short description",
#
def api!(desc = nil, options={}) #:doc:
return unless Apipie.active_dsl?
_apipie_dsl_data[:api] = true
_apipie_dsl_data[:api_from_routes] = { :desc => desc, :options =>options }
end

# Reference other similar method
#
# api :PUT, '/articles/:id'
# see "articles#create"
# def update; end
def see(*args)
return unless Apipie.active_dsl?
_apipie_dsl_data[:see] << args
end

# Show some example of what does the described
# method return.
def example(example) #:doc:
return unless Apipie.active_dsl?
_apipie_dsl_data[:examples] << example.strip_heredoc
end

# Determine if the method should be included
# in the documentation
def show(show)
return unless Apipie.active_dsl?
_apipie_dsl_data[:show] = show
end

# Describe whole resource
#
# Example:
# api :desc => "Show user profile", :path => "/users/", :version => '1.0 - 3.4.2012'
# param :id, Fixnum, :desc => "User ID", :required => true
# desc <<-EOS
# Long description...
# EOS
def resource_description(options = {}, &block) #:doc:
return unless Apipie.active_dsl?
raise ArgumentError, "Block expected" unless block_given?

dsl_data = Resource::Description.eval_dsl(self, &block)
versions = dsl_data[:api_versions]
@apipie_resource_descriptions = versions.map do |version|
Apipie.define_resource_description(self, version, dsl_data)
end
Apipie.set_controller_versions(self, versions)
end
end

end # module DSL
end # module Apipie
42 changes: 42 additions & 0 deletions lib/apipie/dsl/base.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Apipie DSL functions.
module Apipie

# DSL is a module that provides #api, #error, #param, #error.
module DSL

module Base
attr_reader :apipie_resource_descriptions, :api_params

private

def _apipie_dsl_data
@_apipie_dsl_data ||= _apipie_dsl_data_init
end

def _apipie_dsl_data_clear
@_apipie_dsl_data = nil
end

def _apipie_dsl_data_init
@_apipie_dsl_data = {
:api => false,
:api_args => [],
:api_from_routes => nil,
:responses => [],
:params => [],
:headers => [],
:resource_id => nil,
:short_description => nil,
:description => nil,
:examples => [],
:see => [],
:formats => nil,
:api_versions => [],
:meta => nil,
:show => true
}
end
end

end # module DSL
end # module Apipie
170 changes: 170 additions & 0 deletions lib/apipie/dsl/common.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
module Apipie

# DSL is a module that provides #api, #error, #param, #error.
module DSL

module Common
def api_versions(*versions)
_apipie_dsl_data[:api_versions].concat(versions)
end
alias :api_version :api_versions

# Describe the next method.
#
# Example:
# desc "print hello world"
# def hello_world
# puts "hello world"
# end
#
def desc(description) #:doc:
return unless Apipie.active_dsl?
if _apipie_dsl_data[:description]
raise "Double method description."
end
_apipie_dsl_data[:description] = description
end
alias :description :desc
alias :full_description :desc

# describe next method with document in given path
# in convension, these doc located under "#{Rails.root}/doc"
# Example:
# document "hello_world.md"
# def hello_world
# puts "hello world"
# end
def document path
content = File.open(File.join(Rails.root, Apipie.configuration.doc_path, path)).read
desc content
end

# Describe available request/response formats
#
# formats ['json', 'jsonp', 'xml']
def formats(formats) #:doc:
return unless Apipie.active_dsl?
_apipie_dsl_data[:formats] = formats
end

# Describe additional metadata
#
# meta :author => { :name => 'John', :surname => 'Doe' }
def meta(meta) #:doc:
_apipie_dsl_data[:meta] = meta
end


# Describe possible responses
#
# Example:
# response :desc => "speaker is sleeping", :code => 500, :meta => [:some, :more, :data]
# response 500, "speaker is sleeping"
# def hello_world
# return 500 if self.speaker.sleeping?
# puts "hello world"
# end
#
def response(code, example=nil, options={}) #:doc:
return unless Apipie.active_dsl?
_apipie_dsl_data[:responses] << [code, example, options]
end

def error(code, example=nil, options={})
response(code, example, options)
end

def success(code, example=nil, options={})
response(code, example, options)
end

def _apipie_define_validators(description)

# [re]define method only if validation is turned on
if description && (Apipie.configuration.validate == true ||
Apipie.configuration.validate == :implicitly ||
Apipie.configuration.validate == :explicitly)

_apipie_save_method_params(description.method, description.params)

unless instance_methods.include?(:apipie_validations)
define_method(:apipie_validations) do
method_params = self.class._apipie_get_method_params(action_name)

if Apipie.configuration.validate_presence?
method_params.each do |_, param|
# check if required parameters are present
raise ParamMissing.new(param) if param.required && !params.has_key?(param.name)
end
end

if Apipie.configuration.validate_value?
method_params.each do |_, param|
# params validations
param.validate(params[:"#{param.name}"]) if params.has_key?(param.name)
end
end

# Only allow params passed in that are defined keys in the api
# Auto skip the default params (format, controller, action)
if Apipie.configuration.validate_key?
params.reject{|k,_| %w[format controller action].include?(k.to_s) }.each_key do |param|
# params allowed
raise UnknownParam.new(param) if method_params.select {|_,p| p.name.to_s == param.to_s}.empty?
end
end

if Apipie.configuration.process_value?
@api_params ||= {}
method_params.each do |_, param|
# params processing
@api_params[param.as] = param.process_value(params[:"#{param.name}"]) if params.has_key?(param.name)
end
end
end
end

if (Apipie.configuration.validate == :implicitly || Apipie.configuration.validate == true)
old_method = instance_method(description.method)

define_method(description.method) do |*args|
apipie_validations

# run the original method code
old_method.bind(self).call(*args)
end
end

end
end

def _apipie_save_method_params(method, params)
@method_params ||= {}
@method_params[method] = params
end

def _apipie_get_method_params(method)
@method_params[method]
end

# Describe request header.
# Headers can't be validated with config.validate_presence = true
#
# Example:
# header 'ClientId', "client-id"
# def show
# render :text => headers['HTTP_CLIENT_ID']
# end
#
def header(header_name, description, options = {}) #:doc
return unless Apipie.active_dsl?
_apipie_dsl_data[:headers] << {
name: header_name,
description: description,
options: options
}
end
end

end # module DSL
end # module Apipie
Loading