rails-autodoc 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 (81) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/ci.yml +63 -0
  3. data/.gitignore +16 -0
  4. data/.rspec +2 -0
  5. data/.rubocop.yml +81 -0
  6. data/.yardopts +3 -0
  7. data/Appraisals +26 -0
  8. data/CHANGELOG.md +20 -0
  9. data/CONTRIBUTING.md +54 -0
  10. data/Gemfile +10 -0
  11. data/Gemfile.lock +298 -0
  12. data/LICENSE.txt +21 -0
  13. data/README.md +111 -0
  14. data/Rakefile +26 -0
  15. data/app/controllers/rails_autodoc/spec_controller.rb +52 -0
  16. data/config/routes.rb +6 -0
  17. data/docs/annotation-dsl.md +56 -0
  18. data/docs/architecture.md +37 -0
  19. data/docs/ci-integration.md +32 -0
  20. data/docs/configuration.md +48 -0
  21. data/docs/faq.md +29 -0
  22. data/docs/getting-started.md +54 -0
  23. data/docs/index.md +21 -0
  24. data/docs/inference-rules.md +74 -0
  25. data/docs/limitations.md +34 -0
  26. data/docs/migration-from-rswag.md +42 -0
  27. data/docs/serializer-support.md +25 -0
  28. data/lib/generators/rails_autodoc/install_generator.rb +34 -0
  29. data/lib/generators/rails_autodoc/templates/autodoc-verify.yml +19 -0
  30. data/lib/generators/rails_autodoc/templates/initializer.rb +20 -0
  31. data/lib/rails_autodoc/ast_traversal.rb +74 -0
  32. data/lib/rails_autodoc/configuration.rb +45 -0
  33. data/lib/rails_autodoc/dsl/controller_extensions.rb +65 -0
  34. data/lib/rails_autodoc/engine.rb +13 -0
  35. data/lib/rails_autodoc/generator.rb +54 -0
  36. data/lib/rails_autodoc/openapi_spec_builder.rb +334 -0
  37. data/lib/rails_autodoc/railtie.rb +25 -0
  38. data/lib/rails_autodoc/registry.rb +71 -0
  39. data/lib/rails_autodoc/response_inferencer.rb +158 -0
  40. data/lib/rails_autodoc/route_inspector.rb +139 -0
  41. data/lib/rails_autodoc/schema_mapper.rb +142 -0
  42. data/lib/rails_autodoc/serializers/active_model_serializer.rb +27 -0
  43. data/lib/rails_autodoc/serializers/alba.rb +39 -0
  44. data/lib/rails_autodoc/serializers/base.rb +19 -0
  45. data/lib/rails_autodoc/serializers/blueprinter.rb +27 -0
  46. data/lib/rails_autodoc/serializers/registry.rb +29 -0
  47. data/lib/rails_autodoc/strong_params_parser.rb +188 -0
  48. data/lib/rails_autodoc/tasks/autodoc.rake +26 -0
  49. data/lib/rails_autodoc/version.rb +5 -0
  50. data/lib/rails_autodoc.rb +47 -0
  51. data/mkdocs.yml +16 -0
  52. data/rails-autodoc.gemspec +61 -0
  53. data/spec/combustion/config.ru +4 -0
  54. data/spec/dummy/app/assets/config/manifest.js +1 -0
  55. data/spec/dummy/app/controllers/api/v1/users_controller.rb +45 -0
  56. data/spec/dummy/app/models/user.rb +5 -0
  57. data/spec/dummy/config/application.rb +14 -0
  58. data/spec/dummy/config/boot.rb +5 -0
  59. data/spec/dummy/config/database.yml +3 -0
  60. data/spec/dummy/config/environment.rb +5 -0
  61. data/spec/dummy/config/environments/test.rb +12 -0
  62. data/spec/dummy/config/initializers/rails_autodoc.rb +8 -0
  63. data/spec/dummy/config/initializers/sqlite3_boolean.rb +8 -0
  64. data/spec/dummy/config/routes.rb +11 -0
  65. data/spec/dummy/db/migrate/001_create_users.rb +14 -0
  66. data/spec/dummy/db/schema.rb +12 -0
  67. data/spec/rails_autodoc/configuration_spec.rb +34 -0
  68. data/spec/rails_autodoc/dsl_integration_spec.rb +77 -0
  69. data/spec/rails_autodoc/engine_spec.rb +26 -0
  70. data/spec/rails_autodoc/gem_spec.rb +27 -0
  71. data/spec/rails_autodoc/generator_spec.rb +39 -0
  72. data/spec/rails_autodoc/golden_spec.rb +67 -0
  73. data/spec/rails_autodoc/integration_spec.rb +114 -0
  74. data/spec/rails_autodoc/registry_spec.rb +26 -0
  75. data/spec/rails_autodoc/response_inferencer_spec.rb +26 -0
  76. data/spec/rails_autodoc/route_inspector_spec.rb +56 -0
  77. data/spec/rails_autodoc/schema_mapper_spec.rb +42 -0
  78. data/spec/rails_autodoc/serializers/registry_spec.rb +33 -0
  79. data/spec/rails_autodoc/strong_params_parser_spec.rb +41 -0
  80. data/spec/spec_helper.rb +43 -0
  81. metadata +320 -0
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsAutodoc
4
+ class OperationAnnotation
5
+ attr_accessor :controller,
6
+ :action,
7
+ :summary,
8
+ :description,
9
+ :tags,
10
+ :deprecated,
11
+ :body_params,
12
+ :query_params,
13
+ :responses,
14
+ :security,
15
+ :request_body_schema,
16
+ :exclude
17
+
18
+ def initialize(controller:, action:)
19
+ @controller = controller
20
+ @action = action
21
+ @summary = nil
22
+ @description = nil
23
+ @tags = []
24
+ @deprecated = false
25
+ @body_params = []
26
+ @query_params = []
27
+ @responses = {}
28
+ @security = nil
29
+ @request_body_schema = nil
30
+ @exclude = false
31
+ end
32
+
33
+ def operation_id
34
+ "#{controller.name.underscore.tr('/', '_')}_#{action}"
35
+ end
36
+ end
37
+
38
+ class Registry
39
+ def initialize
40
+ @annotations = {}
41
+ end
42
+
43
+ def register(controller, action, &block)
44
+ key = annotation_key(controller, action)
45
+ annotation = (@annotations[key] ||= OperationAnnotation.new(
46
+ controller: controller,
47
+ action: action
48
+ ))
49
+ DSL::ControllerExtensions::AnnotationBuilder.new(annotation).instance_eval(&block)
50
+ annotation
51
+ end
52
+
53
+ def find(controller, action)
54
+ @annotations[annotation_key(controller, action)]
55
+ end
56
+
57
+ def all
58
+ @annotations.values
59
+ end
60
+
61
+ def clear!
62
+ @annotations.clear
63
+ end
64
+
65
+ private
66
+
67
+ def annotation_key(controller, action)
68
+ "#{controller.name}##{action}"
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,158 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "parser/current"
4
+
5
+ module RailsAutodoc
6
+ class ResponseInferencer
7
+ include AstTraversal
8
+
9
+ ResponseHint = Struct.new(:status, :schema_ref, :schema, :description, keyword_init: true)
10
+
11
+ DEFAULT_STATUS = {
12
+ "GET" => "200",
13
+ "POST" => "201",
14
+ "PUT" => "200",
15
+ "PATCH" => "200",
16
+ "DELETE" => "204"
17
+ }.freeze
18
+
19
+ def initialize(source_path:, class_name:, serializer_registry: Serializers::Registry.new)
20
+ @source_path = source_path
21
+ @class_name = class_name
22
+ @serializer_registry = serializer_registry
23
+ @buffer, = Parser::CurrentRuby.parse_file(source_path)
24
+ end
25
+
26
+ def responses_for_action(action_name, verb: "GET")
27
+ action_node = find_action_node(action_name)
28
+ hints = action_node ? extract_render_hints(action_node) : []
29
+
30
+ hints << default_response(verb) if hints.empty?
31
+
32
+ hints
33
+ end
34
+
35
+ private
36
+
37
+ def class_node
38
+ @class_node ||= find_class_node(@buffer, class_name: @class_name)
39
+ end
40
+
41
+ def find_action_node(action_name)
42
+ each_method_definition(class_node) do |child|
43
+ next unless child.type == :def
44
+
45
+ return child if child.children[0].to_s == action_name.to_s
46
+ end
47
+ nil
48
+ end
49
+
50
+ def extract_render_hints(action_node)
51
+ hints = []
52
+ walk_nodes(action_node) do |node|
53
+ next unless node.type == :send
54
+
55
+ if node.children[1] == :render
56
+ hint = build_hint_from_render(node)
57
+ hints << hint if hint
58
+ elsif node.children[1] == :head
59
+ status = normalize_status(node.children[2])
60
+ hints << ResponseHint.new(status: status, schema: nil, description: "No content")
61
+ end
62
+ end
63
+ hints
64
+ end
65
+
66
+ def build_hint_from_render(node)
67
+ status = "200"
68
+ schema_ref = nil
69
+ schema = { type: "object" }
70
+
71
+ node.children[2..].each do |arg|
72
+ next unless arg
73
+
74
+ next unless arg.type == :hash
75
+
76
+ each_hash_pair(arg) do |key, value|
77
+ case literal_value(key)
78
+ when :json
79
+ schema_ref, schema = infer_json_schema(value)
80
+ when :status
81
+ status = normalize_status(value)
82
+ end
83
+ end
84
+ end
85
+
86
+ ResponseHint.new(status: status, schema_ref: schema_ref, schema: schema, description: "Successful response")
87
+ end
88
+
89
+ def infer_json_schema(value_node)
90
+ case value_node.type
91
+ when :send
92
+ receiver = value_node.children[0]
93
+ method_name = value_node.children[1]
94
+ if receiver&.type == :const && method_name == :new
95
+ serializer_class = const_path(receiver)
96
+ schema = @serializer_registry.schema_for(serializer_class)
97
+ return [serializer_class, schema]
98
+ end
99
+ if receiver&.type == :ivar
100
+ model_name = infer_model_from_ivar(receiver)
101
+ return [model_name, { "$ref" => "#/components/schemas/#{model_name}" }] if model_name
102
+ end
103
+ when :const
104
+ model_name = const_path(value_node)
105
+ return [model_name, { "$ref" => "#/components/schemas/#{model_name}" }]
106
+ when :hash
107
+ return [nil, { type: "object" }]
108
+ end
109
+
110
+ [nil, { type: "object" }]
111
+ end
112
+
113
+ def infer_model_from_ivar(node)
114
+ node.children[0].to_s.sub(/^@/, "").classify
115
+ rescue StandardError
116
+ nil
117
+ end
118
+
119
+ def each_hash_pair(hash_node, &block)
120
+ hash_node.children.each do |pair_node|
121
+ next unless pair_node.type == :pair
122
+
123
+ yield pair_node.children[0], pair_node.children[1]
124
+ end
125
+ end
126
+
127
+ def default_response(verb)
128
+ status = DEFAULT_STATUS.fetch(verb, "200")
129
+ if status == "204"
130
+ ResponseHint.new(status: status, schema: nil, description: "No content")
131
+ else
132
+ ResponseHint.new(status: status, schema: { type: "object" }, description: "Successful response")
133
+ end
134
+ end
135
+
136
+ def normalize_status(node)
137
+ value = literal_value(node)
138
+ case value
139
+ when Integer then value.to_s
140
+ when Symbol
141
+ Rack::Utils.status_code(value).to_s
142
+ else
143
+ value.to_s
144
+ end
145
+ rescue StandardError
146
+ "200"
147
+ end
148
+
149
+ def literal_value(node)
150
+ case node&.type
151
+ when :sym then node.children[0]
152
+ when :int then node.children[0]
153
+ when :str then node.children[0]
154
+ else node.to_s
155
+ end
156
+ end
157
+ end
158
+ end
@@ -0,0 +1,139 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsAutodoc
4
+ class RouteOperation
5
+ attr_reader :verb,
6
+ :path,
7
+ :controller_class,
8
+ :action,
9
+ :route_name,
10
+ :path_params,
11
+ :tags,
12
+ :constraints
13
+
14
+ def initialize(verb:, path:, controller_class:, action:, route_name:, path_params:, tags:, constraints: {})
15
+ @verb = verb
16
+ @path = path
17
+ @controller_class = controller_class
18
+ @action = action.to_s
19
+ @route_name = route_name
20
+ @path_params = path_params
21
+ @tags = tags
22
+ @constraints = constraints
23
+ end
24
+
25
+ def operation_id
26
+ "#{controller_class.name.underscore.tr('/', '_')}_#{action}"
27
+ end
28
+
29
+ def openapi_path
30
+ path.gsub(/:([a-zA-Z_][a-zA-Z0-9_]*)/, '{\1}')
31
+ end
32
+ end
33
+
34
+ class RouteInspector
35
+ HTTP_VERBS = %w[GET HEAD POST PUT PATCH DELETE OPTIONS].freeze
36
+
37
+ def initialize(config: RailsAutodoc.config)
38
+ @config = config
39
+ end
40
+
41
+ def operations
42
+ ensure_controllers_loaded!
43
+ collect_operations.sort_by { |op| [op.path, op.verb, op.action] }
44
+ end
45
+
46
+ private
47
+
48
+ def ensure_controllers_loaded!
49
+ return unless defined?(Rails) && Rails.application
50
+
51
+ Rails.application.eager_load! if Rails.application.config.eager_load == false
52
+ rescue StandardError
53
+ nil
54
+ end
55
+
56
+ def collect_operations
57
+ routes.flat_map { |route| operation_from_route(route) }.compact
58
+ end
59
+
60
+ def routes
61
+ Rails.application.routes.routes
62
+ end
63
+
64
+ def operation_from_route(route)
65
+ return nil unless route.respond_to?(:requirements)
66
+
67
+ requirements = route.requirements
68
+ controller_name = requirements[:controller]
69
+ action = requirements[:action]
70
+ return nil if controller_name.blank? || action.blank?
71
+
72
+ controller_class = resolve_controller(controller_name)
73
+ return nil unless controller_class
74
+ return nil unless controller_class.action_methods.include?(action.to_s)
75
+
76
+ path = normalize_path(route.path.spec.to_s)
77
+ return nil if @config.excluded_path?(path)
78
+
79
+ verbs = extract_verbs(route)
80
+ path_params = extract_path_params(path)
81
+ tags = extract_tags(controller_class)
82
+
83
+ verbs.map do |verb|
84
+ RouteOperation.new(
85
+ verb: verb,
86
+ path: path,
87
+ controller_class: controller_class,
88
+ action: action,
89
+ route_name: route.name,
90
+ path_params: path_params,
91
+ tags: tags,
92
+ constraints: route.constraints
93
+ )
94
+ end
95
+ rescue StandardError
96
+ nil
97
+ end
98
+
99
+ def resolve_controller(controller_name)
100
+ controller_path = "#{controller_name.camelize}Controller"
101
+ controller_path.constantize
102
+ rescue NameError
103
+ nil
104
+ end
105
+
106
+ def normalize_path(raw_path)
107
+ path = raw_path
108
+ path = path.sub(/\(\.:format\)\z/, "")
109
+ path = path.sub("(.:format)", "")
110
+ path = "/" if path.blank?
111
+ path
112
+ end
113
+
114
+ def extract_verbs(route)
115
+ verb = route.verb
116
+ if verb.is_a?(Regexp)
117
+ HTTP_VERBS.grep(verb)
118
+ elsif verb.is_a?(String)
119
+ verb.split("|").map(&:upcase).reject { |v| v == "HEAD" }
120
+ else
121
+ ["GET"]
122
+ end
123
+ end
124
+
125
+ def extract_path_params(path)
126
+ path.scan(/:([a-zA-Z_][a-zA-Z0-9_]*)/).flatten.uniq
127
+ end
128
+
129
+ def extract_tags(controller_class)
130
+ parts = controller_class.name.split("::")
131
+ controller_part = parts.last.sub(/Controller\z/, "")
132
+ tags = [controller_part]
133
+
134
+ namespace_parts = parts[0..-2]
135
+ tags.concat(namespace_parts) unless namespace_parts.empty?
136
+ tags.uniq
137
+ end
138
+ end
139
+ end
@@ -0,0 +1,142 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsAutodoc
4
+ class SchemaMapper
5
+ TYPE_MAP = {
6
+ string: { type: "string" },
7
+ text: { type: "string" },
8
+ citext: { type: "string" },
9
+ uuid: { type: "string", format: "uuid" },
10
+ integer: { type: "integer" },
11
+ bigint: { type: "integer", format: "int64" },
12
+ float: { type: "number", format: "float" },
13
+ decimal: { type: "number", format: "double" },
14
+ boolean: { type: "boolean" },
15
+ datetime: { type: "string", format: "date-time" },
16
+ date: { type: "string", format: "date" },
17
+ time: { type: "string", format: "time" },
18
+ json: { type: "object" },
19
+ jsonb: { type: "object" }
20
+ }.freeze
21
+
22
+ def initialize(schema_path: default_schema_path)
23
+ @schema_path = schema_path
24
+ @tables = {}
25
+ @models = {}
26
+ load_schema if @schema_path&.exist?
27
+ end
28
+
29
+ def apply_types!(schema, model_name: nil)
30
+ return schema unless schema.is_a?(Hash)
31
+
32
+ schema[:properties]&.each do |field, field_schema|
33
+ typed = column_schema(model_name, field) if model_name
34
+ schema[:properties][field] = merge_schemas(field_schema, typed) if typed
35
+ apply_types!(schema[:properties][field], model_name: model_name)
36
+ end
37
+
38
+ schema
39
+ end
40
+
41
+ def model_schema(model_name)
42
+ return nil unless defined?(ActiveRecord::Base)
43
+
44
+ model = model_name.constantize
45
+ table = model.table_name
46
+ columns = @tables[table]
47
+ return nil unless columns
48
+
49
+ properties = {}
50
+ required = []
51
+ columns.each do |column_name, column_meta|
52
+ next if %w[id created_at updated_at].include?(column_name)
53
+
54
+ properties[column_name] = column_meta.dup
55
+ required << column_name unless column_meta[:nullable]
56
+ end
57
+
58
+ {
59
+ type: "object",
60
+ properties: properties,
61
+ required: required
62
+ }
63
+ rescue StandardError
64
+ nil
65
+ end
66
+
67
+ def infer_model_from_controller(controller_class)
68
+ name = controller_class.name.split("::").last.sub(/Controller\z/, "").singularize
69
+ name.constantize.name
70
+ rescue StandardError
71
+ nil
72
+ end
73
+
74
+ def all_model_schemas
75
+ return {} unless defined?(ActiveRecord::Base)
76
+
77
+ schemas = {}
78
+ @tables.each_key do |table|
79
+ model_name = table.classify
80
+ schema = model_schema(model_name)
81
+ schemas[model_name] = schema if schema
82
+ rescue StandardError
83
+ next
84
+ end
85
+ schemas
86
+ end
87
+
88
+ private
89
+
90
+ def default_schema_path
91
+ return nil unless defined?(Rails) && Rails.respond_to?(:root)
92
+
93
+ Rails.root.join("db/schema.rb")
94
+ end
95
+
96
+ def load_schema
97
+ content = @schema_path.read.gsub("\r\n", "\n")
98
+ parse_create_tables(content)
99
+ end
100
+
101
+ def parse_create_tables(content)
102
+ content.scan(/create_table\s+"([^"]+)"[^\n]*\n(.*?)end/m).each do |table, body|
103
+ @tables[table] = parse_columns(body)
104
+ end
105
+ end
106
+
107
+ def parse_columns(body)
108
+ columns = {}
109
+ body.scan(/t\.(\w+)\s+"([^"]+)"(?:,\s*(.*?))?(?:\r)?$/).each do |type, name, options|
110
+ columns[name] = build_column_schema(type, options)
111
+ end
112
+ columns
113
+ end
114
+
115
+ def build_column_schema(type, options)
116
+ schema = (TYPE_MAP[type.to_sym] || { type: "string" }).dup
117
+ schema[:nullable] = options.to_s.include?("null: false") == false
118
+ schema
119
+ end
120
+
121
+ def column_schema(model_name, field)
122
+ return nil unless model_name
123
+
124
+ model = model_name.constantize
125
+ table = model.table_name
126
+ column = @tables.dig(table, field.to_s)
127
+ return nil unless column
128
+
129
+ column.dup.tap { |s| s.delete(:nullable) }
130
+ rescue StandardError
131
+ nil
132
+ end
133
+
134
+ def merge_schemas(base, overlay)
135
+ return overlay unless base.is_a?(Hash)
136
+
137
+ base.merge(overlay) do |_key, old_val, new_val|
138
+ old_val.is_a?(Hash) && new_val.is_a?(Hash) ? old_val.merge(new_val) : new_val
139
+ end
140
+ end
141
+ end
142
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsAutodoc
4
+ module Serializers
5
+ class ActiveModelSerializer < Base
6
+ def detect?
7
+ defined?(::ActiveModel::Serializer)
8
+ end
9
+
10
+ def attributes_for(serializer_class)
11
+ if serializer_class.respond_to?(:_attributes)
12
+ serializer_class._attributes.keys.map(&:to_s)
13
+ else
14
+ []
15
+ end
16
+ end
17
+
18
+ def schema_for(serializer_class)
19
+ properties = attributes_for(serializer_class).to_h do |field|
20
+ [field, { type: "string" }]
21
+ end
22
+
23
+ { type: "object", properties: properties }
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsAutodoc
4
+ module Serializers
5
+ class Alba < Base
6
+ def detect?
7
+ defined?(::Alba)
8
+ end
9
+
10
+ def attributes_for(serializer_class)
11
+ serializer_class.instance_methods(false).grep(/^[a-z]/) +
12
+ extract_alba_attributes(serializer_class)
13
+ rescue StandardError
14
+ []
15
+ end
16
+
17
+ def schema_for(serializer_class)
18
+ fields = extract_alba_attributes(serializer_class)
19
+ properties = fields.to_h do |field|
20
+ [field.to_s, { type: "string" }]
21
+ end
22
+
23
+ { type: "object", properties: properties }
24
+ end
25
+
26
+ private
27
+
28
+ def extract_alba_attributes(serializer_class)
29
+ if serializer_class.respond_to?(:attributes)
30
+ serializer_class.attributes.keys.map(&:to_s)
31
+ elsif serializer_class.respond_to?(:_attributes)
32
+ serializer_class._attributes.keys.map(&:to_s)
33
+ else
34
+ []
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsAutodoc
4
+ module Serializers
5
+ class Base
6
+ def detect?
7
+ false
8
+ end
9
+
10
+ def attributes_for(_serializer_class)
11
+ []
12
+ end
13
+
14
+ def schema_for(_serializer_class)
15
+ { type: "object" }
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsAutodoc
4
+ module Serializers
5
+ class Blueprinter < Base
6
+ def detect?
7
+ defined?(::Blueprinter)
8
+ end
9
+
10
+ def attributes_for(serializer_class)
11
+ if serializer_class.respond_to?(:fields)
12
+ serializer_class.fields.keys.map(&:to_s)
13
+ else
14
+ []
15
+ end
16
+ end
17
+
18
+ def schema_for(serializer_class)
19
+ properties = attributes_for(serializer_class).to_h do |field|
20
+ [field, { type: "string" }]
21
+ end
22
+
23
+ { type: "object", properties: properties }
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+ require_relative "alba"
5
+ require_relative "blueprinter"
6
+ require_relative "active_model_serializer"
7
+
8
+ module RailsAutodoc
9
+ module Serializers
10
+ class Registry
11
+ ADAPTERS = [
12
+ Alba.new,
13
+ Blueprinter.new,
14
+ ActiveModelSerializer.new
15
+ ].freeze
16
+
17
+ def active_adapters
18
+ ADAPTERS.select(&:detect?)
19
+ end
20
+
21
+ def schema_for(serializer_class)
22
+ adapter = active_adapters.find do |candidate|
23
+ candidate.schema_for(serializer_class).fetch(:properties, {}).any?
24
+ end
25
+ adapter ? adapter.schema_for(serializer_class) : { type: "object" }
26
+ end
27
+ end
28
+ end
29
+ end