From bd718cab47bce6a68bc25e1fb96f08e83085f119 Mon Sep 17 00:00:00 2001 From: Antonio Roberto Silva Date: Sat, 29 Jun 2024 04:15:03 -0300 Subject: [PATCH] use dry-monad to get succes and failure and added transactions basics --- Gemfile | 1 + Gemfile.lock | 48 ++++++++++++++++++++ blnk.gemspec | 5 ++- lib/blnk.rb | 2 + lib/blnk/balance.rb | 12 +++-- lib/blnk/ledger.rb | 12 +++-- lib/blnk/resourceable.rb | 82 +++++++++++++++++++++++------------ lib/blnk/transaction.rb | 20 +++++++-- test/blnk/test_balance.rb | 20 +++++---- test/blnk/test_ledger.rb | 29 +++++++------ test/blnk/test_transaction.rb | 31 ++++++++----- test/test_helper.rb | 1 + 12 files changed, 194 insertions(+), 69 deletions(-) diff --git a/Gemfile b/Gemfile index bab52a7..42204d5 100644 --- a/Gemfile +++ b/Gemfile @@ -9,6 +9,7 @@ gem 'rake', '~> 13.0' gem 'minitest', '~> 5.16' gem 'minitest-reporters' +gem 'pry' gem 'webmock' gem 'rubocop', '~> 1.21', require: false diff --git a/Gemfile.lock b/Gemfile.lock index 1433a1d..c901ade 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -2,6 +2,9 @@ PATH remote: . specs: blnk (0.1.1) + dry-configurable (~> 1.0.0) + dry-monads (~> 1.6) + dry-validation (~> 1.10.0) http (~> 5.2.0) GEM @@ -16,11 +19,50 @@ GEM benchmark (0.3.0) bigdecimal (3.1.8) builder (3.3.0) + coderay (1.1.3) + concurrent-ruby (1.3.3) crack (1.0.0) bigdecimal rexml diff-lcs (1.5.1) domain_name (0.6.20240107) + dry-configurable (1.0.1) + dry-core (~> 1.0, < 2) + zeitwerk (~> 2.6) + dry-core (1.0.1) + concurrent-ruby (~> 1.0) + zeitwerk (~> 2.6) + dry-inflector (1.0.0) + dry-initializer (3.1.1) + dry-logic (1.5.0) + concurrent-ruby (~> 1.0) + dry-core (~> 1.0, < 2) + zeitwerk (~> 2.6) + dry-monads (1.6.0) + concurrent-ruby (~> 1.0) + dry-core (~> 1.0, < 2) + zeitwerk (~> 2.6) + dry-schema (1.13.4) + concurrent-ruby (~> 1.0) + dry-configurable (~> 1.0, >= 1.0.1) + dry-core (~> 1.0, < 2) + dry-initializer (~> 3.0) + dry-logic (>= 1.4, < 2) + dry-types (>= 1.7, < 2) + zeitwerk (~> 2.6) + dry-types (1.7.2) + bigdecimal (~> 3.0) + concurrent-ruby (~> 1.0) + dry-core (~> 1.0) + dry-inflector (~> 1.0) + dry-logic (~> 1.4) + zeitwerk (~> 2.6) + dry-validation (1.10.0) + concurrent-ruby (~> 1.0) + dry-core (~> 1.0, < 2) + dry-initializer (~> 3.0) + dry-schema (>= 1.12, < 2) + zeitwerk (~> 2.6) e2mmap (0.1.0) ffi (1.17.0-arm64-darwin) ffi (1.17.0-x86_64-linux-gnu) @@ -47,6 +89,7 @@ GEM llhttp-ffi (0.5.0) ffi-compiler (~> 1.0) rake (~> 13.0) + method_source (1.1.0) minitest (5.24.0) minitest-reporters (1.7.1) ansi @@ -61,6 +104,9 @@ GEM parser (3.3.3.0) ast (~> 2.4.1) racc + pry (0.14.2) + coderay (~> 1.1) + method_source (~> 1.0) public_suffix (6.0.0) racc (1.8.0) rainbow (3.1.1) @@ -110,6 +156,7 @@ GEM crack (>= 0.3.2) hashdiff (>= 0.4.0, < 2.0.0) yard (0.9.36) + zeitwerk (2.6.16) PLATFORMS arm64-darwin @@ -119,6 +166,7 @@ DEPENDENCIES blnk! minitest (~> 5.16) minitest-reporters + pry rake (~> 13.0) rubocop (~> 1.21) solargraph diff --git a/blnk.gemspec b/blnk.gemspec index 7a2940c..2373f89 100644 --- a/blnk.gemspec +++ b/blnk.gemspec @@ -2,7 +2,7 @@ require_relative 'lib/blnk/version' -Gem::Specification.new do |spec| +Gem::Specification.new do |spec| # rubocop:disable Metric/Metrics/BlockLength spec.name = 'blnk' spec.version = Blnk::VERSION spec.authors = ['Antonio Roberto Silva'] @@ -34,6 +34,9 @@ Gem::Specification.new do |spec| spec.require_paths = ['lib'] # Uncomment to register a new dependency of your gem + spec.add_dependency 'dry-configurable', '~> 1.0.0' + spec.add_dependency 'dry-monads', '~> 1.6' + spec.add_dependency 'dry-validation', '~> 1.10.0' spec.add_dependency 'http', '~> 5.2.0' # For more information and examples about making a new gem, check out our diff --git a/lib/blnk.rb b/lib/blnk.rb index b1eaccb..33b0f08 100644 --- a/lib/blnk.rb +++ b/lib/blnk.rb @@ -2,6 +2,8 @@ require 'http' require 'ostruct' +require 'dry-validation' +require 'dry/monads' require_relative 'blnk/version' require_relative 'blnk/client' require_relative 'blnk/resourceable' diff --git a/lib/blnk/balance.rb b/lib/blnk/balance.rb index e35c1cb..ea8ed31 100644 --- a/lib/blnk/balance.rb +++ b/lib/blnk/balance.rb @@ -3,9 +3,15 @@ module Blnk # Balance representation class Balance < Resourceable - def self.resource_name = :balances - def self.id_field = :balance_id + class CreateContract < Dry::Validation::Contract + schema do + required(:ledger_id).value(:string) + required(:currency).value(:string) + end + end - def body_data = { ledger_id:, currency: } + self.resource_name = :balances + self.id_field = :balance_id + self.create_contract = CreateContract end end diff --git a/lib/blnk/ledger.rb b/lib/blnk/ledger.rb index e7b5baf..e7e39e4 100644 --- a/lib/blnk/ledger.rb +++ b/lib/blnk/ledger.rb @@ -3,9 +3,15 @@ module Blnk # Ledger representation class Ledger < Resourceable - def self.resource_name = :ledgers - def self.id_field = :ledger_id + class CreateContract < Dry::Validation::Contract + schema do + required(:name).value(:string) + optional(:meta_data).value(:hash) + end + end - def body_data = { name:, meta_data: } + self.resource_name = :ledgers + self.id_field = :ledger_id + self.create_contract = CreateContract end end diff --git a/lib/blnk/resourceable.rb b/lib/blnk/resourceable.rb index 637c0b9..9d5563e 100644 --- a/lib/blnk/resourceable.rb +++ b/lib/blnk/resourceable.rb @@ -3,54 +3,82 @@ module Blnk # Resoureable module that bring some tweaks for basic REST api integration class Resourceable < OpenStruct + extend Client + class SearchResult < OpenStruct; end - include Client + class DefaultSearchContract < Dry::Validation::Contract + schema do + required(:q).value(:string) + end + end class << self - def resource_name = raise NotImplementedError - def id_field = :id + include Dry::Monads[:result] + + attr_accessor :resource_name, :id_field, :create_contract, :search_contract def find(id) - response = new.get_request(path: "/#{resource_name}/#{id}") - return response unless response.status.success? + check_vars + res = get_request(path: "/#{resource_name}/#{id}") + return Success(new(res.parse)) if res.status.success? - new response.parse + Failure(res.parse) end def all - response = new.get_request(path: "/#{resource_name}") - return response unless response.status.success? + check_vars + res = get_request(path: "/#{resource_name}") + return Failure(res.parse&.symbolize_keys) unless res.status.success? - response.parse.map do |r| - new r - end + Success(res.parse.map { |r| new(r) }) end def create(**args) - response = new.post_request( - path: "/#{resource_name}", - body: args - ) - return response unless response.status.success? + contract = wrap_call(create_contract_new, args) + return contract if contract.failure? - new(response.parse) + res = post_request(path: "/#{resource_name}", body: contract.to_h) + return Failure(res.parse&.symbolize_keys) unless res.status.success? + + Success(new(res.parse)) end def search(**args) - response = new.post_request( - path: "/search/#{resource_name}", - body: args - ) - return response unless response.status.success? + contract = wrap_call(search_contract_new, args) + return contract if contract.failure? + + res = post_request(path: "/search/#{resource_name}", body: contract.to_h) + return Failure(res.parse&.symbolize_keys) unless res.status.success? + + result = SearchResult.new(res.parse.merge(resource_name:)) + Success(result) + end - sr = SearchResult.new(response.parse) - sr.resource_name = resource_name - sr + def check_vars + raise NotImplementedError, 'missing self.resource_name' unless resource_name + raise NotImplementedError, 'missing self.id_field' unless id_field + raise NotImplementedError, 'missing self.create_contract' unless create_contract end + + def wrap_call(contract, args) + check_vars + ccall = contract.call(args) + return Failure(ccall.errors.to_h) if ccall.failure? + + ccall + end + + def create_contract_new + return create_contract.new if create_contract + + raise NotImplementedError, 'missing self.create_contract' + end + + def search_contract_new = (search_contract || DefaultSearchContract).new end - def persisted? = table[self.class.id_field] - def body_data = raise NotImplementedError + # table[self.class.id_field] + def persisted? = public_send(self.class.id_field) end end diff --git a/lib/blnk/transaction.rb b/lib/blnk/transaction.rb index 308b2eb..33f7a66 100644 --- a/lib/blnk/transaction.rb +++ b/lib/blnk/transaction.rb @@ -3,9 +3,23 @@ module Blnk # Transaction representation class Transaction < Resourceable - def self.resource_name = :transactions - def self.id_field = :transaction_id + class CreateContract < Dry::Validation::Contract + schema do + required(:amount).value(:integer) + required(:precision).value(:integer) + required(:currency).value(:string) + required(:reference).value(:string) + required(:source).value(:string) + required(:destination).value(:string) + required(:description).value(:string) + required(:allow_overdraft).value(:bool) + optional(:inflight).value(:bool) + optional(:rate).value(:integer) + end + end - def body_data = {} + self.resource_name = :transactions + self.id_field = :transaction_id + self.create_contract = CreateContract end end diff --git a/test/blnk/test_balance.rb b/test/blnk/test_balance.rb index 21f3870..fe2ae3a 100644 --- a/test/blnk/test_balance.rb +++ b/test/blnk/test_balance.rb @@ -52,20 +52,21 @@ def stub_all_balance_request_with_success .to_return_json(body: [balance_response_body], status: 200) end -class TestLedger < Minitest::Test +class TestBalance < Minitest::Test def test_that_balance_not_found stub_find_balance_request_with_error find = Blnk::Balance.find 'BALANCE_ID' - assert find.status.bad_request? + assert find.failure? end def test_that_balance_find_success stub_find_balance_request_with_success find = Blnk::Balance.find 'BALANCE_ID' - assert find.is_a?(Blnk::Balance) - assert find.balance_id.eql?(balance_response_body[:balance_id]) + assert find.success? + assert find.value!.is_a?(Blnk::Balance) + assert find.value!.balance_id.eql?(balance_response_body[:balance_id]) end # NOTE: /balances route does not exist, at moment @@ -92,16 +93,17 @@ def test_that_balance_create_errosr create = Blnk::Balance.create - assert create.status.bad_request? + assert create.failure? end - def test_that_balance_create_success + def test_that_balance_create_success # rubocop:disable Metrics/AbcSize stub_create_balance_request_with_success create = Blnk::Balance.create(ledger_id: 'ledger_id', currency: 'USD') - assert create.is_a?(Blnk::Balance) - assert create.balance_id.eql?(balance_response_body[:balance_id]) - assert create.name.eql?(balance_response_body[:name]) + assert create.success? + assert create.value!.is_a?(Blnk::Balance) + assert create.value!.balance_id.eql?(balance_response_body[:balance_id]) + assert create.value!.name.eql?(balance_response_body[:name]) end end diff --git a/test/blnk/test_ledger.rb b/test/blnk/test_ledger.rb index ffdadf0..089b48b 100644 --- a/test/blnk/test_ledger.rb +++ b/test/blnk/test_ledger.rb @@ -50,15 +50,16 @@ def test_that_ledger_not_found stub_find_ledger_request_with_error find = Blnk::Ledger.find 'LEDGER_ID' - assert find.status.bad_request? + assert find.failure? end def test_that_ledger_find_success stub_find_ledger_request_with_success find = Blnk::Ledger.find 'LEDGER_ID' - assert find.is_a?(Blnk::Ledger) - assert find.ledger_id.eql?(ledger_response_body[:ledger_id]) + assert find.success? + assert find.value!.is_a?(Blnk::Ledger) + assert find.value!.ledger_id.eql?(ledger_response_body[:ledger_id]) end def test_that_ledger_all_error @@ -66,17 +67,18 @@ def test_that_ledger_all_error all = Blnk::Ledger.all - assert !all.status.success? + assert all.failure? end - def test_that_ledger_all_success + def test_that_ledger_all_success # rubocop:disable Metric/AbcSize stub_all_ledger_request_with_success all = Blnk::Ledger.all - assert all.is_a?(Array) - assert all.first.is_a?(Blnk::Ledger) - assert all.first.ledger_id.eql?(ledger_response_body[:ledger_id]) + assert all.success? + assert all.value!.is_a?(Array) + assert all.value!.first.is_a?(Blnk::Ledger) + assert all.value!.first.ledger_id.eql?(ledger_response_body[:ledger_id]) end def test_that_ledger_create_errosr @@ -84,16 +86,17 @@ def test_that_ledger_create_errosr create = Blnk::Ledger.create - assert create.status.bad_request? + assert create.failure? end - def test_that_ledger_create_success + def test_that_ledger_create_success # rubocop:disable Metrics/AbcSize stub_create_ledger_request_with_success create = Blnk::Ledger.create(name: 'ledger_name') - assert create.is_a?(Blnk::Ledger) - assert create.ledger_id.eql?(ledger_response_body[:ledger_id]) - assert create.name.eql?(ledger_response_body[:name]) + assert create.success? + assert create.value!.is_a?(Blnk::Ledger) + assert create.value!.ledger_id.eql?(ledger_response_body[:ledger_id]) + assert create.value!.name.eql?(ledger_response_body[:name]) end end diff --git a/test/blnk/test_transaction.rb b/test/blnk/test_transaction.rb index 426b257..061d5fd 100644 --- a/test/blnk/test_transaction.rb +++ b/test/blnk/test_transaction.rb @@ -61,15 +61,16 @@ def test_that_transaction_not_found stub_find_transaction_request_with_error find = Blnk::Transaction.find 'transaction_id' - assert find.status.bad_request? + assert find.failure? end def test_that_transaction_find_success stub_find_transaction_request_with_success find = Blnk::Transaction.find 'transaction_id' - assert find.is_a?(Blnk::Transaction) - assert find.transaction_id.eql?(transaction_response_body[:transaction_id]) + assert find.success? + assert find.value!.is_a?(Blnk::Transaction) + assert find.value!.transaction_id.eql?(transaction_response_body[:transaction_id]) end # NOTE: /transactions route does not exist, at moment @@ -96,16 +97,26 @@ def test_that_transaction_create_errosr create = Blnk::Transaction.create - assert create.status.bad_request? + assert create.failure? end - def test_that_transaction_create_success + def test_that_transaction_create_success # rubocop:disable Metric/AbcSize, Metrics/MethodLength stub_create_transaction_request_with_success - create = Blnk::Transaction.create(ledger_id: 'ledger_id', currency: 'USD') - - assert create.is_a?(Blnk::Transaction) - assert create.transaction_id.eql?(transaction_response_body[:transaction_id]) - assert create.name.eql?(transaction_response_body[:name]) + create = Blnk::Transaction.create( + amount: 75, + reference: 'ref_005', + currency: 'BRLX', + precision: 100, + source: '@world', + destination: 'bln_469f93bc-40e9-4e0e-b6ab-d11c3638c15d', + description: 'For fees', + allow_overdraft: true + ) + + assert create.success? + assert create.value!.is_a?(Blnk::Transaction) + assert create.value!.transaction_id.eql?(transaction_response_body[:transaction_id]) + assert create.value!.name.eql?(transaction_response_body[:name]) end end diff --git a/test/test_helper.rb b/test/test_helper.rb index fa17f0b..c848817 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -2,6 +2,7 @@ $LOAD_PATH.unshift File.expand_path('../lib', __dir__) require 'blnk' +require 'pry' require 'webmock/minitest' require 'minitest/autorun' require 'minitest/reporters'