oas_core 0.0.1

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.
Files changed (46) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +674 -0
  3. data/README.md +70 -0
  4. data/Rakefile +12 -0
  5. data/lib/oas_core/builders/content_builder.rb +44 -0
  6. data/lib/oas_core/builders/operation_builder.rb +94 -0
  7. data/lib/oas_core/builders/parameter_builder.rb +31 -0
  8. data/lib/oas_core/builders/parameters_builder.rb +46 -0
  9. data/lib/oas_core/builders/path_item_builder.rb +26 -0
  10. data/lib/oas_core/builders/request_body_builder.rb +45 -0
  11. data/lib/oas_core/builders/response_builder.rb +42 -0
  12. data/lib/oas_core/builders/responses_builder.rb +92 -0
  13. data/lib/oas_core/builders/specification_builder.rb +24 -0
  14. data/lib/oas_core/configuration.rb +143 -0
  15. data/lib/oas_core/json_schema_generator.rb +134 -0
  16. data/lib/oas_core/oas_route.rb +24 -0
  17. data/lib/oas_core/spec/components.rb +99 -0
  18. data/lib/oas_core/spec/contact.rb +20 -0
  19. data/lib/oas_core/spec/hashable.rb +41 -0
  20. data/lib/oas_core/spec/info.rb +68 -0
  21. data/lib/oas_core/spec/license.rb +20 -0
  22. data/lib/oas_core/spec/media_type.rb +26 -0
  23. data/lib/oas_core/spec/operation.rb +28 -0
  24. data/lib/oas_core/spec/parameter.rb +39 -0
  25. data/lib/oas_core/spec/path_item.rb +27 -0
  26. data/lib/oas_core/spec/paths.rb +28 -0
  27. data/lib/oas_core/spec/reference.rb +18 -0
  28. data/lib/oas_core/spec/request_body.rb +23 -0
  29. data/lib/oas_core/spec/response.rb +22 -0
  30. data/lib/oas_core/spec/responses.rb +27 -0
  31. data/lib/oas_core/spec/server.rb +19 -0
  32. data/lib/oas_core/spec/specable.rb +56 -0
  33. data/lib/oas_core/spec/specification.rb +34 -0
  34. data/lib/oas_core/spec/tag.rb +20 -0
  35. data/lib/oas_core/string.rb +13 -0
  36. data/lib/oas_core/utils.rb +94 -0
  37. data/lib/oas_core/version.rb +5 -0
  38. data/lib/oas_core/yard/example_tag.rb +14 -0
  39. data/lib/oas_core/yard/oas_core_factory.rb +166 -0
  40. data/lib/oas_core/yard/parameter_tag.rb +16 -0
  41. data/lib/oas_core/yard/request_body_example_tag.rb +13 -0
  42. data/lib/oas_core/yard/request_body_tag.rb +17 -0
  43. data/lib/oas_core/yard/response_example_tag.rb +14 -0
  44. data/lib/oas_core/yard/response_tag.rb +15 -0
  45. data/lib/oas_core.rb +88 -0
  46. metadata +120 -0
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OasCore
4
+ module Spec
5
+ module Specable
6
+ def oas_fields
7
+ []
8
+ end
9
+
10
+ def to_spec
11
+ hash = {}
12
+ oas_fields.each do |var|
13
+ key = var.to_s
14
+
15
+ camel_case_key = key.camelize(:lower).to_sym
16
+ value = send(var)
17
+
18
+ processed_value = if value.respond_to?(:to_spec)
19
+ value.to_spec
20
+ elsif value.is_a?(Array) && value.all? { |elem| elem.respond_to?(:to_spec) }
21
+ value.map(&:to_spec)
22
+ # elsif value.is_a?(Hash)
23
+ # hash = {}
24
+ # value.each do |key, object|
25
+ # hash[key] = object.to_spec
26
+ # end
27
+ # hash
28
+ else
29
+ value
30
+ end
31
+
32
+ hash[camel_case_key] = processed_value unless valid_processed_value?(processed_value)
33
+ end
34
+ hash
35
+ end
36
+
37
+ # rubocop:disable Lint/UnusedMethodArgument
38
+ def as_json(options = nil)
39
+ to_spec
40
+ end
41
+ # rubocop:enable Lint/UnusedMethodArgument
42
+
43
+ private
44
+
45
+ def valid_processed_value?(processed_value)
46
+ ((processed_value.is_a?(Hash) || processed_value.is_a?(Array)) && processed_value.empty?) || processed_value.nil?
47
+ end
48
+
49
+ def snake_to_camel(snake_str)
50
+ words = snake_str.to_s.split('_')
51
+ words[1..].map!(&:capitalize)
52
+ (words[0] + words[1..].join).to_sym
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+
5
+ module OasCore
6
+ module Spec
7
+ class Specification
8
+ include Specable
9
+ attr_accessor :components, :info, :openapi, :servers, :tags, :external_docs, :paths
10
+
11
+ def initialize
12
+ @components = Components.new(self)
13
+ @info = OasCore.config.info
14
+ @openapi = '3.1.0'
15
+ @servers = OasCore.config.servers
16
+ @tags = OasCore.config.tags
17
+ @external_docs = {}
18
+ @paths = Spec::Paths.new(self)
19
+ end
20
+
21
+ def oas_fields
22
+ %i[openapi info servers paths components security tags external_docs]
23
+ end
24
+
25
+ # Create the Security Requirement Object.
26
+ # @see https://spec.openapis.org/oas/latest.html#security-requirement-object
27
+ def security
28
+ return [] unless OasCore.config.authenticate_all_routes_by_default
29
+
30
+ OasCore.config.security_schemas.map { |key, _| { key => [] } }
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OasCore
4
+ module Spec
5
+ class Tag
6
+ include Specable
7
+
8
+ attr_accessor :name, :description
9
+
10
+ def initialize(name:, description:)
11
+ @name = name.titleize
12
+ @description = description
13
+ end
14
+
15
+ def oas_fields
16
+ %i[name description]
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ class String
4
+ def squish
5
+ dup.squish!
6
+ end
7
+
8
+ def squish!
9
+ gsub!(/[[:space:]]+/, ' ')
10
+ strip!
11
+ self
12
+ end
13
+ end
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OasCore
4
+ module Utils
5
+ TYPE_MAPPING = {
6
+ 'String' => 'string',
7
+ 'Integer' => 'number',
8
+ 'Float' => 'number',
9
+ 'TrueClass' => 'boolean',
10
+ 'FalseClass' => 'boolean',
11
+ 'Boolean' => 'boolean',
12
+ 'NilClass' => 'null',
13
+ 'Hash' => 'object',
14
+ 'Object' => 'object',
15
+ 'DateTime' => 'string'
16
+ }.freeze
17
+
18
+ STATUS_SYMBOL_MAPPING = {
19
+ ok: 200,
20
+ created: 201,
21
+ no_content: 204,
22
+ bad_request: 400,
23
+ unauthorized: 401,
24
+ forbidden: 403,
25
+ not_found: 404,
26
+ unprocessable_entity: 422,
27
+ internal_server_error: 500
28
+ }.freeze
29
+
30
+ HTTP_STATUS_DEFINITIONS = {
31
+ 200 => 'The request has succeeded.',
32
+ 201 => 'The request has been fulfilled and resulted in a new resource being created.',
33
+ 404 => 'The requested resource could not be found.',
34
+ 401 => 'You are not authorized to access this resource. You need to authenticate yourself first.',
35
+ 403 => 'You are not allowed to access this resource. You do not have the necessary permissions.',
36
+ 500 => 'An unexpected error occurred on the server. The server was unable to process the request.',
37
+ 422 => 'The server could not process the request due to semantic errors. Please check your input and try again.'
38
+ }.freeze
39
+
40
+ class << self
41
+ def hash_to_json_schema(hash)
42
+ {
43
+ type: 'object',
44
+ properties: hash_to_properties(hash),
45
+ required: []
46
+ }
47
+ end
48
+
49
+ def hash_to_properties(hash)
50
+ hash.transform_values do |value|
51
+ if value.is_a?(Hash)
52
+ hash_to_json_schema(value)
53
+ elsif value.is_a?(Class)
54
+ { type: ruby_type_to_json_type(value.name) }
55
+ else
56
+ { type: ruby_type_to_json_type(value.class.name) }
57
+ end
58
+ end
59
+ end
60
+
61
+ def ruby_type_to_json_type(ruby_type)
62
+ TYPE_MAPPING.fetch(ruby_type, 'string')
63
+ end
64
+
65
+ # Converts a status symbol or string to an integer.
66
+ #
67
+ # @param status [String, Symbol, nil] The status to convert.
68
+ # @return [Integer] The status code as an integer.
69
+ def status_to_integer(status)
70
+ return 200 if status.nil?
71
+
72
+ if status.to_s =~ /^\d+$/
73
+ status.to_i
74
+ else
75
+ STATUS_SYMBOL_MAPPING.fetch(status.to_sym) do
76
+ raise ArgumentError, "Unknown status symbol: #{status}"
77
+ end
78
+ end
79
+ end
80
+
81
+ # Converts a status code to its corresponding text description.
82
+ #
83
+ # @param status_code [Integer] The status code.
84
+ # @return [String] The text description of the status code.
85
+ def get_definition(status_code)
86
+ HTTP_STATUS_DEFINITIONS[status_code] || "Definition not found for status code #{status_code}"
87
+ end
88
+
89
+ def class_to_symbol(klass)
90
+ klass.name.underscore.to_sym
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OasCore
4
+ VERSION = '0.0.1'
5
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OasCore
4
+ module YARD
5
+ class ExampleTag < ::YARD::Tags::Tag
6
+ attr_accessor :content
7
+
8
+ def initialize(tag_name, text, content: {})
9
+ super(tag_name, text, nil, nil)
10
+ @content = content
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,166 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OasCore
4
+ module YARD
5
+ class OasCoreFactory < ::YARD::Tags::DefaultFactory
6
+ # Parses a tag that represents a request body.
7
+ # @param tag_name [String] The name of the tag.
8
+ # @param text [String] The tag text to parse.
9
+ # @return [RequestBodyTag] The parsed request body tag object.
10
+ def parse_tag_with_request_body(tag_name, text)
11
+ description, klass, schema, required = extract_description_and_schema(text.squish)
12
+ RequestBodyTag.new(tag_name, description, klass, schema:, required:)
13
+ end
14
+
15
+ # Parses a tag that represents a request body example.
16
+ # @param tag_name [String] The name of the tag.
17
+ # @param text [String] The tag text to parse.
18
+ # @return [RequestBodyExampleTag] The parsed request body example tag object.
19
+ def parse_tag_with_request_body_example(tag_name, text)
20
+ description, _, hash = extract_description_type_and_content(text.squish, process_content: true,
21
+ expresion: /^(.*?)\[([^\]]*)\](.*)$/m)
22
+ RequestBodyExampleTag.new(tag_name, description, content: hash)
23
+ end
24
+
25
+ # Parses a tag that represents a parameter.
26
+ # @param tag_name [String] The name of the tag.
27
+ # @param text [String] The tag text to parse.
28
+ # @return [ParameterTag] The parsed parameter tag object.
29
+ def parse_tag_with_parameter(tag_name, text)
30
+ name, location, schema, required, description = extract_name_location_schema_and_description(text.squish)
31
+ name = "#{name}[]" if location == 'query' && schema[:type] == 'array'
32
+ ParameterTag.new(tag_name, name, description, schema, location, required:)
33
+ end
34
+
35
+ # Parses a tag that represents a response.
36
+ # @param tag_name [String] The name of the tag.
37
+ # @param text [String] The tag text to parse.
38
+ # @return [ResponseTag] The parsed response tag object.
39
+ def parse_tag_with_response(tag_name, text)
40
+ name, code, schema = extract_name_code_and_schema(text.squish)
41
+ ResponseTag.new(tag_name, code, name, schema)
42
+ end
43
+
44
+ # Parses a tag that represents a response example.
45
+ # @param tag_name [String] The name of the tag.
46
+ # @param text [String] The tag text to parse.
47
+ # @return [ResponseExampleTag] The parsed response example tag object.
48
+ def parse_tag_with_response_example(tag_name, text)
49
+ description, code, hash = extract_name_code_and_hash(text.squish)
50
+ ResponseExampleTag.new(tag_name, description, content: hash, code:)
51
+ end
52
+
53
+ private
54
+
55
+ # Reusable method for extracting description, type, and content with an option to process content.
56
+ # @param text [String] The text to parse.
57
+ # @param process_content [Boolean] Whether to evaluate the content as a hash.
58
+ # @return [Array] An array containing the description, type, and content or remaining text.
59
+ def extract_description_type_and_content(text, process_content: false, expresion: /^(.*?)\s*\[(.*)\]\s*(.*)$/)
60
+ match = text.match(expresion)
61
+ raise ArgumentError, "Invalid tag format: #{text}" if match.nil?
62
+
63
+ description = match[1].strip
64
+ type = match[2].strip
65
+ content = process_content ? eval_content(match[3].strip) : match[3].strip
66
+
67
+ [description, type, content]
68
+ end
69
+
70
+ # Specific method to extract description and schema for request body tags.
71
+ # @param text [String] The text to parse.
72
+ # @return [Array] An array containing the description, class, schema, and required flag.
73
+ def extract_description_and_schema(text)
74
+ description, type, = extract_description_type_and_content(text)
75
+ klass, schema, required = type_text_to_schema(type)
76
+ [description, klass, schema, required]
77
+ end
78
+
79
+ # Specific method to extract name, location, and schema for parameters.
80
+ # @param text [String] The text to parse.
81
+ # @return [Array] An array containing the name, location, schema, and required flag.
82
+ def extract_name_location_schema_and_description(text)
83
+ match = text.match(/^(.*?)\s*\[(.*?)\]\s*(.*)$/)
84
+ name, location = extract_text_and_parentheses_content(match[1].strip)
85
+ schema, required = type_text_to_schema(match[2].strip)[1..]
86
+ description = match[3].strip
87
+ [name, location, schema, required, description]
88
+ end
89
+
90
+ # Specific method to extract name, code, and schema for responses.
91
+ # @param text [String] The text to parse.
92
+ # @return [Array] An array containing the name, code, and schema.
93
+ def extract_name_code_and_schema(text)
94
+ name, code = extract_text_and_parentheses_content(text)
95
+ _, type, = extract_description_type_and_content(text)
96
+ schema = type_text_to_schema(type)[1]
97
+ [name, code, schema]
98
+ end
99
+
100
+ # Specific method to extract name, code, and hash for responses examples.
101
+ # @param text [String] The text to parse.
102
+ # @return [Array] An array containing the name, code, and schema.
103
+ def extract_name_code_and_hash(text)
104
+ name, code = extract_text_and_parentheses_content(text)
105
+ _, _, content = extract_description_type_and_content(text, expresion: /^(.*?)\[([^\]]*)\](.*)$/m)
106
+ hash = eval_content(content)
107
+ [name, code, hash]
108
+ end
109
+
110
+ # Evaluates a string as a hash, handling errors gracefully.
111
+ # @param content [String] The content string to evaluate.
112
+ # @return [Hash] The evaluated hash, or an empty hash if an error occurs.
113
+ # rubocop:disable Security/Eval
114
+ def eval_content(content)
115
+ eval(content)
116
+ rescue StandardError
117
+ {}
118
+ end
119
+ # rubocop:enable Security/Eval
120
+
121
+ # Parses the position name and location from input text.
122
+ # @param input [String] The input text to parse.
123
+ # @return [Array] An array containing the name and location.
124
+ def extract_text_and_parentheses_content(input)
125
+ return unless input =~ /^(.+?)\(([^)]+)\)/
126
+
127
+ text = ::Regexp.last_match(1).strip
128
+ parenthesis_content = ::Regexp.last_match(2).strip
129
+ [text, parenthesis_content]
130
+ end
131
+
132
+ # Extracts the text and whether it's required.
133
+ # @param text [String] The text to parse.
134
+ # @return [Array] An array containing the text and a required flag.
135
+ def text_and_required(text)
136
+ if text.start_with?('!')
137
+ [text.sub(/^!/, ''), true]
138
+ else
139
+ [text, false]
140
+ end
141
+ end
142
+
143
+ # Matches and validates a description and type from text.
144
+ # @param text [String] The text to parse.
145
+ # @return [MatchData] The match data from the regex.
146
+ def description_and_type(text)
147
+ match = text.match(/^(.*?)\s*\[(.*?)\]\s*(.*)$/)
148
+ raise ArgumentError, "The request body tag is not valid: #{text}" if match.nil?
149
+
150
+ match
151
+ end
152
+
153
+ # Converts type text to a schema, checking if it's an ActiveRecord class.
154
+ # @param text [String] The type text to convert.
155
+ # @return [Array] An array containing the class, schema, and required flag.
156
+ def type_text_to_schema(text)
157
+ type_text, required = text_and_required(text)
158
+
159
+ schema = JsonSchemaGenerator.process_string(type_text)[:json_schema]
160
+ klass = Object
161
+
162
+ [klass, schema, required]
163
+ end
164
+ end
165
+ end
166
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OasCore
4
+ module YARD
5
+ class ParameterTag < ::YARD::Tags::Tag
6
+ attr_accessor :schema, :required, :location
7
+
8
+ def initialize(tag_name, name, text, schema, location, required: false)
9
+ super(tag_name, text, nil, name)
10
+ @schema = schema
11
+ @required = required
12
+ @location = location
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OasCore
4
+ module YARD
5
+ class RequestBodyExampleTag < ExampleTag
6
+ attr_accessor :content
7
+
8
+ def initialize(tag_name, text, content: {})
9
+ super
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OasCore
4
+ module YARD
5
+ class RequestBodyTag < ::YARD::Tags::Tag
6
+ attr_accessor :klass, :schema, :required
7
+
8
+ def initialize(tag_name, text, klass, schema: {}, required: false)
9
+ # initialize(tag_name, text, types = nil, name = nil)
10
+ super(tag_name, text, nil, nil)
11
+ @klass = klass
12
+ @schema = schema
13
+ @required = required
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OasCore
4
+ module YARD
5
+ class ResponseExampleTag < ExampleTag
6
+ attr_accessor :code
7
+
8
+ def initialize(tag_name, text, content: {}, code: 200)
9
+ super(tag_name, text, content:)
10
+ @code = code
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OasCore
4
+ module YARD
5
+ class ResponseTag < ::YARD::Tags::Tag
6
+ attr_accessor :schema
7
+
8
+ # TODO: name == code. The name MUST be changed to code for better understanding
9
+ def initialize(tag_name, name, text, schema)
10
+ super(tag_name, text, nil, name)
11
+ @schema = schema
12
+ end
13
+ end
14
+ end
15
+ end
data/lib/oas_core.rb ADDED
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'yard'
4
+ require 'method_source'
5
+ require 'oas_core/string'
6
+
7
+ module OasCore
8
+ require 'oas_core/version'
9
+
10
+ autoload :Configuration, 'oas_core/configuration'
11
+ autoload :OasRoute, 'oas_core/oas_route'
12
+ autoload :Utils, 'oas_core/utils'
13
+ autoload :JsonSchemaGenerator, 'oas_core/json_schema_generator'
14
+
15
+ module Builders
16
+ autoload :OperationBuilder, 'oas_core/builders/operation_builder'
17
+ autoload :PathItemBuilder, 'oas_core/builders/path_item_builder'
18
+ autoload :ResponseBuilder, 'oas_core/builders/response_builder'
19
+ autoload :ResponsesBuilder, 'oas_core/builders/responses_builder'
20
+ autoload :ContentBuilder, 'oas_core/builders/content_builder'
21
+ autoload :ParametersBuilder, 'oas_core/builders/parameters_builder'
22
+ autoload :ParameterBuilder, 'oas_core/builders/parameter_builder'
23
+ autoload :RequestBodyBuilder, 'oas_core/builders/request_body_builder'
24
+ autoload :OasRouteBuilder, 'oas_core/builders/oas_route_builder'
25
+ autoload :SpecificationBuilder, 'oas_core/builders/specification_builder'
26
+ end
27
+
28
+ module Spec
29
+ autoload :Hashable, 'oas_core/spec/hashable'
30
+ autoload :Specable, 'oas_core/spec/specable'
31
+ autoload :Components, 'oas_core/spec/components'
32
+ autoload :Parameter, 'oas_core/spec/parameter'
33
+ autoload :License, 'oas_core/spec/license'
34
+ autoload :Response, 'oas_core/spec/response'
35
+ autoload :PathItem, 'oas_core/spec/path_item'
36
+ autoload :Operation, 'oas_core/spec/operation'
37
+ autoload :RequestBody, 'oas_core/spec/request_body'
38
+ autoload :Responses, 'oas_core/spec/responses'
39
+ autoload :MediaType, 'oas_core/spec/media_type'
40
+ autoload :Paths, 'oas_core/spec/paths'
41
+ autoload :Contact, 'oas_core/spec/contact'
42
+ autoload :Info, 'oas_core/spec/info'
43
+ autoload :Server, 'oas_core/spec/server'
44
+ autoload :Tag, 'oas_core/spec/tag'
45
+ autoload :Specification, 'oas_core/spec/specification'
46
+ autoload :Reference, 'oas_core/spec/reference'
47
+ end
48
+
49
+ module YARD
50
+ autoload :RequestBodyTag, 'oas_core/yard/request_body_tag'
51
+ autoload :ExampleTag, 'oas_core/yard/example_tag'
52
+ autoload :RequestBodyExampleTag, 'oas_core/yard/request_body_example_tag'
53
+ autoload :ParameterTag, 'oas_core/yard/parameter_tag'
54
+ autoload :ResponseTag, 'oas_core/yard/response_tag'
55
+ autoload :ResponseExampleTag, 'oas_core/yard/response_example_tag'
56
+ autoload :OasCoreFactory, 'oas_core/yard/oas_core_factory'
57
+ end
58
+
59
+ class << self
60
+ def configure
61
+ OasCore.configure_yard!
62
+ yield config
63
+ end
64
+
65
+ def config
66
+ @config ||= Configuration.new
67
+ end
68
+
69
+ def configure_yard!
70
+ ::YARD::Tags::Library.default_factory = YARD::OasCoreFactory
71
+ yard_tags = {
72
+ 'Request body' => %i[request_body with_request_body],
73
+ 'Request body Example' => %i[request_body_example with_request_body_example],
74
+ 'Parameter' => %i[parameter with_parameter],
75
+ 'Response' => %i[response with_response],
76
+ 'Response Example' => %i[response_example with_response_example],
77
+ 'Endpoint Tags' => [:tags],
78
+ 'Summary' => [:summary],
79
+ 'No Auth' => [:no_auth],
80
+ 'Auth methods' => %i[auth with_types],
81
+ 'OAS Include' => [:oas_include]
82
+ }
83
+ yard_tags.each do |tag_name, (method_name, handler)|
84
+ ::YARD::Tags::Library.define_tag(tag_name, method_name, handler)
85
+ end
86
+ end
87
+ end
88
+ end