gitlab-grape-openapi 0.0.0 → 0.1.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 (39) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +1 -1
  3. data/README.md +276 -4
  4. data/lib/gitlab/grape_openapi/concerns/fail_fast_annotatable.rb +23 -0
  5. data/lib/gitlab/grape_openapi/concerns/limit_resolver.rb +31 -0
  6. data/lib/gitlab/grape_openapi/concerns/regex_converter.rb +58 -0
  7. data/lib/gitlab/grape_openapi/concerns/serializable.rb +19 -0
  8. data/lib/gitlab/grape_openapi/configuration.rb +25 -0
  9. data/lib/gitlab/grape_openapi/converters/coercer_resolver.rb +74 -0
  10. data/lib/gitlab/grape_openapi/converters/entity_converter.rb +267 -0
  11. data/lib/gitlab/grape_openapi/converters/operation_converter.rb +255 -0
  12. data/lib/gitlab/grape_openapi/converters/parameter_converter.rb +252 -0
  13. data/lib/gitlab/grape_openapi/converters/path_converter.rb +152 -0
  14. data/lib/gitlab/grape_openapi/converters/request_body_converter.rb +97 -0
  15. data/lib/gitlab/grape_openapi/converters/response_converter.rb +185 -0
  16. data/lib/gitlab/grape_openapi/converters/tag_converter.rb +36 -0
  17. data/lib/gitlab/grape_openapi/converters/type_resolver.rb +75 -0
  18. data/lib/gitlab/grape_openapi/generator.rb +60 -0
  19. data/lib/gitlab/grape_openapi/models/info.rb +29 -0
  20. data/lib/gitlab/grape_openapi/models/operation.rb +47 -0
  21. data/lib/gitlab/grape_openapi/models/parameter.rb +43 -0
  22. data/lib/gitlab/grape_openapi/models/path_item.rb +26 -0
  23. data/lib/gitlab/grape_openapi/models/request_body/parameter_schema.rb +250 -0
  24. data/lib/gitlab/grape_openapi/models/request_body/parameters.rb +87 -0
  25. data/lib/gitlab/grape_openapi/models/response.rb +48 -0
  26. data/lib/gitlab/grape_openapi/models/schema.rb +61 -0
  27. data/lib/gitlab/grape_openapi/models/security_scheme.rb +130 -0
  28. data/lib/gitlab/grape_openapi/models/server.rb +31 -0
  29. data/lib/gitlab/grape_openapi/models/server_variable.rb +25 -0
  30. data/lib/gitlab/grape_openapi/models/tag.rb +44 -0
  31. data/lib/gitlab/grape_openapi/request_body_registry.rb +57 -0
  32. data/lib/gitlab/grape_openapi/schema_registry.rb +26 -0
  33. data/lib/gitlab/grape_openapi/serializers/time.rb +19 -0
  34. data/lib/gitlab/grape_openapi/tag_registry.rb +29 -0
  35. data/lib/gitlab/grape_openapi/version.rb +7 -0
  36. data/lib/gitlab-grape-openapi.rb +64 -0
  37. metadata +162 -12
  38. data/lib/gitlab/grape/openapi/version.rb +0 -9
  39. data/lib/gitlab/grape/openapi.rb +0 -21
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f1f4ea6abfdf3c4f4df8d30e3c25801d2e52b6f750ee1e8663b7e6ba84fed19d
4
- data.tar.gz: 484cd5bb383b8936c966bb720dfc6c0be2febb0dd839f9ab2535124050d72b79
3
+ metadata.gz: 8f0408d05e90c29d726e2992e29455b1244d6dc028a28c9e63be8c1d4cfc1a23
4
+ data.tar.gz: 10157f62f24d46cb7e768fb141fdf391db6015d342f72f766b4225faa53919df
5
5
  SHA512:
6
- metadata.gz: f20d63c31feaa71202b4dd517b5000097475102343ff4ab6b808958f795a42f9a73ad068c3b83f65ee3bf2bb658941f17e2a1b16c9a5eb9a61a55eb7ed5e3b23
7
- data.tar.gz: 0c306ec014c6f0646ae25156107c5cbdf74033385cf5084313ee4285eb601b1eae1a3d679c7179b5af2bd5f0e36ac366f789424f930c7f24e8569ad0c0fdac57
6
+ metadata.gz: feb73a721b4d65eb5078105c4c21d360a86aeb93538164d115bf358fc38dadea00b5e2c177b23d2e5adc5cbae5c6172306c0ca4bb78a78a5a0a27f7154ef9f80
7
+ data.tar.gz: d61316ee6a5e1c20ae95e9f35430cff097d3b484301d2622ac426ee4355382b992dac5ae71db0dd492acf1f41eaa50b421b6d2bc86b52a99fd3af04f68cbc0b5
data/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2025 GitLab, Inc.
3
+ Copyright (c) 2026 GitLab, Inc.
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
data/README.md CHANGED
@@ -1,6 +1,278 @@
1
- # Gitlab::Grape::Openapi
1
+ # GitLab Grape OpenAPI
2
2
 
3
- > [!warning] Reserved gem name
4
- > This gem is a placeholder to [reserve a gem name](https://docs.gitlab.com/development/gems/#reserve-a-gem-name)
3
+ > [!IMPORTANT]
4
+ > **Internal use only.** This gem exists to generate the OpenAPI 3.0 spec for
5
+ > the [GitLab Rails monorepo](https://gitlab.com/gitlab-org/gitlab) and is not
6
+ > intended for use outside of GitLab.
7
+ >
8
+ > - It is published to rubygems.org only so the monorepo's `Gemfile` can
9
+ > depend on it — not as a general-purpose Grape → OpenAPI tool.
10
+ > - The public API, configuration DSL, and generated output may change in
11
+ > **any** release, including patch releases. There is no semantic-versioning
12
+ > contract.
13
+ > - No external support is provided. Issues and merge requests from outside
14
+ > GitLab may be closed without review.
15
+ > - Feature work is driven by the needs of `gitlab-org/gitlab`; capabilities
16
+ > that aren't needed there will not be added.
5
17
 
6
- Refer to [`gems/`](https://gitlab.com/gitlab-org/gitlab/-/tree/master/gems?ref_type=heads) directory for actual implementation.
18
+ Internal gem for generating [OpenAPI 3.0](https://spec.openapis.org/oas/v3.0.0) specifications from [Grape](https://github.com/ruby-grape/grape) API definitions, used by [`gitlab-org/gitlab`](https://gitlab.com/gitlab-org/gitlab) to publish its REST API reference.
19
+
20
+ ## Installation
21
+
22
+ Add to your Gemfile:
23
+
24
+ ```ruby
25
+ gem 'gitlab-grape-openapi'
26
+ ```
27
+
28
+ Then run:
29
+
30
+ ```bash
31
+ bundle install
32
+ ```
33
+
34
+ ## Configuration
35
+
36
+ Configure the gem using the `Gitlab::GrapeOpenapi.configure` block, typically in an initializer:
37
+
38
+ ```ruby
39
+ Gitlab::GrapeOpenapi.configure do |config|
40
+ # Required: API metadata
41
+ config.info = Gitlab::GrapeOpenapi::Models::Info.new(
42
+ title: 'My API',
43
+ description: 'API description',
44
+ version: 'v1',
45
+ terms_of_service: 'https://example.com/terms'
46
+ )
47
+
48
+ # API path configuration
49
+ config.api_prefix = "api" # Default: "api"
50
+ config.api_version = "v1" # Default: "v1"
51
+
52
+ # Server definitions
53
+ config.servers = [
54
+ Gitlab::GrapeOpenapi::Models::Server.new(
55
+ url: 'https://{hostname}',
56
+ description: "Production API",
57
+ variables: {
58
+ hostname: Gitlab::GrapeOpenapi::Models::ServerVariable.new(
59
+ default: 'api.example.com',
60
+ description: 'API hostname'
61
+ )
62
+ }
63
+ )
64
+ ]
65
+
66
+ # Security schemes
67
+ config.security_schemes = [
68
+ Gitlab::GrapeOpenapi::Models::SecurityScheme.new(
69
+ name: "bearerAuth",
70
+ type: "http",
71
+ scheme: "bearer"
72
+ )
73
+ ]
74
+
75
+ # Exclude specific API classes from generation
76
+ config.excluded_api_classes = [
77
+ 'API::Internal::Base',
78
+ 'API::Internal::Admin'
79
+ ]
80
+
81
+ # Override tag names for better display
82
+ config.tag_overrides = {
83
+ 'Ci' => 'CI',
84
+ 'Oauth' => 'OAuth'
85
+ }
86
+
87
+ # Map Grape route settings to OpenAPI extensions
88
+ config.annotations = {
89
+ lifecycle: 'x-gitlab-lifecycle'
90
+ }
91
+ end
92
+ ```
93
+
94
+ ### Configuration Options
95
+
96
+ | Option | Type | Default | Description |
97
+ | ---------------------- | ------------------------------- | ------- | ------------------------------------------------------------ |
98
+ | `info` | `Models::Info` | `nil` | API metadata (title, description, version, terms of service) |
99
+ | `api_prefix` | `String` | `"api"` | URL prefix for API routes |
100
+ | `api_version` | `String` | `"v1"` | API version string |
101
+ | `servers` | `Array<Models::Server>` | `[]` | Server definitions for the API |
102
+ | `security_schemes` | `Array<Models::SecurityScheme>` | `[]` | Authentication/authorization schemes |
103
+ | `excluded_api_classes` | `Array<String>` | `[]` | API class names to exclude from generation |
104
+ | `tag_overrides` | `Hash` | `{}` | Map of tag names to their display overrides |
105
+ | `annotations` | `Hash` | `{}` | Map of Grape route settings to OpenAPI extension names |
106
+ | `warnings` | `Boolean` | `false` | Emit stderr warnings for synthesized (undeclared) path params |
107
+
108
+ ### Annotations
109
+
110
+ The `annotations` configuration maps Grape route settings to OpenAPI vendor extensions. For example:
111
+
112
+ ```ruby
113
+ config.annotations = {
114
+ lifecycle: 'x-gitlab-lifecycle'
115
+ }
116
+
117
+ When a Grape endpoint has:
118
+
119
+ ```ruby
120
+ route_setting :lifecycle, 'mature'
121
+ ```
122
+
123
+ The generated OpenAPI spec will include:
124
+
125
+ ```yaml
126
+ x-gitlab-lifecycle: mature
127
+ ```
128
+
129
+ ## Usage
130
+
131
+ ### Generating an OpenAPI Specification
132
+
133
+ ```ruby
134
+ # Load all API and entity classes
135
+ Rails.application.eager_load!
136
+
137
+ api_classes = API::Base.descendants
138
+ entity_classes = Grape::Entity.descendants
139
+
140
+ # Generate the specification
141
+ spec = Gitlab::GrapeOpenapi.generate(
142
+ api_classes: api_classes,
143
+ entity_classes: entity_classes
144
+ )
145
+
146
+ # Output as JSON
147
+ File.write('openapi.json', JSON.pretty_generate(spec))
148
+
149
+ # Or as YAML
150
+ require 'yaml'
151
+ File.write('openapi.yaml', spec.to_yaml)
152
+ ```
153
+
154
+ ### Usage with `gitlab-org/gitlab`
155
+
156
+ 1. Start a Rails console in your GDK:
157
+
158
+ ```bash
159
+ cd ~/gdk/gitlab
160
+ rails console
161
+ ```
162
+
163
+ 2. Generate the OpenAPI specification:
164
+
165
+ ```ruby
166
+ Rails.application.eager_load!
167
+ api_classes = API::Base.descendants
168
+ entity_classes = Grape::Entity.descendants
169
+ spec = Gitlab::GrapeOpenapi.generate(api_classes: api_classes, entity_classes: entity_classes)
170
+ File.write(Rails.root.join('tmp', 'openapi.json'), JSON.pretty_generate(spec))
171
+ ```
172
+
173
+ 3. The spec will be saved to `tmp/openapi.json` in your GitLab directory.
174
+
175
+ ## Architecture
176
+
177
+ The gem follows a converter-based architecture:
178
+
179
+ ```
180
+ Generator
181
+ ├── TagConverter - Extracts tags from API classes
182
+ ├── EntityConverter - Converts Grape::Entity to OpenAPI schemas
183
+ ├── PathConverter - Converts routes to OpenAPI paths
184
+ │ ├── OperationConverter - Converts individual endpoints
185
+ │ ├── ParameterConverter - Converts endpoint parameters
186
+ │ ├── ResponseConverter - Converts endpoint responses
187
+ │ └── RequestBodyConverter - Converts request bodies
188
+ └── TypeResolver - Maps Ruby/Grape types to OpenAPI types
189
+ ```
190
+
191
+ ### Registries
192
+
193
+ - **SchemaRegistry** - Tracks converted entity schemas
194
+ - **RequestBodyRegistry** - Tracks request body schemas
195
+ - **TagRegistry** - Tracks API tags
196
+
197
+ ## Development
198
+
199
+ ```bash
200
+ bundle install
201
+ bundle exec rspec
202
+ ```
203
+
204
+ ### Running Tests
205
+
206
+ ```bash
207
+ bundle exec rspec
208
+ ```
209
+
210
+ ### Linting
211
+
212
+ ```bash
213
+ bundle exec rubocop
214
+ ```
215
+
216
+ ### Releasing
217
+
218
+ Releases are driven by the
219
+ [gem-release CI component](https://gitlab.com/gitlab-org/components/gem-release)
220
+ and a merge request whose title contains `RELEASE`.
221
+
222
+ 1. Open a merge request from the
223
+ [Release template](.gitlab/merge_request_templates/Release.md). The
224
+ `/title RELEASE: v<NEW_VERSION>` quick action ensures the title
225
+ contains `RELEASE`, which is what exposes the release-creation and
226
+ `gem-publication` jobs.
227
+ 2. Bump the `VERSION` constant in
228
+ `lib/gitlab/grape_openapi/version.rb` and fill in the changelog per
229
+ the template.
230
+ 3. Get the MR reviewed and merged. **Do not run the `gem-publication`
231
+ job from the MR pipeline** — it is meant to stay un-run during
232
+ review (see below).
233
+ 4. After merge, follow the post-release steps in `CLAUDE.md` to bump
234
+ the `gitlab-grape-openapi` version in `gitlab-org/gitlab` and
235
+ regenerate the committed `openapi_v3.yaml`.
236
+
237
+ #### How publishing works
238
+
239
+ `GEM_HOST_API_KEY` is configured as an **inherited, protected**
240
+ variable on a parent group, so it is only exposed to pipelines running
241
+ on protected branches or protected tags. This is why running the
242
+ `gem-publication` job manually from the MR pipeline fails — the API key
243
+ is not available there.
244
+
245
+ The expected flow relies on the default branch being protected:
246
+
247
+ - During review, the MR-pipeline `gem-publication` job stays un-run
248
+ (it is `manual`, and would fail anyway without the key).
249
+ - When the version bump lands on the default branch (`main`), the
250
+ `gem-publication` job runs again — this time with the protected
251
+ `GEM_HOST_API_KEY` available — and publishes the gem to RubyGems.
252
+
253
+ The gem-release component's rules that re-trigger publication after
254
+ merge:
255
+
256
+ ```yaml
257
+ .gem-release-default-rules:
258
+ rules:
259
+ - if: '$CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
260
+ changes: ["lib/**/version.rb"]
261
+ - if: '$CI_MERGE_REQUEST_TITLE =~ /RELEASE/ && "...dry_run..." == "true"'
262
+ - if: '$CI_MERGE_REQUEST_TITLE =~ /RELEASE/'
263
+ when: manual
264
+ ```
265
+
266
+ The first rule is the one that publishes: a `push` to the default
267
+ branch that changes `lib/**/version.rb`.
268
+
269
+ ## Contributing
270
+
271
+ This gem is maintained by GitLab's API Platform team for internal use.
272
+ External contributions are not actively solicited; issues and merge
273
+ requests opened by non-GitLab contributors may be closed without review.
274
+ GitLab team members should follow the [standard contribution guidelines](https://docs.gitlab.com/ee/development/contributing/) — see the [project page](https://gitlab.com/gitlab-org/ruby/gems/gitlab-grape-openapi).
275
+
276
+ ## License
277
+
278
+ Released under the [MIT License](LICENSE).
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gitlab
4
+ module GrapeOpenapi
5
+ module Concerns
6
+ module FailFastAnnotatable
7
+ FAIL_FAST_ANNOTATION = '(validation stops on first error)'
8
+
9
+ private
10
+
11
+ def fail_fast_in_validations?(validations)
12
+ validations&.any? { |v| v.dig(:opts, :fail_fast) }
13
+ end
14
+
15
+ def annotate_fail_fast(desc)
16
+ return desc.to_s if desc.to_s.include?(FAIL_FAST_ANNOTATION)
17
+
18
+ "#{desc} #{FAIL_FAST_ANNOTATION}"
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ # API::Validations::Validators::Limit is a custom validator unique to GitLab.
4
+ # This resolver compares by class name string rather than constant reference so that this gem
5
+ # does not blow up in environments where the validator is not defined.
6
+
7
+ module Gitlab
8
+ module GrapeOpenapi
9
+ module Concerns
10
+ module LimitResolver
11
+ private
12
+
13
+ def limit_for(validations)
14
+ validation = validations&.find do |v|
15
+ v[:validator_class].name == 'API::Validations::Validators::Limit'
16
+ rescue NoMethodError
17
+ false
18
+ end
19
+ validation && validation[:options]
20
+ end
21
+
22
+ def apply_limit!(schema, validations)
23
+ return unless schema[:type] == 'string'
24
+
25
+ limit = limit_for(validations)
26
+ schema[:maxLength] = limit if limit.is_a?(Integer) && limit.positive?
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "js_regex"
4
+
5
+ module Gitlab
6
+ module GrapeOpenapi
7
+ module Concerns
8
+ # Converts Ruby Regexp objects into ECMA-262 (JavaScript) compatible
9
+ # pattern strings for use in OpenAPI `pattern` schema fields.
10
+ #
11
+ # OpenAPI requires `pattern` values to be valid ECMA-262 regular
12
+ # expressions, but Ruby RegExes routinely contain constructs that ECMA-262
13
+ # cannot parse: \A / \z anchors, inline option groups like (?i-mx:...),
14
+ # POSIX classes, etc. js_regex translates these into JS-compatible equivalents.
15
+ #
16
+ # The Ruby /i flag is folded into the pattern itself (via character class
17
+ # expansion) so the resulting pattern carries no flags, which OpenAPI's
18
+ # flag-less `pattern` field requires
19
+ module RegexConverter
20
+ # js_regex's `target:` controls which ECMAScript version's regex
21
+ # features it will emit. ES2018 is the newest target the gem
22
+ # supports (as of 3.14.0); earlier targets like the default (ES5)
23
+ # silently drop lookbehinds, changing the semantics of any Ruby
24
+ # regex that relies on them
25
+ JS_REGEX_TARGET = 'ES2018'
26
+
27
+ def regexp_to_pattern(value)
28
+ regexp = extract_regexp(value)
29
+ return unless regexp
30
+
31
+ JsRegex.new(inline_case_insensitivity(regexp), target: JS_REGEX_TARGET).source
32
+ end
33
+
34
+ private
35
+
36
+ # Grape stores the validation's `:options` as the Regexp itself for
37
+ # `regexp: /.../`, or as a Hash `{ value: /.../, message: '...' }` for the
38
+ # long form. Pull the Regexp out of either shape
39
+ def extract_regexp(value)
40
+ return value if value.is_a?(Regexp)
41
+
42
+ value[:value] if value.is_a?(Hash) && value[:value].is_a?(Regexp)
43
+ end
44
+
45
+ # js_regex bakes case-insensitivity into character classes only when /i
46
+ # appears as an inline group; an outer /i flag survives as a JS-level
47
+ # option that we cannot represent in OpenAPI. Wrap the source in (?i:...)
48
+ # and drop the outer flag so js_regex expands the character classes
49
+ def inline_case_insensitivity(regexp)
50
+ return regexp if (regexp.options & Regexp::IGNORECASE).zero?
51
+
52
+ remaining_options = regexp.options & ~Regexp::IGNORECASE
53
+ Regexp.new("(?i:#{regexp.source})", remaining_options)
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gitlab
4
+ module GrapeOpenapi
5
+ module Concerns
6
+ module Serializable
7
+ private
8
+
9
+ def serializable?(value)
10
+ return false if value.is_a?(Proc)
11
+ return false if defined?(ActiveSupport::TimeWithZone) && value.is_a?(ActiveSupport::TimeWithZone)
12
+ return false if value.is_a?(Time)
13
+
14
+ true
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gitlab
4
+ module GrapeOpenapi
5
+ class Configuration
6
+ attr_accessor :api_version, :api_prefix, :excluded_api_classes, :servers, :security_schemes, :info,
7
+ :tag_overrides, :annotations, :coercer_mappings, :warnings
8
+
9
+ def initialize
10
+ @api_prefix = "api"
11
+ @api_version = "v1"
12
+ @excluded_api_classes = []
13
+ @info = nil
14
+
15
+ @servers = []
16
+ @security_schemes = []
17
+
18
+ @tag_overrides = {}
19
+ @annotations = {}
20
+ @coercer_mappings = {}
21
+ @warnings = false
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gitlab
4
+ module GrapeOpenapi
5
+ module Converters
6
+ module CoercerResolver
7
+ def coercer_mapping_for(validations)
8
+ coercer_method = extract_coercer_method(validations)
9
+ return unless coercer_method # Validation doesn't use `coerce_with`
10
+ return if inline_proc?(coercer_method) # Inline Procs shouldn't need a coercer method
11
+
12
+ coercer_name = resolve_coercer_name(coercer_method)
13
+ config = Gitlab::GrapeOpenapi.configuration
14
+ mapping = config.coercer_mappings.find { |pattern, _mapping| coercer_name == pattern }&.last
15
+ return mapping if mapping
16
+
17
+ raise GenerationError,
18
+ "No OpenAPI schema mapping found for coercer '#{coercer_name}'. " \
19
+ "Add an entry for '#{coercer_name}' to coercer_mappings in " \
20
+ "config/initializers/gitlab_grape_openapi.rb, or use an inline lambda instead."
21
+ end
22
+
23
+ def build_coerced_schema(mapping)
24
+ schema = {}
25
+ schema[:type] = mapping[:type] if mapping[:type]
26
+ schema[:items] = { type: mapping[:items_type] } if mapping[:items_type]
27
+ schema[:format] = mapping[:format] if mapping[:format]
28
+ schema[:additionalProperties] = mapping[:additional_properties] if mapping[:additional_properties]
29
+
30
+ schema
31
+ end
32
+
33
+ private
34
+
35
+ def extract_coercer_method(validations)
36
+ return unless validations
37
+
38
+ coerce_validation = validations.find do |v|
39
+ v[:validator_class] == Grape::Validations::Validators::CoerceValidator
40
+ end
41
+ return unless coerce_validation
42
+
43
+ coerce_validation.dig(:options, :method)
44
+ end
45
+
46
+ def inline_proc?(coercer_method)
47
+ return false unless coercer_method.is_a?(Proc)
48
+
49
+ source_file, _line = coercer_method.source_location
50
+ return true unless source_file
51
+
52
+ source_file.exclude?('/validations/types/')
53
+ end
54
+
55
+ def resolve_coercer_name(coercer_method)
56
+ return coercer_name_from_source_location(coercer_method) if coercer_method.is_a?(Proc)
57
+
58
+ coercer_method.name.to_s
59
+ end
60
+
61
+ def coercer_name_from_source_location(coercer_method)
62
+ source_file, _line = coercer_method.source_location
63
+ return coercer_method.to_s unless source_file
64
+
65
+ camelize(File.basename(source_file, '.rb'))
66
+ end
67
+
68
+ def camelize(str)
69
+ str.split('_').map(&:capitalize).join
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end