Add parameter-level annotation support
Overview
As part of the OpenAPI Annotations - REST Parameters epic, the gem needs
to emit OpenAPI extensions on individual parameters, the way it already emits
them on operations via config.annotations.
This is the foundational issue for the epic. Everything else depends on the release it produces.
Background
The gem already has a route-level annotation pipeline: config.annotations maps
route_setting keys to extension names, OperationConverter#extract_annotations
reads them off options[:settings], and Operation#to_h injects them at the top
level of the operation object.
There is no parameter equivalent. Two output paths need one, because Grape parameters land in two different OpenAPI objects:
- Query and path parameters →
Models::Parameter#to_h, whose output hash is fixed and has no extension slot. - POST/PUT body parameters →
Models::RequestBody::ParameterSchema#build, which becomes a property inside aRequestBody_*component schema.
OpenAPI 3.0 permits x- extensions on both the Parameter Object and the Schema
Object.
The source of the annotation is Grape's documentation: hash. Grape's
Validations::AttributesDoc#extract_details deletes it from the validation options
and stores it verbatim, so it arrives at
route.options[:params][key][:documentation]. The gem already reads
options.dig(:documentation, :example) and :default from it.
Proposal
1. Add the configuration
Configuration: add parameter_annotations to attr_accessor, defaulting to {}
in initialize so the change is inert for callers that do not set it.
config.parameter_annotations = { tier: 'x-gitlab-tier', role: 'x-gitlab-role' }Keys are keys within a parameter's documentation: hash; values are the emitted
extension names.
2. Add a shared concern
New Concerns::ParameterAnnotatable, mirroring OperationConverter#extract_annotations:
def parameter_annotations_for(options)
documentation = options[:documentation]
return {} unless documentation.is_a?(Hash)
config = Gitlab::GrapeOpenapi.configuration
documentation.keys.select { |k| config.parameter_annotations.key?(k) }
.to_h { |k| [config.parameter_annotations[k], documentation[k].to_s] }
endThe is_a?(Hash) guard is required, not defensive:
OperationConverter#inject_missing_path_parameters synthesises
Models::Parameter.new(placeholder, options: { required: true }, ...) for path
placeholders with no local declaration, and those have no :documentation key.
3. Emit on query and path parameters
Models::Parameter: add attr_accessor :annotations, and in to_h apply them after
.compact, exactly as Operation#to_h does:
annotations&.each { |k, v| result[k] = v }Converters::ParameterConverter#convert: include the concern and set
param.annotations = parameter_annotations_for(options).
4. Emit on request-body parameters
Models::RequestBody::ParameterSchema#build: merge the annotations into
built_schema at the single tail point alongside apply_allow_blank and
apply_limit!. Merging there rather than inside each build_* branch covers every
shape — basic, enum, range, array, oneOf union, nested hash and nested array — in
one place, and nested parameters inherit it for free because
build_nested_hash_schema and build_nested_array_schema recurse through the same
build.
5. Release
Bump to 0.4.0, update CHANGELOG.md and the README.
Notes
- No registry change is needed.
RequestBodyRegistry#registerhashes the whole normalised schema, so adding an extension to a property changes the hash and the dedup stays correct. documentation:is shared with grape-swagger, which generatesopenapi_v2.yamlfrom the same declarations, but an unrecognised key is safe:GrapeSwagger::DocMethods::ParseParams.callbuilds its output from an explicit whitelist and never splats the settings hash, andExtensions.add_extensions_to_rootonly fires when the hash has an:xkey. A key namedxmust therefore be avoided, since that spelling would leak into the v2 spec.
Acceptance Criteria
-
config.parameter_annotationsadded, defaulting to{} - A query parameter with a mapped
documentation:key emits the extension on its Parameter Object - A path parameter behaves the same
- A request-body parameter emits the extension on its property schema
- A nested body parameter (
type: Hashwithparams) emits it on the nested property - A synthesised path parameter with no
documentation:does not raise - Unmapped
documentation:keys (example,default,type) are unaffected - Request-body schema dedup still collapses identical schemas and separates schemas that differ only by annotation
- Specs cover all of the above; CHANGELOG updated; 0.4.0 released
Related
- Parent epic: OpenAPI Annotations - REST Parameters