swagger_docs_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.
@@ -0,0 +1,150 @@
1
+ # frozen_string_literal: true
2
+ # lib/swagger_docs_rails/schema_parser.rb
3
+ #
4
+ # Lê db/schema.rb e gera schemas OpenAPI 3.0 para cada tabela.
5
+ # Produz três schemas por tabela:
6
+ # - ModelName → schema completo (usado nas respostas)
7
+ # - ModelNameInput → campos editáveis (usado em POST/PATCH genéricos)
8
+ # - ModelNameList → wrapper paginado { data: [...], meta: {...} }
9
+
10
+ module SwaggerDocsRails
11
+ class SchemaParser
12
+ IGNORED_COLUMNS = %w[
13
+ id created_by updated_by deleted_at created_at updated_at
14
+ encrypted_password reset_password_token reset_password_sent_at
15
+ remember_created_at refresh_token refresh_token_expires_at
16
+ token_primeiro_acesso
17
+ ].freeze
18
+
19
+ COLUMN_TYPE_MAP = {
20
+ string: { type: "string" },
21
+ text: { type: "string" },
22
+ integer: { type: "integer", format: "int64" },
23
+ bigint: { type: "integer", format: "int64" },
24
+ float: { type: "number", format: "float" },
25
+ decimal: { type: "number", format: "double" },
26
+ boolean: { type: "boolean" },
27
+ date: { type: "string", format: "date" },
28
+ datetime: { type: "string", format: "date-time" },
29
+ jsonb: { type: "object" },
30
+ json: { type: "object" }
31
+ }.freeze
32
+
33
+ attr_reader :tables
34
+
35
+ def initialize(schema_path = nil)
36
+ @schema_path = schema_path || Rails.root.join("db", "schema.rb")
37
+ @tables = {}
38
+ parse!
39
+ end
40
+
41
+ # Retorna Hash com todos os schemas OpenAPI gerados
42
+ def to_openapi_schemas
43
+ schemas = {}
44
+ @tables.each do |table_name, columns|
45
+ model = table_to_model_name(table_name)
46
+ schemas[model] = build_full_schema(columns)
47
+ schemas["#{model}Input"] = build_input_schema(columns)
48
+ schemas["#{model}List"] = build_list_schema(model)
49
+ end
50
+ schemas
51
+ end
52
+
53
+ def model_name_for_table(table_name)
54
+ table_to_model_name(table_name)
55
+ end
56
+
57
+ private
58
+
59
+ def parse!
60
+ content = File.read(@schema_path)
61
+
62
+ content.scan(/create_table\s+"(\w+)".*?do\s+\|t\|(.*?)end/m) do |table_name, body|
63
+ columns = [{ name: "id", type: :bigint, nullable: false, default: nil }]
64
+
65
+ body.scan(/t\.(\w+)\s+"(\w+)"(.*?)$/) do |col_type, col_name, opts|
66
+ next if col_type == "index"
67
+ columns << {
68
+ name: col_name,
69
+ type: col_type.to_sym,
70
+ nullable: !opts.include?("null: false"),
71
+ default: extract_default(opts)
72
+ }
73
+ end
74
+
75
+ columns << { name: "created_at", type: :datetime, nullable: false, default: nil }
76
+ columns << { name: "updated_at", type: :datetime, nullable: false, default: nil }
77
+
78
+ @tables[table_name] = columns
79
+ end
80
+ end
81
+
82
+ def build_full_schema(columns)
83
+ props = {}
84
+ required = []
85
+ columns.each do |col|
86
+ prop = (COLUMN_TYPE_MAP[col[:type]] || { type: "string" }).dup
87
+ prop[:description] = col[:name].tr("_", " ").capitalize
88
+ prop[:default] = cast_default(col[:default], col[:type]) if col[:default]
89
+ props[col[:name]] = prop
90
+ required << col[:name] unless col[:nullable]
91
+ end
92
+ schema = { type: "object", properties: props }
93
+ schema[:required] = required unless required.empty?
94
+ schema
95
+ end
96
+
97
+ def build_input_schema(columns)
98
+ writable = columns.reject { |c| IGNORED_COLUMNS.include?(c[:name]) }
99
+ props = {}
100
+ required = []
101
+ writable.each do |col|
102
+ prop = (COLUMN_TYPE_MAP[col[:type]] || { type: "string" }).dup
103
+ prop[:description] = col[:name].tr("_", " ").capitalize
104
+ props[col[:name]] = prop
105
+ required << col[:name] if !col[:nullable] && col[:default].nil?
106
+ end
107
+ schema = { type: "object", properties: props }
108
+ schema[:required] = required unless required.empty?
109
+ schema
110
+ end
111
+
112
+ def build_list_schema(model)
113
+ {
114
+ type: "object",
115
+ properties: {
116
+ data: { type: "array", items: { "$ref" => "#/components/schemas/#{model}" } },
117
+ meta: {
118
+ type: "object",
119
+ properties: {
120
+ current_page: { type: "integer" },
121
+ total_pages: { type: "integer" },
122
+ total_count: { type: "integer" },
123
+ per_page: { type: "integer" }
124
+ }
125
+ }
126
+ }
127
+ }
128
+ end
129
+
130
+ def table_to_model_name(table_name)
131
+ singular = table_name.singularize rescue table_name.sub(/s$/, "")
132
+ parts = singular.split("_")
133
+ parts.first == "g" ? "G" + parts[1..].map(&:capitalize).join : parts.map(&:capitalize).join
134
+ end
135
+
136
+ def extract_default(opts)
137
+ m = opts.match(/default:\s*([^,\s]+)/)
138
+ m ? m[1] : nil
139
+ end
140
+
141
+ def cast_default(value, type)
142
+ case type
143
+ when :boolean then value == "true"
144
+ when :integer, :bigint then value.to_i
145
+ when :float, :decimal then value.to_f
146
+ else value.to_s.gsub(/\A["']|["']\z/, "")
147
+ end
148
+ end
149
+ end
150
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SwaggerDocsRails
4
+ VERSION = "0.1.0"
5
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "fileutils"
5
+ require "swagger_docs_rails/version"
6
+ require "swagger_docs_rails/configuration"
7
+ require "swagger_docs_rails/schema_parser"
8
+ require "swagger_docs_rails/controller_parser"
9
+ require "swagger_docs_rails/openapi_builder"
10
+ require "swagger_docs_rails/generator"
11
+ require "swagger_docs_rails/engine" if defined?(Rails::Engine)
12
+
13
+ module SwaggerDocsRails
14
+ class << self
15
+ attr_writer :configuration
16
+
17
+ def configuration
18
+ @configuration ||= Configuration.new
19
+ end
20
+
21
+ def configure
22
+ yield(configuration)
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ namespace :swagger do
4
+ desc "Gera a documentação OpenAPI 3.0 a partir do schema.rb e controllers"
5
+ task :generate, [:output] => :environment do |_task, args|
6
+ require "json"
7
+ require "swagger_docs_rails"
8
+
9
+ config = SwaggerDocsRails.configuration
10
+ output_path = args[:output] || ENV["SWAGGER_OUTPUT"] || config.output_path
11
+
12
+ app_name = begin
13
+ Rails.application.class.module_parent_name
14
+ rescue StandardError
15
+ "API"
16
+ end
17
+
18
+ dados_configuracao = {
19
+ title: ENV.fetch("SWAGGER_TITLE", config.title || app_name),
20
+ version: ENV.fetch("SWAGGER_VERSION", config.version || "1.0.0"),
21
+ description: ENV.fetch("SWAGGER_DESC", config.description || "Documentação OpenAPI gerada automaticamente"),
22
+ server_url: ENV.fetch("SWAGGER_SERVER", config.server_url || "/")
23
+ }
24
+
25
+ puts "→ Parsing db/schema.rb..."
26
+ schema_parser = SwaggerDocsRails::SchemaParser.new
27
+ puts " #{schema_parser.tables.size} tabelas encontradas"
28
+ schema_parser.tables.each_key { |tabela| puts " • #{tabela}" }
29
+
30
+ puts "\n→ Scanning app/controllers/api/..."
31
+ caminhos = [Rails.root.join("app", "controllers", "api"), *Array(config.additional_controller_paths)]
32
+ controller_parser = SwaggerDocsRails::ControllerParser.new(caminhos)
33
+ puts " #{controller_parser.to_resources.size} controllers encontradas"
34
+ controller_parser.to_resources.each do |recurso|
35
+ crud_label = recurso[:actions].any? ? "CRUD[#{recurso[:actions].join(",")}]" : "(sem ações CRUD)"
36
+ custom_label = recurso[:custom_actions].any? ? " + custom[#{recurso[:custom_actions].map { |acao| acao[:name] }.join(",")}]" : ""
37
+ puts " • #{recurso[:controller_class]} #{crud_label}#{custom_label}"
38
+ end
39
+
40
+ puts "\n→ Building OpenAPI document..."
41
+ documento = SwaggerDocsRails::OpenapiBuilder.new(schema_parser, controller_parser, dados_configuracao).build
42
+
43
+ puts " #{documento[:paths].size} paths"
44
+ puts " #{documento.dig(:components, :schemas)&.size || 0} schemas"
45
+
46
+ puts "\n→ Writing to #{output_path}..."
47
+ FileUtils.mkdir_p(File.dirname(output_path.to_s))
48
+ File.write(output_path.to_s, JSON.pretty_generate(documento))
49
+
50
+ tamanho_kb = (File.size(output_path.to_s) / 1024.0).round(1)
51
+ puts "\n✓ Concluído! #{output_path} (#{tamanho_kb} KB)"
52
+ end
53
+
54
+ desc "Valida o documento OpenAPI gerado (requer gem openapi_parser)"
55
+ task validate: :environment do
56
+ require "json"
57
+ require "openapi_parser"
58
+
59
+ path = SwaggerDocsRails.configuration.output_path
60
+
61
+ unless File.exist?(path)
62
+ puts "✗ Arquivo não encontrado. Rode `rails swagger:generate` primeiro."
63
+ exit 1
64
+ end
65
+
66
+ OpenAPIParser.parse(JSON.parse(File.read(path)), strict_reference_validation: true)
67
+ puts "✓ Documento OpenAPI válido (#{path})"
68
+ rescue OpenAPIParser::OpenAPIError => e
69
+ puts "✗ Validação falhou: #{e.message}"
70
+ exit 1
71
+ end
72
+ end
metadata ADDED
@@ -0,0 +1,81 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: swagger_docs_rails
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Clic API
8
+ bindir: exe
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: rails
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '7.0'
19
+ - - "<"
20
+ - !ruby/object:Gem::Version
21
+ version: '9.0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ version: '7.0'
29
+ - - "<"
30
+ - !ruby/object:Gem::Version
31
+ version: '9.0'
32
+ description: Rails Engine para gerar documentação OpenAPI 3.0 a partir do schema.rb
33
+ e controllers Rails.
34
+ email:
35
+ - dev@clic.com.br
36
+ executables: []
37
+ extensions: []
38
+ extra_rdoc_files: []
39
+ files:
40
+ - CHANGELOG.md
41
+ - MIT-LICENSE
42
+ - README.md
43
+ - app/controllers/swagger_docs_rails/swagger_controller.rb
44
+ - config/routes.rb
45
+ - lib/generators/swagger_docs_rails/install/install_generator.rb
46
+ - lib/generators/swagger_docs_rails/install/templates/swagger_docs_rails.rb
47
+ - lib/swagger_docs_rails.rb
48
+ - lib/swagger_docs_rails/configuration.rb
49
+ - lib/swagger_docs_rails/controller_parser.rb
50
+ - lib/swagger_docs_rails/engine.rb
51
+ - lib/swagger_docs_rails/generator.rb
52
+ - lib/swagger_docs_rails/openapi_builder.rb
53
+ - lib/swagger_docs_rails/schema_parser.rb
54
+ - lib/swagger_docs_rails/version.rb
55
+ - lib/tasks/swagger_docs_rails.rake
56
+ homepage: https://rubygems.org/gems/swagger_docs_rails
57
+ licenses:
58
+ - MIT
59
+ metadata:
60
+ allowed_push_host: https://rubygems.org
61
+ homepage_uri: https://rubygems.org/gems/swagger_docs_rails
62
+ source_code_uri: https://rubygems.org/gems/swagger_docs_rails
63
+ changelog_uri: https://rubygems.org/gems/swagger_docs_rails/-/blob/main/CHANGELOG.md
64
+ rdoc_options: []
65
+ require_paths:
66
+ - lib
67
+ required_ruby_version: !ruby/object:Gem::Requirement
68
+ requirements:
69
+ - - ">="
70
+ - !ruby/object:Gem::Version
71
+ version: '3.2'
72
+ required_rubygems_version: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ requirements: []
78
+ rubygems_version: 3.6.9
79
+ specification_version: 4
80
+ summary: Gerador OpenAPI e Swagger UI para APIs Rails.
81
+ test_files: []