openapi-ruby 4.1.0 → 4.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,135 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OpenapiRuby
4
+ # Host-neutral logic behind the served schema documents and the Swagger UI.
5
+ # The Rails controllers and RackApp are both thin shells over this, so the
6
+ # two hosts cannot drift apart.
7
+ module Serving
8
+ module_function
9
+
10
+ def schema_names
11
+ OpenapiRuby.configuration.schemas.keys.map(&:to_s)
12
+ end
13
+
14
+ def schema_config_for(schema_name)
15
+ OpenapiRuby.configuration.schemas[schema_name.to_sym]
16
+ end
17
+
18
+ def schema_file_path(schema_name)
19
+ config = OpenapiRuby.configuration
20
+ ext = (config.schema_output_format == :json) ? "json" : "yaml"
21
+ File.join(OpenapiRuby.app_root, config.schema_output_dir, "#{schema_name}.#{ext}")
22
+ end
23
+
24
+ def schema_format
25
+ (OpenapiRuby.configuration.schema_output_format == :json) ? :json : :yaml
26
+ end
27
+
28
+ # Returns [content, content_type], or nil when the schema is unknown or
29
+ # has not been generated yet — callers turn that into a 404.
30
+ #
31
+ # `request` is handed to the configured :openapi_filter untouched. Rails
32
+ # passes an ActionDispatch::Request and RackApp a Rack::Request; the hook
33
+ # only ever needs the shared Rack request API.
34
+ def schema_document(schema_name, request: nil)
35
+ schema_config = schema_config_for(schema_name)
36
+ return nil unless schema_config
37
+
38
+ file_path = schema_file_path(schema_name)
39
+ return nil unless File.exist?(file_path)
40
+
41
+ content = File.read(file_path)
42
+
43
+ if schema_config[:openapi_filter]
44
+ doc = parse_content(file_path, content)
45
+ schema_config[:openapi_filter].call(doc, request)
46
+ content = serialize_doc(file_path, doc)
47
+ end
48
+
49
+ [content, content_type_for(file_path)]
50
+ end
51
+
52
+ def content_type_for(file_path)
53
+ file_path.end_with?(".json") ? "application/json" : "application/x-yaml"
54
+ end
55
+
56
+ def parse_content(file_path, content)
57
+ if file_path.end_with?(".json")
58
+ JSON.parse(content)
59
+ else
60
+ YAML.safe_load(content, permitted_classes: [Date, Time])
61
+ end
62
+ end
63
+
64
+ def serialize_doc(file_path, doc)
65
+ if file_path.end_with?(".json")
66
+ JSON.pretty_generate(doc)
67
+ else
68
+ doc.to_yaml
69
+ end
70
+ end
71
+
72
+ def oauth2_redirect_file
73
+ File.join(gem_root, "app", "views", "openapi_ruby", "oauth2_redirect.html")
74
+ end
75
+
76
+ def gem_root
77
+ File.expand_path("../..", __dir__)
78
+ end
79
+
80
+ # `schema_urls` is an array of {url:, name:} — the caller builds them,
81
+ # since only it knows how the docs are mounted.
82
+ def swagger_ui_html(schema_urls:, ui_config: {})
83
+ <<~HTML
84
+ <!DOCTYPE html>
85
+ <html lang="en">
86
+ <head>
87
+ <meta charset="UTF-8">
88
+ <title>#{ui_config[:title] || "API Documentation"}</title>
89
+ <link rel="stylesheet" href="https://unpkg.com/swagger-ui-dist@5/swagger-ui.css">
90
+ <style>
91
+ html { box-sizing: border-box; overflow-y: scroll; }
92
+ *, *:before, *:after { box-sizing: inherit; }
93
+ body { margin: 0; background: #fafafa; }
94
+ </style>
95
+ </head>
96
+ <body>
97
+ <div id="swagger-ui"></div>
98
+ <script src="https://unpkg.com/swagger-ui-dist@5/swagger-ui-bundle.js"></script>
99
+ <script src="https://unpkg.com/swagger-ui-dist@5/swagger-ui-standalone-preset.js"></script>
100
+ <script>
101
+ SwaggerUIBundle({
102
+ #{schema_urls_js(schema_urls)},
103
+ dom_id: '#swagger-ui',
104
+ deepLinking: true,
105
+ presets: [
106
+ SwaggerUIBundle.presets.apis,
107
+ SwaggerUIStandalonePreset
108
+ ],
109
+ plugins: [
110
+ SwaggerUIBundle.plugins.DownloadUrl
111
+ ],
112
+ layout: "#{(schema_urls.size > 1) ? "StandaloneLayout" : "BaseLayout"}",
113
+ #{ui_config_js(ui_config)}
114
+ });
115
+ </script>
116
+ </body>
117
+ </html>
118
+ HTML
119
+ end
120
+
121
+ def schema_urls_js(schema_urls)
122
+ if schema_urls.size > 1
123
+ "urls: #{schema_urls.to_json}"
124
+ else
125
+ "url: \"#{schema_urls.first&.fetch(:url)}\""
126
+ end
127
+ end
128
+
129
+ def ui_config_js(ui_config)
130
+ ui_config.except(:title).map { |k, v|
131
+ "#{k}: #{v.to_json}"
132
+ }.join(",\n ")
133
+ end
134
+ end
135
+ end
@@ -0,0 +1,99 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OpenapiRuby
4
+ module Testing
5
+ # Bridges the adapters onto whichever request-issuing API the host's test
6
+ # context provides. Rails integration tests take keyword arguments and
7
+ # expose `response`; rack-test (Hanami, Sinatra, bare Rack) takes
8
+ # positional arguments plus a Rack env and exposes `last_response`.
9
+ #
10
+ # Both response objects answer #status, #body and #headers, so callers
11
+ # need no further normalisation.
12
+ module Transport
13
+ # Rails is checked first: a suite can include Rack::Test::Methods
14
+ # alongside the integration helpers, and in that case the Rails session
15
+ # is the one that boots the app under test.
16
+ def self.for(context)
17
+ if context.respond_to?(:integration_session)
18
+ RailsIntegration.new(context)
19
+ elsif context.respond_to?(:last_response)
20
+ require_app!(context)
21
+ RackTest.new(context)
22
+ elsif OpenapiRuby.rails_host?
23
+ # A hand-rolled harness that defines the verb methods itself. Left
24
+ # working rather than second-guessed.
25
+ RailsIntegration.new(context)
26
+ else
27
+ # Without rack-test, dispatch would land on the example-group DSL's
28
+ # own `get`, and the user would get told that `get` is unavailable
29
+ # inside an example — true, and no help at all here.
30
+ raise OpenapiRuby::Error,
31
+ "openapi_ruby found no way to issue requests from #{describe(context)}. " \
32
+ "Add rack-test to your bundle, `include Rack::Test::Methods`, and define `app`."
33
+ end
34
+ end
35
+
36
+ # rack-test resolves `app` lazily, so a missing one surfaces as a bare
37
+ # NameError from inside the gem rather than as the setup mistake it is.
38
+ def self.require_app!(context)
39
+ return if context.respond_to?(:app)
40
+
41
+ raise OpenapiRuby::Error,
42
+ "openapi_ruby needs the Rack app under test in #{describe(context)}. " \
43
+ "Define it as `let(:app) { MyApp }` in RSpec, or an `app` method in Minitest."
44
+ end
45
+
46
+ def self.describe(context)
47
+ context.is_a?(Class) ? context.name.to_s : context.class.name.to_s
48
+ end
49
+
50
+ class RailsIntegration
51
+ def initialize(context)
52
+ @context = context
53
+ end
54
+
55
+ def dispatch(method, path, params: nil, headers: nil)
56
+ args = {}
57
+ args[:params] = params unless params.nil?
58
+ args[:headers] = headers unless headers.nil?
59
+ @context.send(method.to_sym, path, **args)
60
+ end
61
+
62
+ def response
63
+ @context.response
64
+ end
65
+ end
66
+
67
+ class RackTest
68
+ # Rack keeps these two out of the HTTP_ namespace.
69
+ UNPREFIXED_HEADERS = {
70
+ "content-type" => "CONTENT_TYPE",
71
+ "content-length" => "CONTENT_LENGTH"
72
+ }.freeze
73
+
74
+ def initialize(context)
75
+ @context = context
76
+ end
77
+
78
+ # rack-test treats a String `params` as the request body and a Hash as
79
+ # form/query data, which is the same split the adapters already make.
80
+ def dispatch(method, path, params: nil, headers: nil)
81
+ @context.send(method.to_sym, path, params || {}, rack_env(headers || {}))
82
+ end
83
+
84
+ def response
85
+ @context.last_response
86
+ end
87
+
88
+ private
89
+
90
+ def rack_env(headers)
91
+ headers.each_with_object({}) do |(name, value), env|
92
+ key = name.to_s
93
+ env[UNPREFIXED_HEADERS.fetch(key.downcase) { "HTTP_#{key.upcase.tr("-", "_")}" }] = value.to_s
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module OpenapiRuby
4
- VERSION = "4.1.0"
4
+ VERSION = "4.2.0"
5
5
  end
data/lib/openapi_ruby.rb CHANGED
@@ -12,6 +12,7 @@ require "yaml"
12
12
 
13
13
  require_relative "openapi_ruby/version"
14
14
  require_relative "openapi_ruby/errors"
15
+ require_relative "openapi_ruby/host"
15
16
  require_relative "openapi_ruby/configuration"
16
17
 
17
18
  module OpenapiRuby
@@ -57,6 +58,7 @@ require_relative "openapi_ruby/dsl/response_context"
57
58
  require_relative "openapi_ruby/dsl/operation_context"
58
59
  require_relative "openapi_ruby/dsl/context"
59
60
  require_relative "openapi_ruby/dsl/metadata_store"
61
+ require_relative "openapi_ruby/testing/transport"
60
62
  require_relative "openapi_ruby/testing/request_builder"
61
63
  require_relative "openapi_ruby/testing/response_validator"
62
64
  require_relative "openapi_ruby/testing/request_validator"
@@ -70,6 +72,9 @@ require_relative "openapi_ruby/middleware/error_handler"
70
72
  require_relative "openapi_ruby/middleware/schema_resolver"
71
73
  require_relative "openapi_ruby/middleware/request_validation"
72
74
  require_relative "openapi_ruby/middleware/response_validation"
75
+ require_relative "openapi_ruby/middleware/installer"
76
+ require_relative "openapi_ruby/serving"
77
+ require_relative "openapi_ruby/rack_app"
73
78
  require_relative "openapi_ruby/controller_helpers"
74
79
  if defined?(Rails::Engine)
75
80
  require_relative "openapi_ruby/engine"
@@ -1,21 +1,3 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "openapi_ruby/generator/rake_task_support"
4
-
5
- namespace :openapi_ruby do
6
- desc "Generate OpenAPI schema files from spec definitions and components"
7
- task :generate do
8
- support = OpenapiRuby::Generator::RakeTaskSupport
9
- framework = ENV.fetch("FRAMEWORK") { support.detect_test_framework }.to_s
10
- pattern = ENV.fetch("PATTERN") { support.default_pattern_for(framework) }
11
-
12
- # Spawn a subprocess so RAILS_ENV defaults to "test" cleanly,
13
- # just like rswag did with RSpec::Core::RakeTask.
14
- env = {"RAILS_ENV" => ENV.fetch("RAILS_ENV", "test"), "OPENAPI_RUBY_GENERATING" => "true"}
15
- script = support.generate_script(framework, pattern)
16
- command = "bundle exec ruby -e #{Shellwords.escape(script)}"
17
-
18
- puts "Generating OpenAPI schemas (#{framework})..."
19
- system(env, command) || abort("Schema generation failed")
20
- end
21
- end
3
+ require "openapi_ruby/rake_tasks"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: openapi-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.1.0
4
+ version: 4.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Morten Hartvig
@@ -51,23 +51,9 @@ dependencies:
51
51
  - - ">="
52
52
  - !ruby/object:Gem::Version
53
53
  version: '2.0'
54
- - !ruby/object:Gem::Dependency
55
- name: railties
56
- requirement: !ruby/object:Gem::Requirement
57
- requirements:
58
- - - ">="
59
- - !ruby/object:Gem::Version
60
- version: '7.0'
61
- type: :runtime
62
- prerelease: false
63
- version_requirements: !ruby/object:Gem::Requirement
64
- requirements:
65
- - - ">="
66
- - !ruby/object:Gem::Version
67
- version: '7.0'
68
- description: A unified OpenAPI toolkit for Rails that combines test-driven spec generation,
69
- reusable schema components as Ruby classes, and runtime request/response validation
70
- middleware. Supports OpenAPI 3.0 and 3.1. Works with both RSpec and Minitest.
54
+ description: A unified OpenAPI toolkit for Rails and Hanami that combines test-driven
55
+ spec generation, reusable schema components as Ruby classes, and runtime request/response
56
+ validation middleware. Supports OpenAPI 3.0 and 3.1. Works with both RSpec and Minitest.
71
57
  email:
72
58
  - morten@hartvigsen.dev
73
59
  executables: []
@@ -110,19 +96,26 @@ files:
110
96
  - lib/openapi_ruby/generator/rake_task_support.rb
111
97
  - lib/openapi_ruby/generator/schema_writer.rb
112
98
  - lib/openapi_ruby/generator/test_schema_suppressor.rb
99
+ - lib/openapi_ruby/hanami.rb
100
+ - lib/openapi_ruby/host.rb
113
101
  - lib/openapi_ruby/middleware/coercion.rb
114
102
  - lib/openapi_ruby/middleware/error_handler.rb
103
+ - lib/openapi_ruby/middleware/installer.rb
115
104
  - lib/openapi_ruby/middleware/path_matcher.rb
116
105
  - lib/openapi_ruby/middleware/request_validation.rb
117
106
  - lib/openapi_ruby/middleware/response_validation.rb
118
107
  - lib/openapi_ruby/middleware/schema_resolver.rb
119
108
  - lib/openapi_ruby/minitest.rb
109
+ - lib/openapi_ruby/rack_app.rb
110
+ - lib/openapi_ruby/rake_tasks.rb
120
111
  - lib/openapi_ruby/rspec.rb
112
+ - lib/openapi_ruby/serving.rb
121
113
  - lib/openapi_ruby/testing/assertions.rb
122
114
  - lib/openapi_ruby/testing/coverage.rb
123
115
  - lib/openapi_ruby/testing/request_builder.rb
124
116
  - lib/openapi_ruby/testing/request_validator.rb
125
117
  - lib/openapi_ruby/testing/response_validator.rb
118
+ - lib/openapi_ruby/testing/transport.rb
126
119
  - lib/openapi_ruby/version.rb
127
120
  - lib/tasks/openapi_ruby.rake
128
121
  homepage: https://github.com/openapi-ruby/openapi-ruby
@@ -149,6 +142,6 @@ required_rubygems_version: !ruby/object:Gem::Requirement
149
142
  requirements: []
150
143
  rubygems_version: 3.6.9
151
144
  specification_version: 4
152
- summary: OpenAPI 3.0/3.1 toolkit for Rails — spec generation, schema components, and
153
- runtime validation
145
+ summary: OpenAPI 3.0/3.1 toolkit for Rails and Hanami — spec generation, schema components,
146
+ and runtime validation
154
147
  test_files: []