From 062c49cbabd2cd8e1f573990be79586fdaf3f83c Mon Sep 17 00:00:00 2001 From: snhrm Date: Thu, 29 Oct 2026 23:22:54 +0900 Subject: [PATCH 1/5] Support Ruby 3.3, 3.4, 4.0 and Rails 7.2, 8.0, 8.1 - Relax activesupport upper bound to < 8.2 - Drop bundler ~> 2.0 dev dependency, which conflicts with Bundler 4 bundled with Ruby 4.0 - Test Ruby 3.3, 3.4, 4.0 and Rails 7.0 to 8.1 on CI - Remove Code Climate coverage upload, whose API was shut down --- .github/workflows/test.yml | 16 ++++++++-------- .ruby-version | 2 +- README.md | 2 +- .../{rails-6.1.gemfile => rails-7.1.gemfile} | 2 +- gemfiles/rails-7.2.gemfile | 8 ++++++++ gemfiles/rails-8.0.gemfile | 8 ++++++++ open_api.gemspec | 3 +-- 7 files changed, 28 insertions(+), 13 deletions(-) rename gemfiles/{rails-6.1.gemfile => rails-7.1.gemfile} (84%) create mode 100644 gemfiles/rails-7.2.gemfile create mode 100644 gemfiles/rails-8.0.gemfile diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6b89e86..dff85a7 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -4,13 +4,17 @@ jobs: test: strategy: matrix: - ruby: ["3.0", "3.1", "3.2"] + ruby: ["3.3", "3.4", "4.0"] gemfile: [Gemfile] include: - - ruby: "3.2" + - ruby: "3.3" + gemfile: "gemfiles/rails-8.0.gemfile" + - ruby: "3.3" + gemfile: "gemfiles/rails-7.2.gemfile" + - ruby: "3.3" + gemfile: "gemfiles/rails-7.1.gemfile" + - ruby: "3.3" gemfile: "gemfiles/rails-7.0.gemfile" - - ruby: "3.2" - gemfile: "gemfiles/rails-6.1.gemfile" env: BUNDLE_GEMFILE: "${{ matrix.gemfile }}" @@ -23,7 +27,3 @@ jobs: ruby-version: ${{ matrix.ruby }} bundler-cache: true - run: bundle exec rake - - name: Send coverage - uses: paambaati/codeclimate-action@v5 - env: - CC_TEST_REPORTER_ID: 618e64226e212a5824797037cb1a71487cdbfb3aa664bddc42a7189fb027f73a diff --git a/.ruby-version b/.ruby-version index be94e6f..7919852 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -3.2.2 +4.0.9 diff --git a/README.md b/README.md index 7aae0f3..08fbdc9 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# OpenApi [![Gem Version](https://badge.fury.io/rb/open_api.svg)](https://badge.fury.io/rb/open_api) [![Build Status](https://travis-ci.org/wantedly/open_api.svg?branch=master)](https://travis-ci.org/wantedly/open_api) [![Maintainability](https://api.codeclimate.com/v1/badges/dc4713a3cb67f6edce65/maintainability)](https://codeclimate.com/github/wantedly/open_api/maintainability) [![Test Coverage](https://api.codeclimate.com/v1/badges/dc4713a3cb67f6edce65/test_coverage)](https://codeclimate.com/github/wantedly/open_api/test_coverage) +# OpenApi [![Gem Version](https://badge.fury.io/rb/open_api.svg)](https://badge.fury.io/rb/open_api) [![Build Status](https://travis-ci.org/wantedly/open_api.svg?branch=master)](https://travis-ci.org/wantedly/open_api) `open_api` provides PORO and serializers for OpenAPI and it supports OpenAPI v3.0.1. diff --git a/gemfiles/rails-6.1.gemfile b/gemfiles/rails-7.1.gemfile similarity index 84% rename from gemfiles/rails-6.1.gemfile rename to gemfiles/rails-7.1.gemfile index c43beec..4922d77 100644 --- a/gemfiles/rails-6.1.gemfile +++ b/gemfiles/rails-7.1.gemfile @@ -5,4 +5,4 @@ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } # Specify your gem's dependencies in open_api.gemspec gemspec path: '..' -gem 'activesupport', '~> 6.1.0' +gem 'activesupport', '~> 7.1.0' diff --git a/gemfiles/rails-7.2.gemfile b/gemfiles/rails-7.2.gemfile new file mode 100644 index 0000000..e13500b --- /dev/null +++ b/gemfiles/rails-7.2.gemfile @@ -0,0 +1,8 @@ +source "https://rubygems.org" + +git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } + +# Specify your gem's dependencies in open_api.gemspec +gemspec path: '..' + +gem 'activesupport', '~> 7.2.0' diff --git a/gemfiles/rails-8.0.gemfile b/gemfiles/rails-8.0.gemfile new file mode 100644 index 0000000..5bd7ab5 --- /dev/null +++ b/gemfiles/rails-8.0.gemfile @@ -0,0 +1,8 @@ +source "https://rubygems.org" + +git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } + +# Specify your gem's dependencies in open_api.gemspec +gemspec path: '..' + +gem 'activesupport', '~> 8.0.0' diff --git a/open_api.gemspec b/open_api.gemspec index c23ed81..79cad3b 100644 --- a/open_api.gemspec +++ b/open_api.gemspec @@ -21,8 +21,7 @@ Gem::Specification.new do |spec| spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } spec.require_paths = ["lib"] - spec.add_dependency "activesupport", ['>= 5.0', '< 7.2'] - spec.add_development_dependency "bundler", "~> 2.0" + spec.add_dependency "activesupport", ['>= 5.0', '< 8.2'] spec.add_development_dependency "rake", "~> 13.0" spec.add_development_dependency "rspec", "~> 3.0" spec.add_development_dependency "pry" From 7c33197245c2a99774303f3e31258208c28d3120 Mon Sep 17 00:00:00 2001 From: snhrm Date: Thu, 29 Oct 2026 23:22:54 +0900 Subject: [PATCH 2/5] Update changelog and bump version --- CHANGELOG.md | 6 ++++++ lib/open_api/version.rb | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 42a70a7..844b697 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ ## Unreleased +## 0.6.3 + +- Bump Ruby and Rails + - Supported Rubies: 3.3, 3.4, 4.0 + - Supported Rails: 7.0, 7.1, 7.2, 8.0, 8.1 + ## 0.6.2 - Reflect ownership transfer https://github.com/wantedly/open_api/pull/16 diff --git a/lib/open_api/version.rb b/lib/open_api/version.rb index 49b9c45..fc53e72 100644 --- a/lib/open_api/version.rb +++ b/lib/open_api/version.rb @@ -1,3 +1,3 @@ module OpenApi - VERSION = "0.6.2" + VERSION = "0.6.3" end From 3c3976b0e28c1a42f6336c45a632aeadf76c475d Mon Sep 17 00:00:00 2001 From: snhrm Date: Fri, 30 Oct 2026 15:49:11 +0900 Subject: [PATCH 3/5] Add PR link to changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 844b697..025d43d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ ## 0.6.3 -- Bump Ruby and Rails +- Bump Ruby and Rails https://github.com/wantedly/open_api/pull/18 - Supported Rubies: 3.3, 3.4, 4.0 - Supported Rails: 7.0, 7.1, 7.2, 8.0, 8.1 From 4994d18327f38707131d33d55d614c684e2e22a8 Mon Sep 17 00:00:00 2001 From: snhrm Date: Fri, 30 Oct 2026 17:19:19 +0900 Subject: [PATCH 4/5] Require forwardable explicitly Paths, Responses, Callback and SecurityRequirement extend Forwardable, but nothing required it. psych up to 5.1.0 happened to load it, so requiring open_api after yaml (e.g. right after rails) worked on Ruby 3.2. psych 5.1.1+ (bundled with Ruby 3.3+) no longer loads it, which makes require "open_api" raise NameError unless something else loaded it first. The spec runs in a separate process because simplecov, loaded by spec_helper, requires forwardable and hides the problem. --- CHANGELOG.md | 1 + lib/open_api.rb | 1 + spec/open_api_spec.rb | 8 ++++++++ 3 files changed, 10 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 025d43d..38c2490 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## 0.6.3 +- Require `forwardable` explicitly to fix `NameError: uninitialized constant OpenApi::Paths::Forwardable` when nothing has loaded it beforehand (e.g. `require "open_api"` right after `require "rails"` on Ruby 3.3+, whose psych no longer loads it) https://github.com/wantedly/open_api/pull/18 - Bump Ruby and Rails https://github.com/wantedly/open_api/pull/18 - Supported Rubies: 3.3, 3.4, 4.0 - Supported Rails: 7.0, 7.1, 7.2, 8.0, 8.1 diff --git a/lib/open_api.rb b/lib/open_api.rb index 471f7b0..74f5b5f 100644 --- a/lib/open_api.rb +++ b/lib/open_api.rb @@ -1,4 +1,5 @@ # Dependant libraries +require "forwardable" require "active_support" require "active_support/core_ext/hash" require "active_support/core_ext/string/inflections" diff --git a/spec/open_api_spec.rb b/spec/open_api_spec.rb index 3fca69c..0d273f2 100644 --- a/spec/open_api_spec.rb +++ b/spec/open_api_spec.rb @@ -1,8 +1,16 @@ +require "open3" + RSpec.describe OpenApi do it "has a version number" do expect(OpenApi::VERSION).not_to be nil end + it "can be required without other libraries loaded beforehand" do + lib = File.expand_path("../lib", __dir__) + output, status = Open3.capture2e(RbConfig.ruby, "-rbundler/setup", "-I", lib, "-e", 'require "open_api"') + expect(status).to be_success, output + end + it "creates simple spec" do spec = OpenApi::Specification.new( openapi: "3.0.1", From 7ad9e95c33f2326861b1ef16dae0aabd607d29bc Mon Sep 17 00:00:00 2001 From: snhrm Date: Fri, 30 Oct 2026 17:19:19 +0900 Subject: [PATCH 5/5] Add specs for annotator-shaped YAML and equality, and test Ruby 4.0 with Rails 7.0 --- .github/workflows/test.yml | 2 + spec/open_api/equatable_as_content_spec.rb | 12 ++ .../serializers/yaml_serializer_spec.rb | 148 ++++++++++++++++++ 3 files changed, 162 insertions(+) create mode 100644 spec/open_api/equatable_as_content_spec.rb diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index dff85a7..a6620fd 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -15,6 +15,8 @@ jobs: gemfile: "gemfiles/rails-7.1.gemfile" - ruby: "3.3" gemfile: "gemfiles/rails-7.0.gemfile" + - ruby: "4.0" + gemfile: "gemfiles/rails-7.0.gemfile" env: BUNDLE_GEMFILE: "${{ matrix.gemfile }}" diff --git a/spec/open_api/equatable_as_content_spec.rb b/spec/open_api/equatable_as_content_spec.rb new file mode 100644 index 0000000..14396ce --- /dev/null +++ b/spec/open_api/equatable_as_content_spec.rb @@ -0,0 +1,12 @@ +RSpec.describe OpenApi::EquatableAsContent do + describe "#==" do + it "returns true for objects with the same content" do + expect(OpenApi::Info.new(title: "Awesome API", version: "1.0.0")).to eq OpenApi::Info.new(title: "Awesome API", version: "1.0.0") + end + + it "returns false for objects with different content" do + expect(OpenApi::Info.new(title: "Awesome API", version: "1.0.0")).not_to eq OpenApi::Info.new(title: "Another API", version: "1.0.0") + expect(OpenApi::Info.new(title: "Awesome API", version: "1.0.0")).not_to eq OpenApi::Info.new(title: "Awesome API", version: "2.0.0") + end + end +end diff --git a/spec/open_api/serializers/yaml_serializer_spec.rb b/spec/open_api/serializers/yaml_serializer_spec.rb index 6463863..98aa69e 100644 --- a/spec/open_api/serializers/yaml_serializer_spec.rb +++ b/spec/open_api/serializers/yaml_serializer_spec.rb @@ -58,6 +58,154 @@ EOL ) end + + context "with a specification built in the same way as open_api_annotator" do + let(:specification) do + paths = OpenApi::Paths.new + paths["/books"] = OpenApi::PathItem.new.tap do |path_item| + path_item.operations["get"] = build_operation( + "Returns an array of Book", + OpenApi::Schema.new(type: "array", items: OpenApi::Reference.new(ref: "#/components/schemas/Book")), + ) + end + paths["/books/{id}"] = OpenApi::PathItem.new.tap do |path_item| + path_item.operations["get"] = build_operation("Returns a Book", OpenApi::Reference.new(ref: "#/components/schemas/Book"), ["id"]) + path_item.operations["patch"] = build_operation("Returns a Book", OpenApi::Reference.new(ref: "#/components/schemas/Book"), ["id"]) + end + paths["/books/count"] = OpenApi::PathItem.new.tap do |path_item| + path_item.operations["get"] = build_operation("Returns a integer", OpenApi::Schema.new(type: :integer, format: :int32)) + end + + schema = OpenApi::Schema.new(type: "object", properties: {}) + schema.properties.merge!( + id: OpenApi::Schema.new(type: :integer, format: nil, nullable: false), + title: OpenApi::Schema.new(type: :string, format: nil, nullable: false), + published_at: OpenApi::Schema.new(type: :string, format: :"date-time", nullable: true), + tags: OpenApi::Schema.new(type: "array", items: OpenApi::Schema.new(type: :string, format: nil, nullable: false)), + authors: OpenApi::Schema.new(type: "array", items: OpenApi::Reference.new(ref: "#/components/schemas/Author"), nullable: false), + cover_image: OpenApi::Reference.new(ref: "#/components/schemas/CoverImage"), + publisher: OpenApi::Schema.new(nullable: true, allOf: [OpenApi::Reference.new(ref: "#/components/schemas/Publisher")]), + ) + schema.required = [:id, :title] + components = OpenApi::Components.new(schemas: {}) + components.schemas["Book"] = schema + + OpenApi::Specification.new( + openapi: "3.0.1", + info: OpenApi::Info.new(title: "Book API", version: "1"), + paths: paths, + components: components, + ) + end + + def build_operation(description, schema, path_parameter_names = []) + operation = OpenApi::Operation.new(responses: OpenApi::Responses.new) + operation.responses["200"] = OpenApi::Response.new( + description: description, + content: { "application/json" => OpenApi::MediaType.new(schema: schema) }, + ) + path_parameter_names.each do |name| + operation.parameters = [] unless operation.parameters + operation.parameters.push( + OpenApi::Parameter.new(name: name, in: :path, required: true, schema: OpenApi::Schema.new(type: :string)) + ) + end + operation + end + + it "creates yaml string from specification object" do + is_expected.to eq( +<<-EOL +--- +openapi: 3.0.1 +info: + title: Book API + version: '1' +paths: + "/books": + get: + responses: + '200': + description: Returns an array of Book + content: + application/json: + schema: + type: array + items: + "$ref": "#/components/schemas/Book" + "/books/{id}": + get: + responses: + '200': + description: Returns a Book + content: + application/json: + schema: + "$ref": "#/components/schemas/Book" + parameters: + - name: id + in: path + required: true + schema: + type: string + patch: + responses: + '200': + description: Returns a Book + content: + application/json: + schema: + "$ref": "#/components/schemas/Book" + parameters: + - name: id + in: path + required: true + schema: + type: string + "/books/count": + get: + responses: + '200': + description: Returns a integer + content: + application/json: + schema: + type: integer + format: int32 +components: + schemas: + Book: + type: object + properties: + id: + type: integer + title: + type: string + published_at: + nullable: true + type: string + format: date-time + tags: + type: array + items: + type: string + authors: + type: array + items: + "$ref": "#/components/schemas/Author" + cover_image: + "$ref": "#/components/schemas/CoverImage" + publisher: + nullable: true + allOf: + - "$ref": "#/components/schemas/Publisher" + required: + - id + - title +EOL + ) + end + end end describe "#deserialize" do