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

feat: add @response_example tag for document examples of responses. #43

Merged
merged 2 commits into from
Aug 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 54 additions & 54 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -10,29 +10,29 @@ PATH
GEM
remote: https://rubygems.org/
specs:
actioncable (7.2.0)
actionpack (= 7.2.0)
activesupport (= 7.2.0)
actioncable (7.2.1)
actionpack (= 7.2.1)
activesupport (= 7.2.1)
nio4r (~> 2.0)
websocket-driver (>= 0.6.1)
zeitwerk (~> 2.6)
actionmailbox (7.2.0)
actionpack (= 7.2.0)
activejob (= 7.2.0)
activerecord (= 7.2.0)
activestorage (= 7.2.0)
activesupport (= 7.2.0)
actionmailbox (7.2.1)
actionpack (= 7.2.1)
activejob (= 7.2.1)
activerecord (= 7.2.1)
activestorage (= 7.2.1)
activesupport (= 7.2.1)
mail (>= 2.8.0)
actionmailer (7.2.0)
actionpack (= 7.2.0)
actionview (= 7.2.0)
activejob (= 7.2.0)
activesupport (= 7.2.0)
actionmailer (7.2.1)
actionpack (= 7.2.1)
actionview (= 7.2.1)
activejob (= 7.2.1)
activesupport (= 7.2.1)
mail (>= 2.8.0)
rails-dom-testing (~> 2.2)
actionpack (7.2.0)
actionview (= 7.2.0)
activesupport (= 7.2.0)
actionpack (7.2.1)
actionview (= 7.2.1)
activesupport (= 7.2.1)
nokogiri (>= 1.8.5)
racc
rack (>= 2.2.4, < 3.2)
Expand All @@ -41,35 +41,35 @@ GEM
rails-dom-testing (~> 2.2)
rails-html-sanitizer (~> 1.6)
useragent (~> 0.16)
actiontext (7.2.0)
actionpack (= 7.2.0)
activerecord (= 7.2.0)
activestorage (= 7.2.0)
activesupport (= 7.2.0)
actiontext (7.2.1)
actionpack (= 7.2.1)
activerecord (= 7.2.1)
activestorage (= 7.2.1)
activesupport (= 7.2.1)
globalid (>= 0.6.0)
nokogiri (>= 1.8.5)
actionview (7.2.0)
activesupport (= 7.2.0)
actionview (7.2.1)
activesupport (= 7.2.1)
builder (~> 3.1)
erubi (~> 1.11)
rails-dom-testing (~> 2.2)
rails-html-sanitizer (~> 1.6)
activejob (7.2.0)
activesupport (= 7.2.0)
activejob (7.2.1)
activesupport (= 7.2.1)
globalid (>= 0.3.6)
activemodel (7.2.0)
activesupport (= 7.2.0)
activerecord (7.2.0)
activemodel (= 7.2.0)
activesupport (= 7.2.0)
activemodel (7.2.1)
activesupport (= 7.2.1)
activerecord (7.2.1)
activemodel (= 7.2.1)
activesupport (= 7.2.1)
timeout (>= 0.4.0)
activestorage (7.2.0)
actionpack (= 7.2.0)
activejob (= 7.2.0)
activerecord (= 7.2.0)
activesupport (= 7.2.0)
activestorage (7.2.1)
actionpack (= 7.2.1)
activejob (= 7.2.1)
activerecord (= 7.2.1)
activesupport (= 7.2.1)
marcel (~> 1.0)
activesupport (7.2.0)
activesupport (7.2.1)
base64
bigdecimal
concurrent-ruby (~> 1.0, >= 1.3.1)
Expand Down Expand Up @@ -169,30 +169,30 @@ GEM
rackup (2.1.0)
rack (>= 3)
webrick (~> 1.8)
rails (7.2.0)
actioncable (= 7.2.0)
actionmailbox (= 7.2.0)
actionmailer (= 7.2.0)
actionpack (= 7.2.0)
actiontext (= 7.2.0)
actionview (= 7.2.0)
activejob (= 7.2.0)
activemodel (= 7.2.0)
activerecord (= 7.2.0)
activestorage (= 7.2.0)
activesupport (= 7.2.0)
rails (7.2.1)
actioncable (= 7.2.1)
actionmailbox (= 7.2.1)
actionmailer (= 7.2.1)
actionpack (= 7.2.1)
actiontext (= 7.2.1)
actionview (= 7.2.1)
activejob (= 7.2.1)
activemodel (= 7.2.1)
activerecord (= 7.2.1)
activestorage (= 7.2.1)
activesupport (= 7.2.1)
bundler (>= 1.15.0)
railties (= 7.2.0)
railties (= 7.2.1)
rails-dom-testing (2.2.0)
activesupport (>= 5.0.0)
minitest
nokogiri (>= 1.6)
rails-html-sanitizer (1.6.0)
loofah (~> 2.21)
nokogiri (~> 1.14)
railties (7.2.0)
actionpack (= 7.2.0)
activesupport (= 7.2.0)
railties (7.2.1)
actionpack (= 7.2.1)
activesupport (= 7.2.1)
irb (~> 1.13)
rackup (>= 1.0.0)
rake (>= 12.2)
Expand All @@ -205,7 +205,7 @@ GEM
regexp_parser (2.9.2)
reline (0.5.9)
io-console (~> 0.5)
rexml (3.3.5)
rexml (3.3.6)
strscan
rubocop (1.65.1)
json (~> 2.3)
Expand Down
17 changes: 16 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,22 @@ Documents the responses of the endpoint and overrides the default responses foun

`# @response User not found by the provided Id(404) [Hash{success: Boolean, message: String}]`

`# @response Validation errors(422) [Hash{success: Boolean, erros: Array<Hash{field: String, type: String, detail: Array<String>}>}]`
`# @response Validation errors(422) [Hash{success: Boolean, errors: Array<Hash{field: String, type: String, detail: Array<String>}>}]`

</details>

<details>
<summary style="font-weight: bold; font-size: 1.2em;">@response_example</summary>

**Structure**: `@response_example text(code) [String Hash]`

Documents response examples of the endpoint associated to a response code.

**Example**:

`# @response_example Invalida Email(422) [{success: "false", errors: [{field: "email", type: "email", detail: ["Invalid email"]}] }]`

`# @response_example Id not exists (404) [{success: "false", message: "Nothing found with the provided ID." }]`

</details>

Expand Down
3 changes: 3 additions & 0 deletions lib/oas_rails.rb
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,11 @@ module Spec

module YARD
autoload :RequestBodyTag, 'oas_rails/yard/request_body_tag'
autoload :ExampleTag, 'oas_rails/yard/example_tag'
autoload :RequestBodyExampleTag, 'oas_rails/yard/request_body_example_tag'
autoload :ParameterTag, 'oas_rails/yard/parameter_tag'
autoload :ResponseTag, 'oas_rails/yard/response_tag'
autoload :ResponseExampleTag, 'oas_rails/yard/response_example_tag'
autoload :OasRailsFactory, 'oas_rails/yard/oas_rails_factory'
end

Expand Down Expand Up @@ -84,6 +86,7 @@ def configure_yard!
'Request body Example' => [:request_body_example, :with_request_body_example],
'Parameter' => [:parameter, :with_parameter],
'Response' => [:response, :with_response],
'Response Example' => [:response_example, :with_response_example],
'Endpoint Tags' => [:tags],
'Summary' => [:summary],
'No Auth' => [:no_auth],
Expand Down
5 changes: 4 additions & 1 deletion lib/oas_rails/builders/responses_builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ def initialize(specification)

def from_oas_route(oas_route)
oas_route.docstring.tags(:response).each do |tag|
@responses.add_response(ResponseBuilder.new(@specification).from_tag(tag).build)
content = ContentBuilder.new(@specification, :outgoing).with_schema(tag.schema).with_examples_from_tags(oas_route.docstring.tags(:response_example).filter { |re| re.code == tag.name }).build
response = ResponseBuilder.new(@specification).with_code(tag.name.to_i).with_description(tag.text).with_content(content).build

@responses.add_response(response)
end

self
Expand Down
12 changes: 12 additions & 0 deletions lib/oas_rails/yard/example_tag.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
module OasRails
module YARD
class ExampleTag < ::YARD::Tags::Tag
attr_accessor :content

def initialize(tag_name, text, content: {})
super(tag_name, text, nil, nil)
@content = content
end
end
end
end
21 changes: 20 additions & 1 deletion lib/oas_rails/yard/oas_rails_factory.rb
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,23 @@ def parse_tag_with_response(tag_name, text)
ResponseTag.new(tag_name, code, name, schema)
end

# Parses a tag that represents a response example.
# @param tag_name [String] The name of the tag.
# @param text [String] The tag text to parse.
# @return [ResponseExampleTag] The parsed response example tag object.
def parse_tag_with_response_example(tag_name, text)
description, code, hash = extract_name_code_and_hash(text)
ResponseExampleTag.new(tag_name, description, content: hash, code:)
end

private

# Reusable method for extracting description, type, and content with an option to process content.
# @param text [String] The text to parse.
# @param process_content [Boolean] Whether to evaluate the content as a hash.
# @return [Array] An array containing the description, type, and content or remaining text.
def extract_description_type_and_content(text, process_content: false)
match = text.match(/^(.*?)\s*\[(.*?)\]\s*(.*)$/)
match = text.match(/^(.*?)\s*\[(.*)\]\s*(.*)$/)
raise ArgumentError, "Invalid tag format: #{text}" if match.nil?

description = match[1].strip
Expand Down Expand Up @@ -84,6 +93,16 @@ def extract_name_code_and_schema(text)
[name, code, schema]
end

# Specific method to extract name, code, and hash for responses examples.
# @param text [String] The text to parse.
# @return [Array] An array containing the name, code, and schema.
def extract_name_code_and_hash(text)
name, code = extract_text_and_parentheses_content(text)
_, type, = extract_description_type_and_content(text)
hash = eval_content(type)
[name, code, hash]
end

# Evaluates a string as a hash, handling errors gracefully.
# @param content [String] The content string to evaluate.
# @return [Hash] The evaluated hash, or an empty hash if an error occurs.
Expand Down
5 changes: 2 additions & 3 deletions lib/oas_rails/yard/request_body_example_tag.rb
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
module OasRails
module YARD
class RequestBodyExampleTag < ::YARD::Tags::Tag
class RequestBodyExampleTag < ExampleTag
attr_accessor :content

def initialize(tag_name, text, content: {})
super(tag_name, text, nil, nil)
@content = content
super
end
end
end
Expand Down
12 changes: 12 additions & 0 deletions lib/oas_rails/yard/response_example_tag.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
module OasRails
module YARD
class ResponseExampleTag < ExampleTag
attr_accessor :code

def initialize(tag_name, text, content: {}, code: 200)
super(tag_name, text, content:)
@code = code
end
end
end
end
1 change: 1 addition & 0 deletions lib/oas_rails/yard/response_tag.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module YARD
class ResponseTag < ::YARD::Tags::Tag
attr_accessor :schema

# TODO: name == code. The name MUST be changed to code for better understanding
def initialize(tag_name, name, text, schema)
super(tag_name, text, nil, name)
@schema = schema
Expand Down
2 changes: 2 additions & 0 deletions test/dummy/app/controllers/users_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ def index
# @response User not found by the provided Id(404) [Hash{success: Boolean, message: String}]
# @response You dont have the rigth persmissions for access to this reasource(403) [Hash{success: Boolean, message: String}]
# @response A test response from an Issue(405) [Hash{message: String, data: Hash{availabilities: Array<String>, dates: Array<Date>}}]
# @response_example Nice 405 Error(405) [{message: "Hello", data: {availabilities: ["one", "two", "three"], dates: ["10-06-2020"]}}]
# @response_example Another 405 Error (405) [{message: "another", data: {availabilities: ["three"], dates: []}}]
def show
render json: @user
end
Expand Down
18 changes: 18 additions & 0 deletions test/lib/oas_rails/yard/oas_rails_factory_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
require "test_helper"

module OasRails
module YARD
class OasRailsFactoryTest < ActiveSupport::TestCase
def test_parse_tag_with_response_example
response = OasRailsFactory.new.parse_tag_with_response_example(:response_example,
'405 Error(405) [{message: "Hello", data: {availabilities: ["one", "two", "three"], dates: ["10-06-2020"]}}]')

assert response.is_a?(ResponseExampleTag)
assert_equal "405", response.code
assert_equal '405 Error', response.text
expected_hash = { message: "Hello", data: { availabilities: %w[one two three], dates: ["10-06-2020"] } }
assert_equal expected_hash, response.content
end
end
end
end