-
Notifications
You must be signed in to change notification settings - Fork 477
Add OpenAPI 3.0/3.1 support with backward-compatible architecture #969
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
Introduce version-agnostic DTO classes that serve as the foundation for supporting multiple OpenAPI versions (2.0, 3.0, 3.1). New classes added: - ApiModel::Spec - Root specification container - ApiModel::Info - API metadata - ApiModel::Server - Server definitions (OAS3) - ApiModel::PathItem - Path with operations - ApiModel::Operation - HTTP operation definitions - ApiModel::Parameter - Query/path/header parameters - ApiModel::RequestBody - Request body for OAS3 - ApiModel::Response - Response definitions - ApiModel::MediaType - Content-type wrappers - ApiModel::Schema - JSON Schema representation - ApiModel::SecurityScheme - Security definitions - ApiModel::Tag - Operation grouping - ApiModel::Components - Schema/security container Each class includes to_h and to_swagger2_h methods for version-specific output generation.
Model builders parse Grape routes and Swagger output to create version-agnostic ApiModel objects. This enables clean conversion between spec formats. New builders: - SchemaBuilder - Creates Schema objects from type definitions - ParameterBuilder - Builds Parameter objects from route params - ResponseBuilder - Creates Response objects from route responses - OperationBuilder - Builds Operation objects from route definitions - SpecBuilder - Main entry point, builds complete Spec from Swagger hash The SpecBuilder.build_from_swagger_hash method enables gradual migration by converting existing Swagger 2.0 output to the API Model.
Exporters convert ApiModel::Spec objects to version-specific output formats. New exporters: - Base - Abstract base class with common utilities - Swagger2 - Produces Swagger 2.0 output (validates refactor) - OAS30 - Produces OpenAPI 3.0 output with: - requestBody instead of body parameters - components/schemas instead of definitions - servers array instead of host/basePath/schemes - Schema wrapper for parameters - Content wrapper for responses - nullable: true for nullable fields - OAS31 - Extends OAS30 with: - type: ["string", "null"] instead of nullable keyword - License identifier support (SPDX) Factory method GrapeSwagger::Exporter.for_version(version) returns the appropriate exporter class.
Add openapi_version option to add_swagger_documentation: - nil or '2.0' - Swagger 2.0 output (default, backward compatible) - '3.0' - OpenAPI 3.0 output - '3.1' - OpenAPI 3.1 output Example usage: add_swagger_documentation(openapi_version: '3.0') When openapi_version is specified, the Swagger 2.0 output is converted to the API Model and then exported to the requested OpenAPI version.
- Handle $ref in schema_builder.build_from_definition to properly extract model names from reference paths - Ensure body parameters with schema refs get proper content wrappers - Fix schema building for all param types (not just non-body) - Add comprehensive tests for OAS 3.0 and 3.1 output The tests verify: - Swagger 2.0 default behavior unchanged - OAS 3.0 uses requestBody with content wrappers - OAS 3.1 produces correct version output - Refs are properly converted to #/components/schemas/ - Parameters include schema wrappers
- Add OpenAPI 3.0/3.1 support mention in Swagger-Spec section - Add openapi_version to configuration table of contents - Document available options: nil (Swagger 2.0), '3.0', '3.1' - Explain key differences when using OpenAPI 3.x
Tests added: - File upload: verifies type: file → format: binary conversion - Security schemes: apiKey, basic→http, OAuth2 flow conversion - Response headers: schema wrapper for OAS3 headers - Nullable fields: OAS 3.0 nullable vs OAS 3.1 type array - Nested entities: $ref path conversion to components/schemas Fixes: - Preserve empty arrays in compact_hash (security: [], scopes: []) - Export security even when empty (to explicitly disable security) - Handle security requirements at global and operation levels
OpenAPI 3.0 does not support `in: formData` parameters. This converts formData parameters to requestBody with appropriate content types: - application/x-www-form-urlencoded for regular form fields - multipart/form-data when file uploads are present The schema includes all form fields as properties with their types, descriptions, and required markers preserved.
The tests expected 'required' arrays in entity definitions but the entities didn't specify 'required: true' in their documentation. Added 'required: true' to entity exposures to match test expectations and updated the parameter required expectation accordingly.
- Add webhooks support with export in OAS31 exporter - Add jsonSchemaDialect field for OAS 3.1 - Add $schema keyword support for schemas - Add contentMediaType/contentEncoding for binary data - Support license identifier (SPDX) in hash format - Fix nested_entities_spec.rb test pollution issues
- 539: Add required: true to Element entity exposures to match test expectations - 962: Fix expectation - hidden properties shouldn't appear in required array
- Complete API test with users, files endpoints - Tests for path/query/body parameter separation - Tests for requestBody, security, components/schemas - Tests for file uploads with multipart/form-data - Tests for OAS 3.0 vs 3.1 differences - Validation that no Swagger 2.0 artifacts remain
- Add json_schema_dialect option for specifying JSON Schema dialect - Add webhooks option for defining webhook endpoints (OAS 3.1 only) - Both options are ignored when using OAS 3.0 for compatibility - Include comprehensive tests for all configuration scenarios
- Add json_schema_dialect option documentation - Add webhooks option documentation with examples - Update table of contents with new options
- Add 'null' to PRIMITIVE_MAPPINGS for explicit null type - Add NilClass to RUBY_TYPE_MAPPINGS for Ruby nil type - Handle type: null in OAS 3.0 by converting to nullable: true - OAS 3.1 outputs type: null directly per JSON Schema 2020-12
Adapted from grape-swagger_rebased branch to verify: - Body parameters are converted to requestBody - Path parameters remain separate from requestBody - Required fields are properly marked - Entity parameters create proper schema references
Test coverage for array parameters in OAS 3.0: - Grouped array parameters with requestBody - Typed group parameters - Array of primitive types (String, Integer) - Mixed object and array parameters - Array of type in form - Array of entities with schema references - Tests both array_use_braces options (true/false)
Test coverage for type/format mappings in OAS 3.0: - Integer -> integer/int32 - Numeric -> integer/int64 - Float -> number/float - BigDecimal -> number/double - String, Symbol -> string - Date -> string/date - DateTime, Time -> string/date-time - Boolean -> boolean - File -> string/binary - JSON -> object Also added 'json' -> object mapping to PRIMITIVE_MAPPINGS in SchemaBuilder to properly handle the lowercase 'json' type that comes from DataType.call.
- Fix duplicate method definition for Parameter#required - Replace case statements with hash lookups to avoid duplicate branches - Fix unused method arguments - Fix line length issues - Move constants outside private section - Apply auto-corrections for style offenses Remaining offenses are Metrics-related (complexity, method length) which require more significant refactoring.
Extract helper methods from large to_h and export_* methods to reduce cyclomatic complexity, ABC size, and method length: - api_model/schema.rb: Split to_h into add_*_fields helpers - api_model/operation.rb: Split to_h and to_swagger2_h - api_model/components.rb: Split to_h into add_*_components - api_model/parameter.rb: Split to_swagger2_h - api_model/spec.rb: Split to_swagger2_h - exporter/oas30.rb: Refactor export_schema into build_schema_output - exporter/oas31.rb: Leverage OAS30 helpers, add 3.1-specific methods - exporter/swagger2.rb: Refactor export, export_parameter, export_schema - model_builder/schema_builder.rb: Split build_from_param - model_builder/spec_builder.rb: Split build_operation, build_response - doc_methods.rb: Auto-correct style offenses
…on schemas - Port param_type_body_nested_spec.rb to OAS3 format - Tests nested Hash params with requestBody schema - Tests arrays of nested objects - Tests additionalProperties support - Port response_with_models_spec.rb to OAS3 format - Tests response schemas using #/components/schemas/ refs - Tests failure codes with and without models - Tests default_response handling - Add composition schema support (oneOf/anyOf) - SchemaBuilder now parses oneOf and anyOf in definitions - OAS30 exporter correctly exports all composition types - Add comprehensive unit tests for composition schemas - Fix SchemaBuilder for nested object properties - Add apply_object_from_param for proper object handling - Preserve properties, required arrays, additionalProperties
…llbacks Additional Properties: - Add OAS3 test for additional_properties with various types - Fix additionalProperties $ref conversion from #/definitions to #/components/schemas Discriminator: - Add discriminator parsing in SchemaBuilder - Add comprehensive tests for discriminator with allOf inheritance - Support both string and object discriminator formats Links and Callbacks: - Add callbacks export in operation - Add links and callbacks in components export - Update components_empty? to check for links/callbacks - Add comprehensive tests for response links and operation callbacks All 727 tests pass.
Nullable handling: - Add document_nullable to ParseParams to extract nullable from documentation - Add nullable to MoveParams.property_keys for definition preservation - Add nullable to SchemaBuilder.apply_param_constraints for schema flow - OAS 3.0: outputs nullable: true - OAS 3.1: outputs type: ["string", "null"] New OAS3 specs ported from Swagger 2.0: - param_type_spec.rb: query, path, header params with schema wrapper - extensions_spec.rb: x- extensions at root and operation level - detail_spec.rb: summary and description handling - status_codes_spec.rb: HTTP status code handling - nullable_handling_spec.rb: nullable integration tests
- docs/openapi_3_implementation.md: Comprehensive guide covering architecture, API model layer, exporters, model builders, OAS 3.0 vs 3.1 differences, and test coverage - docs/openapi_3_changelog.md: Change summary with all new files, modified files, features implemented, and usage examples
Switch OAS3 generation from conversion-based approach to direct building from Grape routes. This preserves all route options and fixes nested entity handling. Key changes: - Route OAS3 requests through DirectSpecBuilder instead of converting from Swagger 2.0 output - Pass DirectSpecBuilder instance to model parsers so nested entities can call expose_params_from_model for proper registration - Add schema_ref_with_description using allOf pattern for refs with descriptions - Handle canonical_name in additionalProperties export All 771 tests pass (293 OAS3 + 220 Swagger 2.0 + issue specs).
- Update data flow section to show direct build path for OAS 3.x - Add DirectSpecBuilder documentation in Model Builders section - Update changelog to list direct_spec_builder.rb as primary
Reorganize namespace for better clarity and alignment with OpenAPI spec: Module renames: - GrapeSwagger::ApiModel::Spec → GrapeSwagger::OpenAPI::Document - GrapeSwagger::ModelBuilder::DirectSpecBuilder → GrapeSwagger::OpenAPI::Builder::FromRoutes - GrapeSwagger::ModelBuilder::SpecBuilder → GrapeSwagger::OpenAPI::Builder::FromHash - All ApiModel classes moved to OpenAPI namespace Directory structure: - lib/grape-swagger/api_model/ → lib/grape-swagger/openapi/ - lib/grape-swagger/model_builder/ → lib/grape-swagger/openapi/builder/ Updated references in: - lib/grape-swagger.rb - lib/grape-swagger/doc_methods.rb - lib/grape-swagger/exporter/*.rb - spec/openapi_v3/*.rb - docs/*.md
Also fix Contributing section to use new class names.
- Fix indentation in from_routes.rb (was 4 spaces, now 6) - Fix Layout/EndAlignment - Fix Style/HashEachMethods - Fix Layout/EmptyLineAfterGuardClause - Fix Style/GuardClause and Style/IfUnlessModifier - Fix Style/RescueModifier (use begin/rescue/end) - Fix various trailing whitespace and line length issues Remaining offenses are Metrics issues (class/method length, complexity) that require larger refactoring to address.
Split large classes/modules into smaller, focused units to address rubocop Metrics offenses: **oas30.rb (357→326 lines)** - Extract SchemaExporter module with schema export methods - Extract SchemaFields module with field helper methods **schema_builder.rb** - Extract build_from_definition into smaller helper methods - Reduce cyclomatic complexity from 19 to under limit **from_routes.rb (754→448 lines)** - Extract ParameterBuilder module for parameter handling - Extract ParamSchemaBuilder module for schema building - Extract RequestBodyBuilder module for request body handling - Extract ResponseBuilder module for response building All 771 tests pass (293 OAS3, 478 Swagger 2.0). No rubocop offenses in openapi/builder and exporter directories.
When a property is marked as hidden via documentation: { hidden: true },
grape-swagger-entity correctly removes it from the properties hash but
incorrectly leaves it in the required array. This causes invalid schemas
where a property is marked required but doesn't exist.
This fix filters the required array to only include properties that
actually exist in the properties hash.
- Extract webhook building logic to DocMethods::Webhooks module - Extract output building logic to DocMethods::OutputBuilder module - Remove dead code (convert_to_openapi3 method) - Fix consumes string handling in parameter location detection: When consumes is a string (e.g., 'multipart/form-data'), normalize to array before checking for form content types - Reduces DocMethods module from 181 to under 100 lines - All rubocop offenses resolved
- Change route.attributes.success to route.options[:success] since Grape 3.1 removed the `alias attributes options` from BaseRoute - Update RouteHelper in specs to support Grape 3.1's new Route constructor that takes a Pattern object with keyword arguments - Maintain backward compatibility with Grape 2.0 and 3.0
The TypedDefinition entity test was using Swagger 2.0 expected values
('type: file' and 'type: json') but OAS 3.0 correctly converts these to:
- file → type: string, format: binary
- json → type: object
Added OAS 3.0 specific expected values for the entity parser test context.
- Add global schema_name_generator callback for custom naming - Remove Grape::Entity-specific code from param_schema_builder - Use has_model_parser? for parser detection instead of class check - Support OpenAPI::Schema objects from model parsers - Simplify DataType.parse_entity_name to use callback This enables creating new parsers (e.g., grape-swagger-dry) without entity-specific assumptions in grape-swagger core.
- Simplify build_tags with flat_map instead of nested loops - Extract expose_ref_if_needed method for cleaner ref handling - Use case/when for expose_nested_refs type dispatch - Consolidate nullable flag setting in parameter_builder - Handle mixed Schema/Hash types in Schema#to_h methods
- Rename FromRoutes to Builder::Spec as main builder class - Move helper modules to builder/concerns/ directory - Consolidate documentation into single docs/openapi_3.md file - Update architecture diagram to reflect new structure
Use x-nullable instead of nullable for Swagger 2.0 output since nullable is not a valid Swagger 2.0 property. OpenAPI 3.x handles nullable separately through the Builder::Spec path. Add test coverage for Swagger 2.0 nullable handling.
The identifier field is only valid in OpenAPI 3.1, not Swagger 2.0. The OAS 3.x path already handles identifier correctly via Builder::Spec.
Support both syntaxes for nullable fields:
- documentation: { nullable: true } (recommended)
- documentation: { x: { nullable: true } } (backward compatible)
This allows users migrating from Swagger 2.0 to OAS 3.x to keep their
existing x: { nullable: true } syntax and get proper nullable output:
- OAS 3.0: nullable: true
- OAS 3.1: type: ["string", "null"]
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR adds comprehensive OpenAPI 3.0 and 3.1 support to grape-swagger through a well-architected layered approach. The implementation introduces an intermediate OpenAPI model layer that serves as a bridge between Grape routes and multiple output formats (Swagger 2.0, OAS 3.0, and OAS 3.1), maintaining full backward compatibility.
Key Changes:
- New OpenAPI model layer with version-agnostic data structures (Document, Schema, Operation, Parameter, etc.)
- Builder architecture that constructs OpenAPI models directly from Grape routes
- Version-specific exporters (Swagger2, OAS30, OAS31) that serialize models to target formats
- Support for OAS 3.x features: requestBody, components/schemas, servers array, nullable handling
- OAS 3.1-specific features: jsonSchemaDialect, webhooks, type arrays for nullable, license identifier
Reviewed changes
Copilot reviewed 70 out of 70 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| spec/swagger_v2/reference_entity_spec.rb | Added required: true to entity documentation for test accuracy |
| spec/swagger_v2/nullable_spec.rb | New test for Swagger 2.0 x-nullable extension handling |
| spec/support/route_helper.rb | Added Grape 3.1+ compatibility for Route constructor |
| spec/openapi_v3/*.rb | Comprehensive test suite for OAS 3.0/3.1 features |
| lib/grape-swagger/openapi/*.rb | Core OpenAPI model classes (Document, Schema, Operation, etc.) |
| lib/grape-swagger/openapi/builder/*.rb | Builder architecture for constructing OpenAPI models from Grape |
| lib/grape-swagger/exporter/*.rb | Version-specific exporters with shared schema export logic |
| spec/issues/*.rb | Fixed test expectations for entity requirements |
| lib/grape-swagger/doc_methods/move_params.rb | Added nullable to property_keys |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Split Builder::Spec into focused concerns: - InfoBuilder: API info and license configuration - ServerBuilder: Server URLs from host/basePath/schemes - SecurityBuilder: Security schemes and OAuth flows - OperationBuilder: Operation details (summary, produces, consumes) - TagBuilder: Tag collection and merging This improves maintainability by organizing related methods together.
Summary
This PR adds comprehensive OpenAPI 3.0 and 3.1 support to grape-swagger through a new layered architecture, while maintaining full backward compatibility with existing Swagger 2.0 functionality.
This implementation follows the approach outlined in #928 (comment):
Instead of duplicating the Swagger 2.0 code and modifying it for OAS3 (which creates maintenance challenges with "two separate codebases doing almost the same job"), this PR introduces:
Document,Schema,Operation,Parameter, etc.OpenAPI::Builder::Specthat populates the model from Grape routesSwagger2,OAS30,OAS31that serialize the model to the target formatThis architecture makes it easy to maintain both versions and leverage OpenAPI 3.x schema composition benefits.
Background
This architecture was initially prototyped in grape-oas as a standalone gem to validate the approach of using intermediate structures for multi-version OpenAPI support. This PR integrates that proven pattern directly into grape-swagger.
Usage
Key Features
OpenAPI 3.x Support
$refpathsOAS 3.1 Specific Features
jsonSchemaDialectconfiguration optionwebhookssupporttype: ["string", "null"]for nullable (instead ofnullable: true)identifierfield (SPDX)Nullable Handling
Both syntaxes work across all versions:
documentation: { nullable: true }x-nullablenullable: truetype: ["string", "null"]documentation: { x: { nullable: true } }x-nullablenullable: truetype: ["string", "null"]Architecture
This separation enables:
Backward Compatibility
x: { nullable: true }syntax migrates seamlessly to OAS 3.xx-nullableextension (notnullable)identifierexcluded from Swagger 2.0 outputTest Plan
x: { nullable: true }Future Enhancements
With this architecture in place, future work could include: