From 60319cb6bbc9fd120002faa84c1d48344e69083f Mon Sep 17 00:00:00 2001 From: Gleb Tv Date: Thu, 2 Jul 2026 22:57:59 +0300 Subject: [PATCH] Replace ffi-backed HTTP client --- .github/workflows/main.yml | 7 +- .rubocop.yml | 26 +++- Gemfile | 24 ++-- Gemfile.lock | 255 ++++++++++++++++++++----------------- README.md | 10 ++ lib/yookassa/client.rb | 25 ++-- yookassa.gemspec | 4 +- 7 files changed, 209 insertions(+), 142 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 1ee97c4..27145b0 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -14,12 +14,11 @@ jobs: strategy: matrix: ruby: - - 2.6 - - 2.7 - - 3.0 + - "3.3" + - "3.4" steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Set up Ruby uses: ruby/setup-ruby@v1 with: diff --git a/.rubocop.yml b/.rubocop.yml index 3a733b3..9567388 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,6 +1,7 @@ AllCops: - TargetRubyVersion: 2.6 + TargetRubyVersion: 3.3 NewCops: enable + SuggestExtensions: false Style/StringLiterals: Enabled: true @@ -17,4 +18,25 @@ Layout/LineLength: Max: 150 Metrics/BlockLength: - IgnoredMethods: ['describe', 'context', 'shared_examples'] + AllowedMethods: ["describe", "context", "shared_examples"] + +Metrics/MethodLength: + Max: 15 + +Naming/PredicateMethod: + Enabled: false + +Style/KeywordArgumentsMerging: + Enabled: false + +Style/RedundantCurrentDirectoryInPath: + Enabled: false + +Gemspec/AddRuntimeDependency: + Enabled: false + +Gemspec/DeprecatedAttributeAssignment: + Enabled: false + +Gemspec/RequireMFA: + Enabled: false diff --git a/Gemfile b/Gemfile index 25f9d3e..8165ad3 100644 --- a/Gemfile +++ b/Gemfile @@ -5,22 +5,24 @@ source "https://rubygems.org" git_source(:github) { |repo_name| "https://github.com/#{repo_name}" } # Specify your gem's dependencies in yookassa.gemspec +gem "httpclient", github: "glebtv/httpclient", branch: "master" + gemspec group :documentation do - gem "rack" - gem "webrick" - gem "yard" + gem "rack", "~> 3.2" + gem "webrick", "~> 1.9" + gem "yard", "~> 0.9" end group :development, :test do - gem "pry" - gem "pry-byebug", "~> 3.8.0" - gem "rake", "~> 13.0" - gem "rspec", "~> 3.5" - gem "rubocop", "~> 1.22" + gem "pry", "~> 0.16" + gem "pry-byebug", "~> 3.8" + gem "rake", "~> 13.4" + gem "rspec", "~> 3.13" + gem "rubocop", "~> 1.88" gem "rubocop-rake", "~> 0.6.0" - gem "rubocop-rspec", "~> 2.5" - gem "simplecov", "~> 0.16" - gem "webmock", "~> 3.14" + gem "rubocop-rspec", "~> 2.31" + gem "simplecov", "~> 0.22" + gem "webmock", "~> 3.26" end diff --git a/Gemfile.lock b/Gemfile.lock index ddda7f5..55179d1 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,158 +1,183 @@ +GIT + remote: https://github.com/glebtv/httpclient + revision: 28d3d1d4fb428e98cc52f04294fe21717db9b28f + branch: master + specs: + httpclient (2.9.0) + mutex_m + PATH remote: . specs: yookassa (0.2.0) dry-struct dry-validation - http (~> 5.0.1) + httpclient (>= 2.9) GEM remote: https://rubygems.org/ specs: - addressable (2.8.0) - public_suffix (>= 2.0.2, < 5.0) - ast (2.4.2) - byebug (11.1.3) + addressable (2.9.0) + public_suffix (>= 2.0.2, < 8.0) + ast (2.4.3) + bigdecimal (4.1.2) + byebug (13.0.0) + reline (>= 0.6.0) coderay (1.1.3) - concurrent-ruby (1.1.9) - crack (0.4.5) + concurrent-ruby (1.3.7) + crack (1.0.1) + bigdecimal rexml - diff-lcs (1.4.4) - docile (1.4.0) - domain_name (0.5.20190701) - unf (>= 0.0.5, < 1.0.0) - dry-configurable (0.13.0) + diff-lcs (1.6.2) + docile (1.4.1) + dry-configurable (1.4.0) + dry-core (~> 1.0) + zeitwerk (~> 2.6) + dry-core (1.2.0) concurrent-ruby (~> 1.0) - dry-core (~> 0.6) - dry-container (0.9.0) + logger + zeitwerk (~> 2.6) + dry-inflector (1.3.1) + dry-initializer (3.2.0) + dry-logic (1.6.0) + bigdecimal concurrent-ruby (~> 1.0) - dry-configurable (~> 0.13, >= 0.13.0) - dry-core (0.7.1) + dry-core (~> 1.1) + zeitwerk (~> 2.6) + dry-schema (1.16.0) concurrent-ruby (~> 1.0) - dry-inflector (0.2.1) - dry-initializer (3.0.4) - dry-logic (1.2.0) - concurrent-ruby (~> 1.0) - dry-core (~> 0.5, >= 0.5) - dry-schema (1.8.0) - concurrent-ruby (~> 1.0) - dry-configurable (~> 0.13, >= 0.13.0) - dry-core (~> 0.5, >= 0.5) - dry-initializer (~> 3.0) - dry-logic (~> 1.0) - dry-types (~> 1.5) - dry-struct (1.4.0) - dry-core (~> 0.5, >= 0.5) - dry-types (~> 1.5) + dry-configurable (~> 1.0, >= 1.0.1) + dry-core (~> 1.1) + dry-initializer (~> 3.2) + dry-logic (~> 1.6) + dry-types (~> 1.9, >= 1.9.1) + zeitwerk (~> 2.6) + dry-struct (1.8.1) + dry-core (~> 1.1) + dry-types (~> 1.8, >= 1.8.2) ice_nine (~> 0.11) - dry-types (1.5.1) + zeitwerk (~> 2.6) + dry-types (1.9.1) + bigdecimal (>= 3.0) concurrent-ruby (~> 1.0) - dry-container (~> 0.3) - dry-core (~> 0.5, >= 0.5) - dry-inflector (~> 0.1, >= 0.1.2) - dry-logic (~> 1.0, >= 1.0.2) - dry-validation (1.7.0) + dry-core (~> 1.0) + dry-inflector (~> 1.0) + dry-logic (~> 1.4) + zeitwerk (~> 2.6) + dry-validation (1.11.1) concurrent-ruby (~> 1.0) - dry-container (~> 0.7, >= 0.7.1) - dry-core (~> 0.5, >= 0.5) - dry-initializer (~> 3.0) - dry-schema (~> 1.8, >= 1.8.0) - ffi (1.15.4) - ffi-compiler (1.0.1) - ffi (>= 1.0.0) - rake - hashdiff (1.0.1) - http (5.0.4) - addressable (~> 2.8) - http-cookie (~> 1.0) - http-form_data (~> 2.2) - llhttp-ffi (~> 0.4.0) - http-cookie (1.0.4) - domain_name (~> 0.5) - http-form_data (2.3.0) + dry-core (~> 1.1) + dry-initializer (~> 3.2) + dry-schema (~> 1.14) + zeitwerk (~> 2.6) + hashdiff (1.2.1) ice_nine (0.11.2) - llhttp-ffi (0.4.0) - ffi-compiler (~> 1.0) - rake (~> 13.0) - method_source (1.0.0) - parallel (1.21.0) - parser (3.0.2.0) + io-console (0.8.2) + json (2.20.0) + language_server-protocol (3.17.0.5) + lint_roller (1.1.0) + logger (1.7.0) + method_source (1.1.0) + mutex_m (0.3.0) + parallel (2.1.0) + parser (3.3.11.1) ast (~> 2.4.1) - pry (0.14.1) + racc + prism (1.9.0) + pry (0.16.0) coderay (~> 1.1) method_source (~> 1.0) - pry-byebug (3.8.0) - byebug (~> 11.0) - pry (~> 0.10) - public_suffix (4.0.6) - rack (2.2.3) - rainbow (3.0.0) - rake (13.0.6) - regexp_parser (2.1.1) - rexml (3.2.5) - rspec (3.10.0) - rspec-core (~> 3.10.0) - rspec-expectations (~> 3.10.0) - rspec-mocks (~> 3.10.0) - rspec-core (3.10.1) - rspec-support (~> 3.10.0) - rspec-expectations (3.10.1) + reline (>= 0.6.0) + pry-byebug (3.12.0) + byebug (~> 13.0) + pry (>= 0.13, < 0.17) + public_suffix (7.0.5) + racc (1.8.1) + rack (3.2.6) + rainbow (3.1.1) + rake (13.4.2) + regexp_parser (2.12.0) + reline (0.6.3) + io-console (~> 0.5) + rexml (3.4.4) + rspec (3.13.2) + rspec-core (~> 3.13.0) + rspec-expectations (~> 3.13.0) + rspec-mocks (~> 3.13.0) + rspec-core (3.13.6) + rspec-support (~> 3.13.0) + rspec-expectations (3.13.5) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.10.0) - rspec-mocks (3.10.2) + rspec-support (~> 3.13.0) + rspec-mocks (3.13.8) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.10.0) - rspec-support (3.10.2) - rubocop (1.22.3) - parallel (~> 1.10) - parser (>= 3.0.0.0) + rspec-support (~> 3.13.0) + rspec-support (3.13.7) + rubocop (1.88.1) + json (~> 2.3) + language_server-protocol (~> 3.17.0.2) + lint_roller (~> 1.1.0) + parallel (>= 1.10) + parser (>= 3.3.0.2) rainbow (>= 2.2.2, < 4.0) - regexp_parser (>= 1.8, < 3.0) - rexml - rubocop-ast (>= 1.12.0, < 2.0) + regexp_parser (>= 2.9.3, < 3.0) + rubocop-ast (>= 1.49.0, < 2.0) ruby-progressbar (~> 1.7) - unicode-display_width (>= 1.4.0, < 3.0) - rubocop-ast (1.12.0) - parser (>= 3.0.1.1) + unicode-display_width (>= 2.4.0, < 4.0) + rubocop-ast (1.49.1) + parser (>= 3.3.7.2) + prism (~> 1.7) + rubocop-capybara (2.23.0) + lint_roller (~> 1.1) + rubocop (~> 1.81) + rubocop-factory_bot (2.28.0) + lint_roller (~> 1.1) + rubocop (~> 1.72, >= 1.72.1) rubocop-rake (0.6.0) rubocop (~> 1.0) - rubocop-rspec (2.5.0) - rubocop (~> 1.19) - ruby-progressbar (1.11.0) - simplecov (0.21.2) + rubocop-rspec (2.31.0) + rubocop (~> 1.40) + rubocop-capybara (~> 2.17) + rubocop-factory_bot (~> 2.22) + rubocop-rspec_rails (~> 2.28) + rubocop-rspec_rails (2.29.1) + rubocop (~> 1.61) + ruby-progressbar (1.13.0) + simplecov (0.22.0) docile (~> 1.1) simplecov-html (~> 0.11) simplecov_json_formatter (~> 0.1) - simplecov-html (0.12.3) - simplecov_json_formatter (0.1.3) - unf (0.1.4) - unf_ext - unf_ext (0.0.8) - unicode-display_width (2.1.0) - webmock (3.14.0) + simplecov-html (0.13.2) + simplecov_json_formatter (0.1.4) + unicode-display_width (3.2.0) + unicode-emoji (~> 4.1) + unicode-emoji (4.2.0) + webmock (3.26.2) addressable (>= 2.8.0) crack (>= 0.3.2) hashdiff (>= 0.4.0, < 2.0.0) - webrick (1.7.0) - yard (0.9.26) + webrick (1.9.2) + yard (0.9.44) + zeitwerk (2.8.2) PLATFORMS ruby DEPENDENCIES - pry - pry-byebug (~> 3.8.0) - rack - rake (~> 13.0) - rspec (~> 3.5) - rubocop (~> 1.22) + httpclient! + pry (~> 0.16) + pry-byebug (~> 3.8) + rack (~> 3.2) + rake (~> 13.4) + rspec (~> 3.13) + rubocop (~> 1.88) rubocop-rake (~> 0.6.0) - rubocop-rspec (~> 2.5) - simplecov (~> 0.16) - webmock (~> 3.14) - webrick - yard + rubocop-rspec (~> 2.31) + simplecov (~> 0.22) + webmock (~> 3.26) + webrick (~> 1.9) + yard (~> 0.9) yookassa! BUNDLED WITH - 2.2.22 + 4.0.11 diff --git a/README.md b/README.md index a2e6fbf..b850c65 100644 --- a/README.md +++ b/README.md @@ -125,6 +125,16 @@ Everyone is encouraged to help improve this project. Here are a few ways you can - Write, clarify, or fix documentation - Suggest or add new features +## Development + +This gem uses the `glebtv/httpclient` fork for local development to avoid the `http` gem's `llhttp-ffi` dependency chain. + +```sh +bin/setup +bundle exec rspec +bundle exec rubocop +``` + ## License The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT). diff --git a/lib/yookassa/client.rb b/lib/yookassa/client.rb index 1522405..52e4a45 100644 --- a/lib/yookassa/client.rb +++ b/lib/yookassa/client.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true -require "http" +require "httpclient" +require "json" require_relative "./entity/error" module Yookassa @@ -10,12 +11,15 @@ module Yookassa attr_reader :http def initialize(shop_id: Yookassa.config.shop_id, api_key: Yookassa.config.api_key, oauth_token: nil) - @http = HTTP.headers(accept: "application/json") + @http = HTTPClient.new( + default_header: { "Accept" => "application/json" }, + force_basic_auth: true + ) if shop_id && api_key - @http.basic_auth(user: shop_id, pass: api_key) + @http.set_auth(API_URL, shop_id, api_key) elsif oauth_token - @http.headers("Authorization" => "Bearer #{oauth_token}") + @http.default_header["Authorization"] = "Bearer #{oauth_token}" else message = "Specify `shop_id` and `api_key` settings in a `.configure` block " \ "or pass `oauth_token` to a client" @@ -26,23 +30,28 @@ module Yookassa private def get(endpoint, query: {}) - api_call { http.get("#{API_URL}#{endpoint}", params: query) } + api_call { http.get("#{API_URL}#{endpoint}", query: query) } end def post(endpoint, idempotency_key:, payload: {}) - api_call { http.headers("Idempotence-Key" => idempotency_key).post("#{API_URL}#{endpoint}", json: payload) } + headers = json_headers.merge("Idempotence-Key" => idempotency_key) + api_call { http.post("#{API_URL}#{endpoint}", body: JSON.generate(payload), header: headers) } end def delete(endpoint, idempotency_key:) - api_call { http.headers("Idempotence-Key" => idempotency_key).delete("#{API_URL}#{endpoint}") } + api_call { http.delete("#{API_URL}#{endpoint}", header: { "Idempotence-Key" => idempotency_key }) } end def api_call response = yield if block_given? body = JSON.parse(response.body.to_s, symbolize_names: true) - return body if response.status.success? + return body if response.status.between?(200, 299) Entity::Error.new(**body) end + + def json_headers + { "Content-Type" => "application/json" } + end end end diff --git a/yookassa.gemspec b/yookassa.gemspec index 5136745..30b365a 100644 --- a/yookassa.gemspec +++ b/yookassa.gemspec @@ -20,9 +20,9 @@ Gem::Specification.new do |spec| spec.require_paths = ["lib"] - spec.required_ruby_version = ">= 2.6" + spec.required_ruby_version = ">= 3.3" spec.add_runtime_dependency "dry-struct" spec.add_runtime_dependency "dry-validation" - spec.add_runtime_dependency "http", "~> 5.0" + spec.add_runtime_dependency "httpclient", ">= 2.9" end