Module: Rails::Swagger

Defined in:
lib/rails/swagger.rb,
lib/rails/swagger/engine.rb,
lib/rails/swagger/router.rb,
lib/rails/swagger/controller.rb

Defined Under Namespace

Modules: Controller Classes: Endpoint, Engine, Router

Constant Summary collapse

ALLOWED_FORMATS =

Defines the Swagger spec file formats that are parsable by this gem. Currently only the JSON format is supported.

[".json"].freeze
RESOURCE_ROUTES =

Defines RESTful routing conventions

{
  get: :index,
  post: :create
}.freeze
PARAM_ROUTES =
{
  get: :show,
  patch: :update,
  put: :update,
  delete: :destroy
}.freeze

Class Method Summary collapse

Class Method Details

.Engine(base_module, file) ⇒ Object

Helper method to create a new engine based on a module namespace prefix and Swagger spec file. The engine ceated will be a subclass of Rails::Swagger::Engine, which itself inherits from Rails::Engine.



19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
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
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
# File 'lib/rails/swagger/engine.rb', line 19

def self.Engine base_module, file

  # Convert the module prefix into a constant if passed in as a string
  base_module = Object.const_get base_module if String === base_module

  # Ensure the Swagger spec file is in an acceptable format
  ext = File.extname(file)
  unless ALLOWED_FORMATS.include? ext
    raise "Swagger files must end with #{ALLOWED_FORMATS.join(' or ')}. File given: #{file}"
  end

  # Attempt to read and parse the Swagger spec file
  document = File.read file
  case File.extname file
  when ".json"
    begin
      require 'json'
      document = JSON.parse document
    rescue JSON::ParserError
      raise $!, "Problem parsing swagger spec file \"#{file}\": #{$!.message.lines.first.strip}", $@
    end
  else
    raise "Swagger files must end with #{ALLOWED_FORMATS.join(' or ')}. File given: #{file}"
  end

  # Verify that the swagger version is supported
  unless document["swagger"] == "2.0"
    raise "Unsupported swagger version: #{document["swagger"]}. #{self} supports only version 2.0"
  end

  # Builds a routing tree based on the swagger spec file.
  # We'll add each endpoint to the routing tree and additionally
  # store it in an array to be used below.
  router = Router.new
  endpoints = []
  document["paths"].each do |url, actions|
    actions.each do |verb, definition|
      route = Endpoint.new(verb.downcase.to_sym, url, definition)
      router << route
      endpoints << route
    end
  end

  # Creates the engine that will be used to actually route the
  # contents of the swagger spec file. The engine will eventually be
  # attached to the base module (argument to this current method).
  #
  # Exposes `::router` and `::endpoints` methods to allow other parts
  # of the code to tie requests back to their spec file definitions.
  engine = Class.new Engine do

    @router = router
    @endpoints = Hash.new
    @schema = document

    class << self
      def router
        @router
      end
      def endpoints
        @endpoints
      end
      def schema
        @schema
      end
    end

    # Rack app for serving the original swagger file
    # swagger_app = Class.new do
    #   def inspect
    #     "Rails::Swagger::Engine"
    #   end
    #   define_method :call do |env|
    #     [
    #       200,
    #       {"Content-Type" => "application/json"},
    #       [engine.schema.to_json]
    #     ]
    #   end
    # end

    # Adds routes to the engine by passing the Mapper to the top
    # of the routing tree. `self` inside the block refers to an
    # instance of `ActionDispatch::Routing::Mapper`.
    self.routes.draw do
      scope module: base_module.name.underscore, format: false do
        # get "swagger.json", to: swagger_app.new
        router.draw self
      end
    end

  end

  # Assign the engine as a class on the base module
  base_module.const_set :Engine, engine

  # Creates a hash that maps routes back to their swagger spec file
  # equivalents. This is accomplished by mocking a request for each
  # swagger spec file endpoint and determining which controller and
  # action the request is routed to. Swagger spec file definitions
  # are then attached to that controller/action pair.
  endpoints.each do |route|

    # Mocks a request using the route's URL
    url = ::ActionDispatch::Journey::Router::Utils.normalize_path route.path
    env = ::Rack::MockRequest.env_for url, method: route[:method].upcase
    req = ::ActionDispatch::Request.new env

    # Maps the swagger spec endpoint to the destination controller
    # action by routing the request.
    mapped = engine.routes.router.recognize(req){}.first[1]
    key = "#{mapped[:controller]}##{mapped[:action]}"
    engine.endpoints[key] = route

  end
  engine.endpoints.freeze

  # Defines a helper module on the base module that can be used to
  # properly generate swagger-aware controllers. Any controllers
  # referenced from a swagger spec file should include this module.
  mod = Module.new do
    @base = base_module
    def self.included controller
      base_module = @base
      controller.include Controller
      define_method :swagger_engine do
        base_module.const_get :Engine
      end
    end
  end
  base_module.const_set :SwaggerController, mod

  # Returns the new engine
  base_module.const_get :Engine

end