structured_params-openapi 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,74 @@
1
+ # Generated from lib/structured_params/openapi/configuration.rb with RBS::Inline
2
+
3
+ module StructuredParams
4
+ # OpenAPI contract configuration and drift-check utilities.
5
+ module OpenAPI
6
+ # Global OpenAPI document and component-name convention for drift checks.
7
+ class Configuration
8
+ @ignored_issues: Array[Hash[Symbol, untyped]]
9
+
10
+ @documents: Hash[String, Array[untyped]]
11
+
12
+ attr_accessor document: Hash[untyped, untyped] | String?
13
+
14
+ attr_writer schema_name_resolver: untyped
15
+
16
+ attr_writer document_resolver: untyped
17
+
18
+ attr_writer parameter_class_filter: untyped
19
+
20
+ # : () -> void
21
+ def initialize: () -> void
22
+
23
+ # The OpenAPI document for +parameter_class+: the +document_resolver+ result when it
24
+ # returns one, otherwise the global +document+. Lets one app check classes from several
25
+ # API namespaces (e.g. admin and front) against separate files.
26
+ # : (singleton(StructuredParams::Params)) -> (Hash[untyped, untyped] | String)?
27
+ def document_for: (singleton(StructuredParams::Params)) -> (Hash[untyped, untyped] | String)?
28
+
29
+ # Parses an OpenAPI file once per modification time, so a bulk check across many
30
+ # parameter classes does not re-read the same document for each of them. JSON files
31
+ # work too, since JSON is valid YAML. Only the mtime invalidates the cache, so two writes
32
+ # within the filesystem's timestamp granularity can serve a stale parse. YAML dates such as
33
+ # `example: 2020-01-01` load as Date, hence +permitted_classes+.
34
+ # : (String) -> Hash[untyped, untyped]
35
+ def load_document: (String) -> Hash[untyped, untyped]
36
+
37
+ # Suppresses matching findings instead of raising them as errors. Each of +path+, +kind+
38
+ # and +parameter_class+ narrows the rule; an omitted one matches everything, but at least
39
+ # one must be given. Without +parameter_class+ the rule applies to every drift check,
40
+ # including once per parent class for a nested object reused by several top-level
41
+ # parameter classes. Skips (unsupported schema features) are never ignored; they only
42
+ # appear in Result#skipped.
43
+ #
44
+ # Paths use attribute names joined with +.+ and +[]+ for array items, e.g.
45
+ # +items[].sku+. A schema that cannot be found at all (+:missing_schema+) concerns the
46
+ # whole class rather than a property, so omit +path+ for it.
47
+ #
48
+ # Ignored findings are moved to DriftCheck::Result#ignored rather than dropped, so
49
+ # they stay auditable; #valid? and #to_message treat them as resolved.
50
+ # : (?path: String?, ?kind: Symbol?, ?parameter_class: (Class | String)?) -> void
51
+ def ignore_drift: (?path: String?, ?kind: Symbol?, ?parameter_class: (Class | String)?) -> void
52
+
53
+ # : (Symbol, String, ?untyped) -> bool
54
+ def ignored?: (Symbol, String, ?untyped) -> bool
55
+
56
+ # : () -> Array[singleton(StructuredParams::Params)]
57
+ def parameter_classes: () -> Array[singleton(StructuredParams::Params)]
58
+
59
+ # : (singleton(StructuredParams::Params), ?String?) -> String
60
+ def pointer_for: (singleton(StructuredParams::Params), ?String?) -> String
61
+ end
62
+
63
+ self.@configuration: Configuration?
64
+
65
+ # : () -> Configuration
66
+ def self.configuration: () -> Configuration
67
+
68
+ # : () { (Configuration) -> void } -> void
69
+ def self.configure: () { (Configuration) -> void } -> void
70
+
71
+ # : () -> void
72
+ def self.reset_configuration!: () -> void
73
+ end
74
+ end
@@ -0,0 +1,19 @@
1
+ # Generated from lib/structured_params/openapi/drift_check/issue.rb with RBS::Inline
2
+
3
+ module StructuredParams
4
+ module OpenAPI
5
+ class DriftCheck
6
+ # A single finding (error, skipped check, or ignored finding) from a drift check.
7
+ class Issue < Struct[Symbol | String]
8
+ attr_accessor kind(): Symbol
9
+
10
+ attr_accessor path(): String
11
+
12
+ attr_accessor message(): String
13
+
14
+ def self.new: (?kind: Symbol, ?path: String, ?message: String) -> instance
15
+ | ({ ?kind: Symbol, ?path: String, ?message: String }) -> instance
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,40 @@
1
+ # Generated from lib/structured_params/openapi/drift_check/result.rb with RBS::Inline
2
+
3
+ module StructuredParams
4
+ module OpenAPI
5
+ class DriftCheck
6
+ # Result of a single schema/class comparison.
7
+ class Result
8
+ attr_reader errors: Array[Issue]
9
+
10
+ attr_reader skipped: Array[Issue]
11
+
12
+ attr_reader ignored: Array[Issue]
13
+
14
+ attr_reader parameter_class: singleton(::StructuredParams::Params)
15
+
16
+ # : (singleton(::StructuredParams::Params), Array[Issue], Array[Issue], Array[Issue]) -> void
17
+ def initialize: (singleton(::StructuredParams::Params), Array[Issue], Array[Issue], Array[Issue]) -> void
18
+
19
+ # : () -> bool
20
+ def valid?: () -> bool
21
+
22
+ # : () -> String
23
+ def to_message: () -> String
24
+
25
+ # Lists what a passing check still left unverified (skipped) or suppressed (ignored),
26
+ # so those stay visible in CI output instead of only on the result object.
27
+ # : () -> String
28
+ def to_report: () -> String
29
+
30
+ private
31
+
32
+ # : () -> String
33
+ def class_label: () -> String
34
+
35
+ # : (Array[Issue]) -> String
36
+ def format_issues: (Array[Issue]) -> String
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,201 @@
1
+ # Generated from lib/structured_params/openapi/drift_check.rb with RBS::Inline
2
+
3
+ module StructuredParams
4
+ # Schema comparison types for OpenAPI drift checks.
5
+ module OpenAPI
6
+ # Compares OpenAPI object schemas with StructuredParams declarations.
7
+ # rubocop:disable-next Metrics/ClassLength
8
+ class DriftCheck
9
+ @active_pairs: Array[Array[untyped]]
10
+
11
+ @ignored: Array[Issue]
12
+
13
+ @skipped: Array[Issue]
14
+
15
+ @errors: Array[Issue]
16
+
17
+ @document: Hash[untyped, untyped]?
18
+
19
+ @required_validation: :presence | :none
20
+
21
+ @property_names: Hash[Symbol, String]
22
+
23
+ @pointer: String?
24
+
25
+ @parameter_class: singleton(::StructuredParams::Params)
26
+
27
+ @schema_source: Hash[untyped, untyped] | String?
28
+
29
+ @openapi_source: Hash[untyped, untyped] | String?
30
+
31
+ # Finding kinds that can be raised as errors (and therefore matched by +ignore_drift+).
32
+ ERROR_KINDS: Array[Symbol]
33
+
34
+ # Options that stop a presence validator from enforcing presence on every validation.
35
+ CONDITIONAL_PRESENCE_OPTIONS: Array[Symbol]
36
+
37
+ # Schema types that a +oneOf+/+anyOf+ of plain scalars may offer.
38
+ SCALAR_SCHEMA_TYPES: Array[Symbol]
39
+
40
+ # rubocop:disable Metrics/ParameterLists
41
+ # : (**untyped) -> Result
42
+ def self.call: (**untyped) -> Result
43
+
44
+ # rubocop:disable Metrics/ParameterLists
45
+ # : (**untyped) -> void
46
+ def initialize: (**untyped) -> void
47
+
48
+ # : () -> Result
49
+ def call: () -> Result
50
+
51
+ private
52
+
53
+ # A missing or mistyped schema means the class silently lost its contract coverage,
54
+ # so it is an error (suppressible with +ignore_drift+) rather than a skip.
55
+ # : () -> Hash[untyped, untyped]?
56
+ def load_root_schema: () -> Hash[untyped, untyped]?
57
+
58
+ # : (String) -> nil
59
+ def report_missing_schema: (String) -> nil
60
+
61
+ # : () -> [Hash[untyped, untyped], Hash[untyped, untyped]]
62
+ def load_schema: () -> [ Hash[untyped, untyped], Hash[untyped, untyped] ]
63
+
64
+ # : () -> Hash[untyped, untyped]
65
+ def load_document: () -> Hash[untyped, untyped]
66
+
67
+ # : () -> String?
68
+ def selected_pointer: () -> String?
69
+
70
+ # : (Hash[untyped, untyped], singleton(StructuredParams::Params), String) -> void
71
+ def check_object: (Hash[untyped, untyped], singleton(StructuredParams::Params), String) -> void
72
+
73
+ # : (Hash[untyped, untyped], singleton(StructuredParams::Params), String) -> void
74
+ # rubocop:disable-next Metrics/AbcSize
75
+ def check_object_properties: (Hash[untyped, untyped], singleton(StructuredParams::Params), String) -> void
76
+
77
+ # : (untyped, ActiveModel::Type::Value, String) -> void
78
+ def compare_property: (untyped, ActiveModel::Type::Value, String) -> void
79
+
80
+ # : (Hash[Symbol, String], Hash[String, ActiveModel::Type::Value], String) -> void
81
+ def compare_attribute_names: (Hash[Symbol, String], Hash[String, ActiveModel::Type::Value], String) -> void
82
+
83
+ # : (Hash[untyped, untyped], Hash[Symbol, String], singleton(StructuredParams::Params), String) -> void
84
+ def compare_required_attributes: (Hash[untyped, untyped], Hash[Symbol, String], singleton(StructuredParams::Params), String) -> void
85
+
86
+ # A required property that the schema allows to be null must not demand +presence+,
87
+ # which would reject the null the contract permits.
88
+ # : (Hash[untyped, untyped], String, String) -> bool
89
+ def nullable_property?: (Hash[untyped, untyped], String, String) -> bool
90
+
91
+ # : (Hash[untyped, untyped]) -> bool
92
+ def nullable_schema?: (Hash[untyped, untyped]) -> bool
93
+
94
+ # : (untyped) -> bool
95
+ def null_type?: (untyped) -> bool
96
+
97
+ # Runs a lookup whose findings are reported elsewhere, so they are not recorded twice.
98
+ # : [T] () { () -> T } -> T
99
+ def without_reporting: [T] () { () -> T } -> T
100
+
101
+ # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
102
+ # : (Hash[untyped, untyped], ActiveModel::Type::Value, String) -> void
103
+ def compare_type: (Hash[untyped, untyped], ActiveModel::Type::Value, String) -> void
104
+
105
+ # : (Hash[untyped, untyped], Type::Array, String) -> void
106
+ def compare_array_item_type: (Hash[untyped, untyped], Type::Array, String) -> void
107
+
108
+ # : (Hash[untyped, untyped]) -> Symbol?
109
+ def schema_type: (Hash[untyped, untyped]) -> Symbol?
110
+
111
+ # : (Symbol?, Symbol) -> bool
112
+ def compatible_types?: (Symbol?, Symbol) -> bool
113
+
114
+ # : (singleton(StructuredParams::Params), Symbol) -> bool
115
+ def presence_validator?: (singleton(StructuredParams::Params), Symbol) -> bool
116
+
117
+ # A +oneOf+/+anyOf+ of plain scalars matches when the attribute fits any of them.
118
+ # : (Array[Symbol], ActiveModel::Type::Value, String) -> void
119
+ def compare_scalar_union: (Array[Symbol], ActiveModel::Type::Value, String) -> void
120
+
121
+ # The scalar types offered by a +oneOf+/+anyOf+, or nil when it is not a plain scalar union.
122
+ # : (Hash[untyped, untyped], String) -> Array[Symbol]?
123
+ def scalar_union_types: (Hash[untyped, untyped], String) -> Array[Symbol]?
124
+
125
+ # : (untyped, String) -> Symbol?
126
+ def scalar_option_type: (untyped, String) -> Symbol?
127
+
128
+ # : (Hash[untyped, untyped]) -> String?
129
+ def union_key: (Hash[untyped, untyped]) -> String?
130
+
131
+ # +oneOf: [X, {type: null}]+ and +type: [T, 'null']+ are how OpenAPI 3.1 says "X or null".
132
+ # Collapse them to X and mark the result +nullable+, so they are compared like a 3.0
133
+ # +nullable: true+ schema instead of being treated as unsupported unions.
134
+ # : (Hash[untyped, untyped]) -> Hash[untyped, untyped]
135
+ def collapse_nullable: (Hash[untyped, untyped]) -> Hash[untyped, untyped]
136
+
137
+ # : (Hash[untyped, untyped]) -> Hash[untyped, untyped]?
138
+ def collapse_null_type: (Hash[untyped, untyped]) -> Hash[untyped, untyped]?
139
+
140
+ # : (Hash[untyped, untyped]) -> Hash[untyped, untyped]?
141
+ def collapse_null_union: (Hash[untyped, untyped]) -> Hash[untyped, untyped]?
142
+
143
+ # : (untyped, Hash[untyped, untyped]) -> Hash[untyped, untyped]?
144
+ def nullable_option: (untyped, Hash[untyped, untyped]) -> Hash[untyped, untyped]?
145
+
146
+ # : (untyped) -> bool
147
+ def null_option?: (untyped) -> bool
148
+
149
+ # : (Hash[untyped, untyped], String) -> bool
150
+ def skip_union?: (Hash[untyped, untyped], String) -> bool
151
+
152
+ # : (Hash[untyped, untyped], String, ?Array[String]) -> Hash[untyped, untyped]
153
+ def resolve_references: (Hash[untyped, untyped], String, ?Array[String]) -> Hash[untyped, untyped]
154
+
155
+ # A dangling or circular local reference is a broken contract, not an unsupported feature.
156
+ # : (String, String, Array[String]) -> Hash[untyped, untyped]?
157
+ def fetch_reference: (String, String, Array[String]) -> Hash[untyped, untyped]?
158
+
159
+ # : (String, String, String) -> nil
160
+ def unresolved_reference: (String, String, String) -> nil
161
+
162
+ # A `$ref` left after resolve_references was already reported (skipped if external, an
163
+ # error if unresolvable), so comparing it further would only add misleading findings.
164
+ # : (Hash[untyped, untyped]) -> bool
165
+ def unresolved_reference?: (Hash[untyped, untyped]) -> bool
166
+
167
+ # OpenAPI/JSON Schema forbid sibling keys next to `$ref`, so generators such as TypeSpec
168
+ # commonly wrap a single `$ref` in `allOf` to attach a `description`, `default`, etc.
169
+ # That's equivalent to the ref carrying those siblings directly, so unwrap and merge a
170
+ # single-entry `allOf` (typically a `$ref`, though an inline schema is unwrapped the same
171
+ # way) instead of treating it as unsupported schema composition. `allOf` with more than
172
+ # one entry is genuine composition and stays out of scope (reported via skip_union?).
173
+ # : (Hash[untyped, untyped]) -> Hash[untyped, untyped]
174
+ def unwrap_single_all_of: (Hash[untyped, untyped]) -> Hash[untyped, untyped]
175
+
176
+ # : (untyped, String) -> untyped
177
+ def resolve_pointer: (untyped, String) -> untyped
178
+
179
+ # : (untyped, String) -> untyped
180
+ def dig_pointer_segment: (untyped, String) -> untyped
181
+
182
+ # : (Hash[untyped, untyped], String) -> untyped
183
+ def value_for: (Hash[untyped, untyped], String) -> untyped
184
+
185
+ # : (untyped) -> Symbol
186
+ def attribute_name: (untyped) -> Symbol
187
+
188
+ # : (String, String) -> String
189
+ def join_path: (String, String) -> String
190
+
191
+ # : (String, Symbol, String) -> void
192
+ def error: (String, Symbol, String) -> void
193
+
194
+ # : (String) -> String
195
+ def display_path: (String) -> String
196
+
197
+ # : (String, String) -> void
198
+ def skip: (String, String) -> void
199
+ end
200
+ end
201
+ end
@@ -0,0 +1,46 @@
1
+ # Generated from lib/structured_params/openapi/registry.rb with RBS::Inline
2
+
3
+ module StructuredParams
4
+ # OpenAPI contract registration and bulk execution utilities.
5
+ module OpenAPI
6
+ # Raised by Registry.check_all! when one or more registered contracts drift.
7
+ class DriftError < StandardError
8
+ end
9
+
10
+ # Collects exceptional mappings and runs all configured drift checks.
11
+ class Registry
12
+ self.@entries: Array[Array[untyped]]?
13
+
14
+ # +parameter_class+ may be a class name String (e.g. from a Rails initializer,
15
+ # before Zeitwerk autoloading is set up); it is resolved lazily in +check_all+.
16
+ # +openapi+ (a file path or Hash) overrides the configured document for this class, so
17
+ # one app can check classes against several OpenAPI files.
18
+ # : (singleton(StructuredParams::Params) | String, **untyped) -> void
19
+ # rubocop:disable-next Metrics/ParameterLists
20
+ def self.register: (singleton(StructuredParams::Params) | String, **untyped) -> void
21
+
22
+ # : (?auto_discover: bool) -> Array[DriftCheck::Result]
23
+ def self.check_all: (?auto_discover: bool) -> Array[DriftCheck::Result]
24
+
25
+ # With +strict+, a check that skipped part of a schema also fails the run.
26
+ # : (?auto_discover: bool, ?strict: bool) -> Array[DriftCheck::Result]
27
+ def self.check_all!: (?auto_discover: bool, ?strict: bool) -> Array[DriftCheck::Result]
28
+
29
+ # Describes what passing checks skipped or ignored, plus a one-line summary.
30
+ # : (Array[DriftCheck::Result]) -> String
31
+ def self.report: (Array[DriftCheck::Result]) -> String
32
+
33
+ # : () -> void
34
+ def self.clear: () -> void
35
+
36
+ # : () -> Hash[singleton(StructuredParams::Params), Hash[untyped, untyped]]
37
+ private def self.auto_discovered_checks: () -> Hash[singleton(StructuredParams::Params), Hash[untyped, untyped]]
38
+
39
+ # : () -> Array[Array[untyped]]
40
+ private def self.entries: () -> Array[Array[untyped]]
41
+
42
+ # : (singleton(StructuredParams::Params) | String) -> singleton(StructuredParams::Params)
43
+ private def self.resolve: (singleton(StructuredParams::Params) | String) -> singleton(StructuredParams::Params)
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,7 @@
1
+ # Generated from lib/structured_params/openapi/version.rb with RBS::Inline
2
+
3
+ module StructuredParams
4
+ module OpenAPI
5
+ VERSION: String
6
+ end
7
+ end
@@ -0,0 +1,14 @@
1
+ # Generated from lib/structured_params/openapi.rb with RBS::Inline
2
+
3
+ module StructuredParams
4
+ # OpenAPI drift check for StructuredParams classes.
5
+ module OpenAPI
6
+ RAKE_TASK_PATH: String
7
+
8
+ # Defers loading the drift-check rake task until Rails actually loads rake tasks,
9
+ # so requiring this gem never depends on the Rake DSL being mixed into +main+
10
+ # (e.g. `rails runner`, `rails console`, app boot).
11
+ class Railtie < Rails::Railtie
12
+ end
13
+ end
14
+ end
metadata ADDED
@@ -0,0 +1,82 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: structured_params-openapi
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Mizuki Yamamoto
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: structured_params
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: 0.9.5
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: 0.9.5
26
+ description: Detects differences between OpenAPI request schemas and StructuredParams
27
+ classes, with a Rake task for CI, JSON Pointer and component naming conventions,
28
+ and support for several OpenAPI documents.
29
+ email:
30
+ - mizuki-y@syati.info
31
+ executables: []
32
+ extensions: []
33
+ extra_rdoc_files: []
34
+ files:
35
+ - CHANGELOG.md
36
+ - LICENSE.txt
37
+ - README.md
38
+ - README_ja.md
39
+ - docs/drift-check.md
40
+ - docs/drift-check_ja.md
41
+ - lib/structured_params/openapi.rb
42
+ - lib/structured_params/openapi/configuration.rb
43
+ - lib/structured_params/openapi/drift_check.rb
44
+ - lib/structured_params/openapi/drift_check/issue.rb
45
+ - lib/structured_params/openapi/drift_check/result.rb
46
+ - lib/structured_params/openapi/registry.rb
47
+ - lib/structured_params/openapi/version.rb
48
+ - lib/tasks/structured_params_openapi.rake
49
+ - sig/structured_params/openapi.rbs
50
+ - sig/structured_params/openapi/configuration.rbs
51
+ - sig/structured_params/openapi/drift_check.rbs
52
+ - sig/structured_params/openapi/drift_check/issue.rbs
53
+ - sig/structured_params/openapi/drift_check/result.rbs
54
+ - sig/structured_params/openapi/registry.rbs
55
+ - sig/structured_params/openapi/version.rbs
56
+ homepage: https://github.com/Syati/structured_params
57
+ licenses:
58
+ - MIT
59
+ metadata:
60
+ homepage_uri: https://github.com/Syati/structured_params/tree/main/structured_params-openapi#readme
61
+ changelog_uri: https://github.com/Syati/structured_params/blob/main/structured_params-openapi/CHANGELOG.md
62
+ bug_tracker_uri: https://github.com/Syati/structured_params/issues
63
+ source_code_uri: https://github.com/Syati/structured_params/tree/main/structured_params-openapi
64
+ rubygems_mfa_required: 'true'
65
+ rdoc_options: []
66
+ require_paths:
67
+ - lib
68
+ required_ruby_version: !ruby/object:Gem::Requirement
69
+ requirements:
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ version: 3.2.0
73
+ required_rubygems_version: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - ">="
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ requirements: []
79
+ rubygems_version: 3.6.9
80
+ specification_version: 4
81
+ summary: OpenAPI drift check for StructuredParams.
82
+ test_files: []