diff --git a/.github/workflows/rubocop.yml b/.github/workflows/rubocop.yml new file mode 100644 index 0000000..3b0aaac --- /dev/null +++ b/.github/workflows/rubocop.yml @@ -0,0 +1,32 @@ +name: Rubocop +on: [pull_request] + +jobs: + linters: + name: Linters + runs-on: ubuntu-latest + if: ${{ github.actor != 'dependabot[bot]' }} + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Setup Ruby + uses: ruby/setup-ruby@v1 + - name: Setup Rubocop + # A bug requires the use of `bundle install` to use `rubocop-rails`. A fix is coming to Rubocop. + # See: https://github.com/rubocop/rubocop/issues/12823 + run: | + bundle install + - name: Run Rubocop + # We must pass the list of files because for now, rubocop on the entire project throws too many errors. + # We exclude the db/schema.rb file explicitly because passing a list of files will override the `AllCops.Exclude` config in .rubocop.yml + run: | + FILES=$(git diff --diff-filter=d --name-only origin/${{ github.base_ref }}...HEAD -- '*.rb' ':!db/*schema.rb') + if [ -z "$FILES" ]; then + echo "No Ruby files to lint" + exit 0 + else + echo "Linting Ruby files" + bundle exec rubocop $FILES + fi diff --git a/README.md b/README.md index 006930d..997b238 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ bundle install ### Basic usage: ```ruby -class MyService < TurbineService::Base +class MyService < Turbine::Base def initialize(my_args) @my_args = my_args @@ -40,13 +40,13 @@ end result = MyService.call(my_args) result.success? # => true result.failure? # => false -result.raise_if_error! # => TurbineService::Result instance +result.raise_if_error! # => Turbine::Result instance ``` ### With failures ```ruby -class MyService < TurbineService::Base +class MyService < Turbine::Base def initialize(my_args) @my_args = my_args @@ -72,14 +72,14 @@ result.failure? # => true result.error.code # => :my_error_code -result.raise_if_error! # => raises TurbineService::ServiceFailure +result.raise_if_error! # => raises Turbine::ServiceFailure ``` ### With data attached to the result ```ruby -class MyService < TurbineService::Base - class Result < TurbineService::Result +class MyService < Turbine::Base + class Result < Turbine::Result attr_accessor :my_data end diff --git a/lib/turbine.rb b/lib/turbine.rb new file mode 100644 index 0000000..4fddc7e --- /dev/null +++ b/lib/turbine.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +require "turbine/version" + +# Base structure +require "turbine/service/result" +require "turbine/service/base" + +# Failures +require "turbine/failures/base_failure" +require "turbine/failures/forbidden_failure" +require "turbine/failures/not_allowed_failure" +require "turbine/failures/not_found_failure" +require "turbine/failures/service_failure" +require "turbine/failures/unauthorized_failure" +require "turbine/failures/validation_failure" diff --git a/lib/turbine_service/failures/base_failure.rb b/lib/turbine/failures/base_failure.rb similarity index 91% rename from lib/turbine_service/failures/base_failure.rb rename to lib/turbine/failures/base_failure.rb index ab85903..602c143 100644 --- a/lib/turbine_service/failures/base_failure.rb +++ b/lib/turbine/failures/base_failure.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -module TurbineService +module Turbine module Failures class BaseFailure < StandardError attr_reader :result diff --git a/lib/turbine_service/failures/forbidden_failure.rb b/lib/turbine/failures/forbidden_failure.rb similarity index 90% rename from lib/turbine_service/failures/forbidden_failure.rb rename to lib/turbine/failures/forbidden_failure.rb index 956e9cb..0587842 100644 --- a/lib/turbine_service/failures/forbidden_failure.rb +++ b/lib/turbine/failures/forbidden_failure.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -module TurbineService +module Turbine module Failures class ForbiddenFailure < BaseFailure attr_reader :code diff --git a/lib/turbine_service/failures/not_allowed_failure.rb b/lib/turbine/failures/not_allowed_failure.rb similarity index 90% rename from lib/turbine_service/failures/not_allowed_failure.rb rename to lib/turbine/failures/not_allowed_failure.rb index 39ca5a0..28395b9 100644 --- a/lib/turbine_service/failures/not_allowed_failure.rb +++ b/lib/turbine/failures/not_allowed_failure.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -module TurbineService +module Turbine module Failures class NotAllowedFailure < BaseFailure attr_reader :code diff --git a/lib/turbine_service/failures/not_found_failure.rb b/lib/turbine/failures/not_found_failure.rb similarity index 93% rename from lib/turbine_service/failures/not_found_failure.rb rename to lib/turbine/failures/not_found_failure.rb index d1eec49..de8774e 100644 --- a/lib/turbine_service/failures/not_found_failure.rb +++ b/lib/turbine/failures/not_found_failure.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -module TurbineService +module Turbine module Failures class NotFoundFailure < BaseFailure attr_reader :resource diff --git a/lib/turbine_service/failures/service_failure.rb b/lib/turbine/failures/service_failure.rb similarity index 93% rename from lib/turbine_service/failures/service_failure.rb rename to lib/turbine/failures/service_failure.rb index e07b16a..410379b 100644 --- a/lib/turbine_service/failures/service_failure.rb +++ b/lib/turbine/failures/service_failure.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -module TurbineService +module Turbine module Failures class ServiceFailure < BaseFailure attr_reader :code, :error_message diff --git a/lib/turbine_service/failures/unauthorized_failure.rb b/lib/turbine/failures/unauthorized_failure.rb similarity index 90% rename from lib/turbine_service/failures/unauthorized_failure.rb rename to lib/turbine/failures/unauthorized_failure.rb index 860647d..d6387a5 100644 --- a/lib/turbine_service/failures/unauthorized_failure.rb +++ b/lib/turbine/failures/unauthorized_failure.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -module TurbineService +module Turbine module Failures class UnauthorizedFailure < BaseFailure attr_reader :code diff --git a/lib/turbine_service/failures/validation_failure.rb b/lib/turbine/failures/validation_failure.rb similarity index 94% rename from lib/turbine_service/failures/validation_failure.rb rename to lib/turbine/failures/validation_failure.rb index caec6dc..9aa1053 100644 --- a/lib/turbine_service/failures/validation_failure.rb +++ b/lib/turbine/failures/validation_failure.rb @@ -2,7 +2,7 @@ require "json" -module TurbineService +module Turbine module Failures class ValidationFailure < BaseFailure attr_reader :messages diff --git a/lib/turbine/service/base.rb b/lib/turbine/service/base.rb new file mode 100644 index 0000000..0f08d36 --- /dev/null +++ b/lib/turbine/service/base.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +module Turbine + module Service + class Base + Result = Turbine::Service::Result + + def self.call(*args, **keyword_args, &block) + new(*args, **keyword_args).call(&block) + end + + def initialize(*, **) + @result = self.class::Result.new + end + + def call(**args, &block) + raise NotImplementedError + end + + private + + attr_reader :result + end + end +end diff --git a/lib/turbine/service/result.rb b/lib/turbine/service/result.rb new file mode 100644 index 0000000..63399d1 --- /dev/null +++ b/lib/turbine/service/result.rb @@ -0,0 +1,71 @@ +# frozen_string_literal: true + +module Turbine + module Service + class Result + attr_reader :error + + def initialize + @failure = false + @error = nil + end + + def failure? + failure + end + + def success? + !failure + end + + def fail_with_error!(error) + @failure = true + @error = error + + self + end + + def forbidden_failure!(code: "feature_unavailable") + fail_with_error!(Turbine::Failures::ForbiddenFailure.new(self, code)) + end + + def not_allowed_failure!(code:) + fail_with_error!(Turbine::Failures::NotAllowedFailure.new(self, code)) + end + + def not_found_failure!(resource:) + fail_with_error!(Turbine::Failures::NotFoundFailure.new(self, resource)) + end + + def service_failure!(code:, message:) + fail_with_error!(Turbine::Failures::ServiceFailure.new(self, code, message)) + end + + def unauthorized_failure!(code: "unauthorized") + fail_with_error!(Turbine::Failures::UnauthorizedFailure.new(self, code)) + end + + def validation_failure!(errors:) + fail_with_error!(Turbine::Failures::ValidationFailure.new(self, errors)) + end + + def record_validation_failure!(record) + validation_failure!(errors: record.errors.messages) + end + + def single_validation_failure!(code:, field: :base) + validation_failure!(errors: {field.to_sym => [code]}) + end + + def raise_if_error! + return self if success? + + raise(error) + end + + private + + attr_accessor :failure + end + end +end diff --git a/lib/turbine_service/version.rb b/lib/turbine/version.rb similarity index 71% rename from lib/turbine_service/version.rb rename to lib/turbine/version.rb index 1fe7317..d99c9c5 100644 --- a/lib/turbine_service/version.rb +++ b/lib/turbine/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -module TurbineService +module Turbine VERSION = "0.0.1" end diff --git a/lib/turbine_service.rb b/lib/turbine_service.rb deleted file mode 100644 index 4aea925..0000000 --- a/lib/turbine_service.rb +++ /dev/null @@ -1,16 +0,0 @@ -# frozen_string_literal: true - -require "turbine_service/version" - -# Base structure -require "turbine_service/result" -require "turbine_service/base" - -# Failures -require "turbine_service/failures/base_failure" -require "turbine_service/failures/forbidden_failure" -require "turbine_service/failures/not_allowed_failure" -require "turbine_service/failures/not_found_failure" -require "turbine_service/failures/service_failure" -require "turbine_service/failures/unauthorized_failure" -require "turbine_service/failures/validation_failure" diff --git a/lib/turbine_service/base.rb b/lib/turbine_service/base.rb deleted file mode 100644 index a595518..0000000 --- a/lib/turbine_service/base.rb +++ /dev/null @@ -1,23 +0,0 @@ -# frozen_string_literal: true - -module TurbineService - class Base - Result = TurbineService::Result - - def self.call(*args, **keyword_args, &block) - new(*args, **keyword_args).call(&block) - end - - def initialize(*, **) - @result = self.class::Result.new - end - - def call(**args, &block) - raise NotImplementedError - end - - private - - attr_reader :result - end -end diff --git a/lib/turbine_service/result.rb b/lib/turbine_service/result.rb deleted file mode 100644 index 07d5254..0000000 --- a/lib/turbine_service/result.rb +++ /dev/null @@ -1,69 +0,0 @@ -# frozen_string_literal: true - -module TurbineService - class Result - attr_reader :error - - def initialize - @failure = false - @error = nil - end - - def failure? - failure - end - - def success? - !failure - end - - def fail_with_error!(error) - @failure = true - @error = error - - self - end - - def forbidden_failure!(code: "feature_unavailable") - fail_with_error!(Failures::ForbiddenFailure.new(self, code)) - end - - def not_allowed_failure!(code:) - fail_with_error!(Failures::NotAllowedFailure.new(self, code)) - end - - def not_found_failure!(resource:) - fail_with_error!(Failures::NotFoundFailure.new(self, resource)) - end - - def service_failure!(code:, message:) - fail_with_error!(Failures::ServiceFailure.new(self, code, message)) - end - - def unauthorized_failure!(code: "unauthorized") - fail_with_error!(Failures::UnauthorizedFailure.new(self, code)) - end - - def validation_failure!(errors:) - fail_with_error!(Failures::ValidationFailure.new(self, errors)) - end - - def record_validation_failure!(record) - validation_failure!(errors: record.errors.messages) - end - - def single_validation_failure!(code:, field: :base) - validation_failure!(errors: {field.to_sym => [code]}) - end - - def raise_if_error! - return self if success? - - raise(error) - end - - private - - attr_accessor :failure - end -end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index eb304e3..a31a4ad 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -2,7 +2,7 @@ require "debug" -require "turbine_service" +require "turbine" RSpec.configure do |config| # Enable flags like --only-failures and --next-failure diff --git a/spec/turbine_service/base_spec.rb b/spec/turbine/service/base_spec.rb similarity index 80% rename from spec/turbine_service/base_spec.rb rename to spec/turbine/service/base_spec.rb index f48bf99..597be9e 100644 --- a/spec/turbine_service/base_spec.rb +++ b/spec/turbine/service/base_spec.rb @@ -2,7 +2,7 @@ require "spec_helper" -class DummyService < TurbineService::Base +class DummyService < Turbine::Service::Base def initialize(message:) @message = message @@ -17,11 +17,11 @@ def call end end -class DummyService::Result < TurbineService::Result +class DummyService::Result < Turbine::Service::Result attr_accessor :message end -class DummyServiceWithoutResult < TurbineService::Base +class DummyServiceWithoutResult < Turbine::Service::Base def initialize(message:) @message = message @@ -35,7 +35,7 @@ def call end end -RSpec.describe TurbineService::Base do +RSpec.describe Turbine::Service::Base do describe "#call" do it { expect { described_class.call }.to raise_error(NotImplementedError) } @@ -54,7 +54,7 @@ def call context "without result definition" do subject(:result) { DummyServiceWithoutResult.call(message: "foo") } - it { expect(result).to be_a(TurbineService::Result) } + it { expect(result).to be_a(Turbine::Service::Result) } it { expect(result).to be_success } end end diff --git a/spec/turbine_service/result_spec.rb b/spec/turbine/service/result_spec.rb similarity index 79% rename from spec/turbine_service/result_spec.rb rename to spec/turbine/service/result_spec.rb index 1075bf7..9709c19 100644 --- a/spec/turbine_service/result_spec.rb +++ b/spec/turbine/service/result_spec.rb @@ -2,9 +2,9 @@ require "spec_helper" -require_relative "../support/validation_error" +require_relative "../../support/validation_error" -RSpec.describe TurbineService::Result do +RSpec.describe Turbine::Service::Result do subject(:result) { described_class.new } it { expect(result).to be_success } @@ -31,10 +31,10 @@ it { expect(result).not_to be_success } it { expect(result).to be_failure } - it { expect(result.error).to be_a(TurbineService::Failures::ForbiddenFailure) } + it { expect(result.error).to be_a(Turbine::Failures::ForbiddenFailure) } it { expect(result.error.code).to eq("feature_unavailable") } - it { expect { result.raise_if_error! }.to raise_error(TurbineService::Failures::ForbiddenFailure) } + it { expect { result.raise_if_error! }.to raise_error(Turbine::Failures::ForbiddenFailure) } context "when passing a code to the failure" do before { result.forbidden_failure!(code: "custom_code") } @@ -48,10 +48,10 @@ it { expect(result).not_to be_success } it { expect(result).to be_failure } - it { expect(result.error).to be_a(TurbineService::Failures::NotAllowedFailure) } + it { expect(result.error).to be_a(Turbine::Failures::NotAllowedFailure) } it { expect(result.error.code).to eq("custom_code") } - it { expect { result.raise_if_error! }.to raise_error(TurbineService::Failures::NotAllowedFailure) } + it { expect { result.raise_if_error! }.to raise_error(Turbine::Failures::NotAllowedFailure) } end describe ".not_found_failure!" do @@ -59,10 +59,10 @@ it { expect(result).not_to be_success } it { expect(result).to be_failure } - it { expect(result.error).to be_a(TurbineService::Failures::NotFoundFailure) } + it { expect(result.error).to be_a(Turbine::Failures::NotFoundFailure) } it { expect(result.error.code).to eq("custom_resource_not_found") } - it { expect { result.raise_if_error! }.to raise_error(TurbineService::Failures::NotFoundFailure) } + it { expect { result.raise_if_error! }.to raise_error(Turbine::Failures::NotFoundFailure) } end describe ".service_failure!" do @@ -70,11 +70,11 @@ it { expect(result).not_to be_success } it { expect(result).to be_failure } - it { expect(result.error).to be_a(TurbineService::Failures::ServiceFailure) } + it { expect(result.error).to be_a(Turbine::Failures::ServiceFailure) } it { expect(result.error.code).to eq("custom_code") } it { expect(result.error.message).to eq("custom_code: custom_message") } - it { expect { result.raise_if_error! }.to raise_error(TurbineService::Failures::ServiceFailure) } + it { expect { result.raise_if_error! }.to raise_error(Turbine::Failures::ServiceFailure) } end describe ".unauthorized_failure!" do @@ -82,10 +82,10 @@ it { expect(result).not_to be_success } it { expect(result).to be_failure } - it { expect(result.error).to be_a(TurbineService::Failures::UnauthorizedFailure) } + it { expect(result.error).to be_a(Turbine::Failures::UnauthorizedFailure) } it { expect(result.error.code).to eq("unauthorized") } - it { expect { result.raise_if_error! }.to raise_error(TurbineService::Failures::UnauthorizedFailure) } + it { expect { result.raise_if_error! }.to raise_error(Turbine::Failures::UnauthorizedFailure) } context "when passing a code to the failure" do before { result.unauthorized_failure!(code: "custom_code") } @@ -99,11 +99,11 @@ it { expect(result).not_to be_success } it { expect(result).to be_failure } - it { expect(result.error).to be_a(TurbineService::Failures::ValidationFailure) } + it { expect(result.error).to be_a(Turbine::Failures::ValidationFailure) } it { expect(result.error.messages).to eq({field: ["error"]}) } it { expect(result.error.message).to eq('Validation errors: {"field":["error"]}') } - it { expect { result.raise_if_error! }.to raise_error(TurbineService::Failures::ValidationFailure) } + it { expect { result.raise_if_error! }.to raise_error(Turbine::Failures::ValidationFailure) } end describe ".record_validation_failure!" do @@ -115,11 +115,11 @@ it { expect(result).not_to be_success } it { expect(result).to be_failure } - it { expect(result.error).to be_a(TurbineService::Failures::ValidationFailure) } + it { expect(result.error).to be_a(Turbine::Failures::ValidationFailure) } it { expect(result.error.messages).to eq({field: ["error"]}) } it { expect(result.error.message).to eq('Validation errors: {"field":["error"]}') } - it { expect { result.raise_if_error! }.to raise_error(TurbineService::Failures::ValidationFailure) } + it { expect { result.raise_if_error! }.to raise_error(Turbine::Failures::ValidationFailure) } end describe ".single_validation_failure!" do @@ -127,11 +127,11 @@ it { expect(result).not_to be_success } it { expect(result).to be_failure } - it { expect(result.error).to be_a(TurbineService::Failures::ValidationFailure) } + it { expect(result.error).to be_a(Turbine::Failures::ValidationFailure) } it { expect(result.error.messages).to eq({base: ["error"]}) } it { expect(result.error.message).to eq('Validation errors: {"base":["error"]}') } - it { expect { result.raise_if_error! }.to raise_error(TurbineService::Failures::ValidationFailure) } + it { expect { result.raise_if_error! }.to raise_error(Turbine::Failures::ValidationFailure) } context "when passing a field to the failure" do before { result.single_validation_failure!(code: "error", field: "field") } diff --git a/turbine_service.gemspec b/turbine_service.gemspec index 6c64661..b89db93 100644 --- a/turbine_service.gemspec +++ b/turbine_service.gemspec @@ -2,11 +2,11 @@ $LOAD_PATH.push File.expand_path("lib", __dir__) -require "turbine_service/version" +require "turbine/version" Gem::Specification.new do |spec| spec.name = "turbine_service" - spec.version = TurbineService::VERSION + spec.version = Turbine::VERSION spec.platform = Gem::Platform::RUBY spec.required_ruby_version = ">= 3.0.0" spec.authors = ["Vincent Pochet"]