rswag: OAS 3 Document has examples in the wrong path

According to the README:

Enable auto generation examples from responses To enable examples generation from responses add callback above run_test! like:

after do |example|
  example.metadata[:response][:examples] = { 'application/json' => JSON.parse(response.body, symbolize_names: true) }
end

However this results in the examples being directly on the response object, whereas it should be on the media type object of the response’s content property.

Structural error at paths./api/v1/accounts.get.responses.200
should NOT have additional properties
additionalProperty: examples
Jump to line 158

Any suggestions for a way to work around this?

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Reactions: 12
  • Comments: 17 (4 by maintainers)

Commits related to this issue

Most upvoted comments

Rswag: 2.3.1 Rails: 6.0.3.x

Here my code how to fix in the meantime the examples problem:

...
  # spec/swagger_helper.rb
  # A quick workaround unless rswag fix it.
  # upgrade_content! - https://github.com/rswag/rswag/issues/325
  # stop - https://github.com/rswag/rswag/pull/304
  config.before(:all) do
    module Rswag
      module Specs
        # Hook SwaggerFormatter class method.
        class SwaggerFormatter < ::RSpec::Core::Formatters::BaseTextFormatter
          ::RSpec::Core::Formatters.register(self, :example_group_finished, :stop)

          def upgrade_content!(mime_list, target_node)
            # Fix examples with the following line change.
            target_node[:content] ||= {}
            schema = target_node[:schema]
            return if mime_list.empty? || schema.nil?

            mime_list.each do |mime_type|
              # Fix examples with the following line change.
              (target_node[:content][mime_type] ||= {}).merge!(schema: schema)
            end
          end

          def stop(_notification = nil)
            @config.swagger_docs.each do |url_path, doc|
              unless doc_version(doc).start_with?('2')
                doc[:paths]&.each_pair do |_k, v|
                  v.each_pair do |_verb, value|
                    is_hash = value.is_a?(Hash)
                    if is_hash && value.dig(:parameters)
                      schema_param = value.dig(:parameters)&.find { |p| (p[:in] == :body || p[:in] == :formData) && p[:schema] }
                      mime_list = value.dig(:consumes)
                      if value && schema_param && mime_list
                        value[:requestBody] = { content: {} } unless value.dig(:requestBody, :content)
                        mime_list.each do |mime|
                          value[:requestBody][:content][mime] = { schema: schema_param[:schema] }
                          # Fix examples with the following line.
                          value[:requestBody][:content][mime].merge!(examples: schema_param[:examples]) if schema_param[:examples]
                        end
                      end

                      value[:parameters].reject! { |p| p[:in] == :body || p[:in] == :formData }
                    end
                    remove_invalid_operation_keys!(value)
                  end
                end
              end

              file_path = File.join(@config.swagger_root, url_path)
              dirname = File.dirname(file_path)
              FileUtils.mkdir_p dirname unless File.exist?(dirname)

              File.open(file_path, 'w') do |file|
                file.write(pretty_generate(doc))
              end

              @output.puts "Swagger doc generated at #{file_path}"
            end
          end
        end
      end
    end
  end

And the examples collecting should look like:

# spec/requests/...

after do |example|
  example.metadata[:response][:content] = {
      'application/json' => { example: JSON.parse(response.body, symbolize_names: true) }
  }
end

As a quick workaround, I had to monkey patch the formatter:

module Rswag
  module Specs
    class SwaggerFormatter < ::RSpec::Core::Formatters::BaseTextFormatter

      def upgrade_content!(mime_list, target_node)
        target_node[:content] ||= {} # Here we're avoiding "content" key overriding
        schema = target_node[:schema]
        return if mime_list.empty? || schema.nil?

        mime_list.each do |mime_type|
          # TODO upgrade to have content-type specific schema
          (target_node[:content][mime_type] ||= {}).merge!(schema: schema)
        end
      end

    end
  end
end

And the examples collecting should look like

  after do |example|
    example.metadata[:response][:content] = {
      'application/json' => { example: ... }
    }
  end

@oblakeerickson you’re right, was on 2.3.2. Thanks for letting me know! 🙏

@phlegx Thank you so much. Will give it a try

Update: It works! Thank you again.

I wanted automatic generation of both multiple response AND payload examples. At the point, it got a little thick, so threw auto-generation of path parameters too.

Can see solution at https://gist.github.com/rdnewman/ba5143f1598eff72b2cba48ce78ca43d. Most files should be plug and play (but watch the top level module names). Only tested with my own project that I did this for (using Redoc), so YMMV.

I haven’t been able to get this workaround to work for my examples. Has there been any progress on the PR that would fix this?

Hi @olegykz,

I can confirm that your workaround works as advertised 😃

Gerard.