Module: Rails::Openapi
- Defined in:
- lib/rails/openapi/engine.rb,
lib/rails/openapi/router.rb,
lib/rails/openapi/version.rb
Defined Under Namespace
Classes: Endpoint, Engine, Router
Constant Summary collapse
- RESOURCE_ROUTES =
Defines RESTful routing conventions
{ get: :index, post: :create, put: :update, patch: :update, delete: :destroy }.freeze
- PARAM_ROUTES =
{ get: :show, post: :update, patch: :update, put: :update, delete: :destroy }.freeze
- VERSION =
"1.0.0"
Class Method Summary collapse
-
.Engine(namespace:, schema:, publish_schema: true) ⇒ Object
Helper method to create a new engine based on a module namespace prefix and an OpenAPI spec file.
Class Method Details
.Engine(namespace:, schema:, publish_schema: true) ⇒ Object
Helper method to create a new engine based on a module namespace prefix and an OpenAPI spec file
11 12 13 14 15 16 17 18 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 |
# File 'lib/rails/openapi/engine.rb', line 11 def self.Engine namespace:, schema:, publish_schema: true # Convert the module prefix into a constant if passed in as a string base_module = Object.const_get namespace if String === namespace # Ensure the OpenAPI spec file is in an acceptable format begin require "yaml" document = YAML.safe_load schema unless document.is_a?(Hash) && document["openapi"].present? raise "The schema argument could not be parsed as an OpenAPI schema" end unless Gem::Version.new(document["openapi"]) >= Gem::Version.new("3.0") raise "The schema argument must be an OpenAPI 3.0+ schema. You passed in a schema with version #{document["openapi"]}" end rescue Psych::SyntaxError raise $!, "Problem parsing OpenAPI schema: #{$!..lines.first.strip}", $@ end # Builds a routing tree based on the OpenAPI 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| next if verb == "parameters" 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 OpenAPI 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 = {} @schema = document.freeze class << self attr_reader :router attr_reader :endpoints attr_reader :schema end # Rack app for serving the original OpenAPI file openapi_app = Class.new do def inspect "Rails::Openapi::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`. routes.draw do scope module: base_module.name.underscore, format: false do get "openapi.json", to: openapi_app.new, as: :openapi_schema if publish_schema 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 OpenAPI spec file # equivalents. This is accomplished by mocking a request for each # OpenAPI spec file endpoint and determining which controller and # action the request is routed to. OpenAPI 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 OpenAPI spec endpoint to the destination controller # action by routing the request. begin mapped = engine.routes.router.recognize(req) {}.first[2].defaults rescue Rails.logger.error "Could not resolve the OpenAPI route for #{req.method} #{req.url}" next end 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 OpenAPI-aware controllers. Any controllers # referenced from a OpenAPI spec file should include this module. mod = Module.new do @base = base_module def self.included controller base_module = @base # Returns a reference to the Rails engine generated for this OpenAPI spec file define_method :openapi_engine do base_module.const_get :Engine end # Returns the OpenAPI schema used to generate the Rails engine as a Hash define_method :openapi_schema do base_module.const_get(:Engine).schema end # Returns the OpenAPI spec's endpoint definition for current request define_method :openapi_endpoint do openapi_engine.endpoints["#{request.path_parameters[:controller]}##{request.path_parameters[:action]}"] end # Allows the helper methods to also be used in views controller.helper_method :openapi_engine, :openapi_schema, :openapi_endpoint end end base_module.const_set :OpenapiHelper, mod # Returns the new engine base_module.const_get :Engine end |