oas_rails 0.1.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.
Files changed (43) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +147 -0
  4. data/Rakefile +8 -0
  5. data/app/assets/config/oas_rails_manifest.js +1 -0
  6. data/app/assets/stylesheets/oas_rails/application.css +15 -0
  7. data/app/controllers/oas_rails/application_controller.rb +4 -0
  8. data/app/controllers/oas_rails/oas_rails_controller.rb +9 -0
  9. data/app/helpers/oas_rails/application_helper.rb +4 -0
  10. data/app/helpers/oas_rails/oas_rails_helper.rb +4 -0
  11. data/app/helpers/oas_rails/test_helper.rb +4 -0
  12. data/app/jobs/oas_rails/application_job.rb +4 -0
  13. data/app/mailers/oas_rails/application_mailer.rb +6 -0
  14. data/app/models/oas_rails/application_record.rb +5 -0
  15. data/app/views/layouts/oas_rails/application.html.erb +18 -0
  16. data/app/views/oas_rails/oas_rails/index.html.erb +1 -0
  17. data/app/views/oas_rails/test/show.html.erb +1 -0
  18. data/config/routes.rb +4 -0
  19. data/lib/generators/oas_rails/config/config_generator.rb +11 -0
  20. data/lib/generators/oas_rails/config/templates/oas_rails_initializer.rb +49 -0
  21. data/lib/oas_rails/configuration.rb +28 -0
  22. data/lib/oas_rails/contact.rb +12 -0
  23. data/lib/oas_rails/engine.rb +5 -0
  24. data/lib/oas_rails/info.rb +60 -0
  25. data/lib/oas_rails/license.rb +11 -0
  26. data/lib/oas_rails/media_type.rb +57 -0
  27. data/lib/oas_rails/oas_base.rb +29 -0
  28. data/lib/oas_rails/oas_route.rb +143 -0
  29. data/lib/oas_rails/operation.rb +118 -0
  30. data/lib/oas_rails/parameter.rb +47 -0
  31. data/lib/oas_rails/path_item.rb +25 -0
  32. data/lib/oas_rails/paths.rb +19 -0
  33. data/lib/oas_rails/request_body.rb +29 -0
  34. data/lib/oas_rails/response.rb +12 -0
  35. data/lib/oas_rails/responses.rb +20 -0
  36. data/lib/oas_rails/route_extractor.rb +87 -0
  37. data/lib/oas_rails/server.rb +10 -0
  38. data/lib/oas_rails/specification.rb +42 -0
  39. data/lib/oas_rails/tag.rb +17 -0
  40. data/lib/oas_rails/version.rb +3 -0
  41. data/lib/oas_rails/yard/oas_yard_factory.rb +160 -0
  42. data/lib/oas_rails.rb +120 -0
  43. metadata +159 -0
@@ -0,0 +1,160 @@
1
+ module OasRails
2
+ module Yard
3
+ class RequestBodyTag < ::YARD::Tags::Tag
4
+ attr_accessor :klass, :schema, :required
5
+
6
+ def initialize(tag_name, text, klass, schema: {}, required: false)
7
+ # initialize(tag_name, text, types = nil, name = nil)
8
+ super(tag_name, text, nil, nil)
9
+ @klass = klass
10
+ @schema = schema
11
+ @required = required
12
+ end
13
+ end
14
+
15
+ class RequestBodyExampleTag < ::YARD::Tags::Tag
16
+ attr_accessor :content
17
+
18
+ def initialize(tag_name, text, content: {})
19
+ super(tag_name, text, nil, nil)
20
+ @content = content
21
+ end
22
+ end
23
+
24
+ class ParameterTag < ::YARD::Tags::Tag
25
+ attr_accessor :schema, :required, :location
26
+
27
+ def initialize(tag_name, name, text, schema, location, required: false)
28
+ super(tag_name, text, nil, name)
29
+ @schema = schema
30
+ @required = required
31
+ @location = location
32
+ end
33
+ end
34
+
35
+ class ResponseTag < ::YARD::Tags::Tag
36
+ attr_accessor :schema
37
+
38
+ def initialize(tag_name, name, text, schema)
39
+ super(tag_name, text, nil, name)
40
+ @schema = schema
41
+ end
42
+ end
43
+
44
+ class OasYardFactory < ::YARD::Tags::DefaultFactory
45
+ ## parse_tag is a prefix used by YARD
46
+
47
+ def parse_tag_with_request_body(tag_name, text)
48
+ match = text.match(/^(.*?)\s*\[(.*?)\]\s*(.*)$/)
49
+ if !match.nil?
50
+ text = match[1].strip
51
+ type, required = parse_type(match[2].strip)
52
+
53
+ if type.constantize == Hash
54
+ hash_string = match[3].strip
55
+
56
+ begin
57
+ hash = eval(hash_string)
58
+ hash = OasRails.hash_to_json_schema(hash)
59
+ rescue StandardError
60
+ hash = {}
61
+ end
62
+ end
63
+
64
+ RequestBodyTag.new(tag_name, text, type.constantize, schema: hash, required:)
65
+ else
66
+ Rails.logger.debug("Error parsing request_body tag: #{text}")
67
+ end
68
+ end
69
+
70
+ def parse_tag_with_request_body_example(tag_name, text)
71
+ match = text.match(/^(.*?)\s*\[(.*?)\]\s*(.*)$/)
72
+ if !match.nil?
73
+ text = match[1].strip
74
+ type = match[2]
75
+
76
+ if type.constantize == Hash
77
+ hash_string = match[3].strip
78
+
79
+ begin
80
+ hash = eval(hash_string)
81
+ rescue StandardError
82
+ hash = {}
83
+ end
84
+ end
85
+ RequestBodyExampleTag.new(tag_name, text, content: hash)
86
+ else
87
+ Rails.logger.debug("Error parsing request body example tag: #{text}")
88
+ end
89
+ end
90
+
91
+ def parse_tag_with_parameter(tag_name, text)
92
+ match = text.match(/^(.*?)\s*\[(.*?)\]\s*(.*)$/)
93
+ if !match.nil?
94
+ text = match[1].strip
95
+ name, location = parse_position_name(text)
96
+ type, required = parse_type(match[2].strip)
97
+ schema = OasRails.type_to_schema(type)
98
+
99
+ ParameterTag.new(tag_name, name, match[3].strip, schema, location, required:)
100
+ else
101
+ Rails.logger.debug("Error parsing request body example tag: #{text}")
102
+ end
103
+ end
104
+
105
+ def parse_tag_with_response(tag_name, text)
106
+ match = text.match(/^(.*?)\s*\[(.*?)\]\s*(.*)$/)
107
+ if !match.nil?
108
+ name, code = parse_position_name(match[1].strip)
109
+
110
+ begin
111
+ hash = parse_str_to_hash(match[3].strip)
112
+ hash = OasRails.hash_to_json_schema(hash)
113
+ rescue StandardError
114
+ hash = {}
115
+ end
116
+
117
+ ResponseTag.new(tag_name, code, name, hash)
118
+ else
119
+ Rails.logger.debug("Error parsing request body example tag: #{text}")
120
+ end
121
+ end
122
+
123
+ def parse_position_name(input)
124
+ return unless input =~ /^([^(]+)\((.*)\)$/
125
+
126
+ name = ::Regexp.last_match(1)
127
+ location = ::Regexp.last_match(2)
128
+ [name, location]
129
+ end
130
+
131
+ def parse_type(type_string)
132
+ if type_string.end_with?('!')
133
+ [type_string.chomp('!'), true]
134
+ else
135
+ [type_string, false]
136
+ end
137
+ end
138
+
139
+ def parse_str_to_hash(str)
140
+ str = str.gsub(/^\{|\}$/, '') # Remove leading { and trailing }
141
+ pairs = str.split(',').map(&:strip)
142
+
143
+ pairs.each_with_object({}) do |pair, hash|
144
+ key, value = pair.split(':').map(&:strip)
145
+ key = key.to_sym
146
+ hash[key] = case value
147
+ when 'String' then String
148
+ when 'Integer' then Integer
149
+ when 'Float' then Float
150
+ when 'Boolean' then [TrueClass, FalseClass]
151
+ when 'Array' then Array
152
+ when 'Hash' then Hash
153
+ when 'DateTime' then DateTime
154
+ else value
155
+ end
156
+ end
157
+ end
158
+ end
159
+ end
160
+ end
data/lib/oas_rails.rb ADDED
@@ -0,0 +1,120 @@
1
+ require "yard"
2
+ require "method_source"
3
+ require "esquema"
4
+
5
+ require_relative 'oas_rails/version'
6
+ require_relative 'oas_rails/engine'
7
+ require_relative 'oas_rails/oas_base'
8
+ require_relative 'oas_rails/configuration'
9
+ require_relative 'oas_rails/specification'
10
+ require_relative 'oas_rails/route_extractor'
11
+ require_relative 'oas_rails/oas_route'
12
+ require_relative 'oas_rails/operation'
13
+
14
+ require_relative 'oas_rails/info'
15
+ require_relative 'oas_rails/contact'
16
+ require_relative 'oas_rails/paths'
17
+ require_relative 'oas_rails/path_item'
18
+ require_relative 'oas_rails/parameter'
19
+ require_relative 'oas_rails/tag'
20
+ require_relative 'oas_rails/license'
21
+ require_relative 'oas_rails/server'
22
+ require_relative "oas_rails/request_body"
23
+ require_relative "oas_rails/media_type"
24
+ require_relative 'oas_rails/yard/oas_yard_factory'
25
+ require_relative "oas_rails/response"
26
+ require_relative "oas_rails/responses"
27
+
28
+ module OasRails
29
+ class << self
30
+ def configure
31
+ yield config
32
+ end
33
+
34
+ def config
35
+ @config ||= Configuration.new
36
+ end
37
+
38
+ def configure_yard!
39
+ ::YARD::Tags::Library.default_factory = Yard::OasYardFactory
40
+ yard_tags = {
41
+ 'Request body' => [:request_body, :with_request_body],
42
+ 'Request body Example' => [:request_body_example, :with_request_body_example],
43
+ 'Parameter' => [:parameter, :with_parameter],
44
+ 'Response' => [:response, :with_response],
45
+ 'Endpoint Tags' => [:tags],
46
+ 'Summary' => [:summary]
47
+ }
48
+ yard_tags.each do |tag_name, (method_name, handler)|
49
+ ::YARD::Tags::Library.define_tag(tag_name, method_name, handler)
50
+ end
51
+ end
52
+
53
+ def configure_esquema!
54
+ Esquema.configure do |config|
55
+ config.exclude_associations = true
56
+ config.exclude_foreign_keys = true
57
+ config.excluded_columns = %i[id created_at updated_at deleted_at]
58
+ end
59
+ end
60
+
61
+ def detect_test_framework
62
+ if defined?(FactoryBot)
63
+ :factory_bot
64
+ elsif ActiveRecord::Base.connection.table_exists?('ar_internal_metadata')
65
+ :fixtures
66
+ else
67
+ :unknown
68
+ end
69
+ end
70
+
71
+ TYPE_MAPPING = {
72
+ 'String' => 'string',
73
+ 'Integer' => 'number',
74
+ 'Float' => 'number',
75
+ 'TrueClass' => 'boolean',
76
+ 'FalseClass' => 'boolean',
77
+ 'Boolean' => 'boolean',
78
+ 'NilClass' => 'null',
79
+ 'Hash' => 'object',
80
+ 'Object' => 'object',
81
+ 'DateTime' => 'string'
82
+ }.freeze
83
+
84
+ def type_to_schema(type_string)
85
+ if type_string.start_with?('Array<')
86
+ inner_type = type_string[/Array<(.+)>$/, 1]
87
+ {
88
+ "type" => "array",
89
+ "items" => type_to_schema(inner_type)
90
+ }
91
+ else
92
+ { "type" => TYPE_MAPPING.fetch(type_string, 'string') }
93
+ end
94
+ end
95
+
96
+ def hash_to_json_schema(hash)
97
+ {
98
+ type: 'object',
99
+ properties: hash_to_properties(hash),
100
+ required: []
101
+ }
102
+ end
103
+
104
+ def hash_to_properties(hash)
105
+ hash.transform_values do |value|
106
+ if value.is_a?(Hash)
107
+ hash_to_json_schema(value)
108
+ elsif value.is_a?(Class)
109
+ { type: ruby_type_to_json_type(value.name) }
110
+ else
111
+ { type: ruby_type_to_json_type(value.class.name) }
112
+ end
113
+ end
114
+ end
115
+
116
+ def ruby_type_to_json_type(ruby_type)
117
+ TYPE_MAPPING.fetch(ruby_type, 'string')
118
+ end
119
+ end
120
+ end
metadata ADDED
@@ -0,0 +1,159 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: oas_rails
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - a-chacon
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2024-07-27 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: esquema
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 0.1.2
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 0.1.2
27
+ - !ruby/object:Gem::Dependency
28
+ name: method_source
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rails
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '7.0'
48
+ - - ">="
49
+ - !ruby/object:Gem::Version
50
+ version: 7.0.0
51
+ type: :runtime
52
+ prerelease: false
53
+ version_requirements: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - "~>"
56
+ - !ruby/object:Gem::Version
57
+ version: '7.0'
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: 7.0.0
61
+ - !ruby/object:Gem::Dependency
62
+ name: yard
63
+ requirement: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '0.9'
68
+ type: :runtime
69
+ prerelease: false
70
+ version_requirements: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: '0.9'
75
+ description: |2+
76
+ # Open API Specification For Rails
77
+
78
+ OasRails is a Rails engine for generating **automatic interactive documentation for your Rails APIs**. It generates an **OAS 3.1** document and displays it using **[RapiDoc](https://rapidocweb.com)**.
79
+
80
+ ## What Sets OasRails Apart?
81
+
82
+ - **Dynamic**: No command required to generate docs
83
+ - **Simple**: Complement default documentation with a few comments; no need to learn a complex DSL
84
+ - **Pure Ruby on Rails APIs**: No additional frameworks needed (e.g., Grape, RSpec)
85
+
86
+ email:
87
+ - andres.ch@protonmail.com
88
+ executables: []
89
+ extensions: []
90
+ extra_rdoc_files: []
91
+ files:
92
+ - MIT-LICENSE
93
+ - README.md
94
+ - Rakefile
95
+ - app/assets/config/oas_rails_manifest.js
96
+ - app/assets/stylesheets/oas_rails/application.css
97
+ - app/controllers/oas_rails/application_controller.rb
98
+ - app/controllers/oas_rails/oas_rails_controller.rb
99
+ - app/helpers/oas_rails/application_helper.rb
100
+ - app/helpers/oas_rails/oas_rails_helper.rb
101
+ - app/helpers/oas_rails/test_helper.rb
102
+ - app/jobs/oas_rails/application_job.rb
103
+ - app/mailers/oas_rails/application_mailer.rb
104
+ - app/models/oas_rails/application_record.rb
105
+ - app/views/layouts/oas_rails/application.html.erb
106
+ - app/views/oas_rails/oas_rails/index.html.erb
107
+ - app/views/oas_rails/test/show.html.erb
108
+ - config/routes.rb
109
+ - lib/generators/oas_rails/config/config_generator.rb
110
+ - lib/generators/oas_rails/config/templates/oas_rails_initializer.rb
111
+ - lib/oas_rails.rb
112
+ - lib/oas_rails/configuration.rb
113
+ - lib/oas_rails/contact.rb
114
+ - lib/oas_rails/engine.rb
115
+ - lib/oas_rails/info.rb
116
+ - lib/oas_rails/license.rb
117
+ - lib/oas_rails/media_type.rb
118
+ - lib/oas_rails/oas_base.rb
119
+ - lib/oas_rails/oas_route.rb
120
+ - lib/oas_rails/operation.rb
121
+ - lib/oas_rails/parameter.rb
122
+ - lib/oas_rails/path_item.rb
123
+ - lib/oas_rails/paths.rb
124
+ - lib/oas_rails/request_body.rb
125
+ - lib/oas_rails/response.rb
126
+ - lib/oas_rails/responses.rb
127
+ - lib/oas_rails/route_extractor.rb
128
+ - lib/oas_rails/server.rb
129
+ - lib/oas_rails/specification.rb
130
+ - lib/oas_rails/tag.rb
131
+ - lib/oas_rails/version.rb
132
+ - lib/oas_rails/yard/oas_yard_factory.rb
133
+ homepage: https://github.com/a-chacon/oas_rails
134
+ licenses:
135
+ - GPL-3.0-only
136
+ metadata:
137
+ homepage_uri: https://github.com/a-chacon/oas_rails
138
+ post_install_message:
139
+ rdoc_options: []
140
+ require_paths:
141
+ - lib
142
+ required_ruby_version: !ruby/object:Gem::Requirement
143
+ requirements:
144
+ - - ">="
145
+ - !ruby/object:Gem::Version
146
+ version: '3.0'
147
+ required_rubygems_version: !ruby/object:Gem::Requirement
148
+ requirements:
149
+ - - ">="
150
+ - !ruby/object:Gem::Version
151
+ version: '0'
152
+ requirements: []
153
+ rubygems_version: 3.5.15
154
+ signing_key:
155
+ specification_version: 4
156
+ summary: OasRails is a Rails engine for generating automatic interactive documentation
157
+ for your Rails APIs. It generates an OAS document and displays it using a nice UI.
158
+ test_files: []
159
+ ...