Skip to content

Commit

Permalink
Allow a model to be converted into a hash or a json string (#5)
Browse files Browse the repository at this point in the history
* Allow models to be converted to a hash or JSON string

This is mostly useful when processing messages on a Transaction
Notification Service. This gem tries to minimise the shock value of the
EML APIs and return sensible values but it's likely that the result set
will need to be added to a database so a mechanism is required to
convert back from custom objects to something that can be stored

* 👮
  • Loading branch information
HashNotAdam committed Nov 20, 2019
1 parent 9f847b8 commit adb7359
Show file tree
Hide file tree
Showing 32 changed files with 381 additions and 160 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
.env
coverage
Gemfile.lock
pkg
spec/examples.txt
spec/vcr_cassettes
2 changes: 2 additions & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ require:

AllCops:
TargetRubyVersion: 2.6.4
Exclude:
- bin/**/*

Layout/AlignParameters:
EnforcedStyle: with_fixed_indentation
Expand Down
4 changes: 3 additions & 1 deletion Rakefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# frozen_string_literal: true

require "bundler/gem_tasks"
require "rspec/core/rake_task"

RSpec::Core::RakeTask.new(:spec)

task :default => :spec
task default: :spec
2 changes: 1 addition & 1 deletion eml.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ Gem::Specification.new do |spec|
spec.homepage = "https://github.com/MorningCoffeeDev/eml_ruby"
spec.license = "MIT"

spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
spec.files = Dir.chdir(File.expand_path(__dir__)) do
`git ls-files -z`.split("\x0").
reject { |f| f.match(%r{^(test|spec|features)/}) }
end
Expand Down
1 change: 1 addition & 0 deletions lib/eml.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ module EML
require "eml/lib/basic_auth/verify"
require "eml/lib/constant_time_compare"
require "eml/lib/endpoint_class"
require "eml/lib/model_hash"

require "eml/model"
require "eml/parameters"
Expand Down
18 changes: 9 additions & 9 deletions lib/eml/error/rest.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,27 +16,27 @@ class RESTError < Error
def initialize(message = nil, response = nil)
super(message)

@response = T.let(response, EML::Response)
@response = T.let(response, T.nilable(EML::Response))
end

sig { returns(String) }
sig { returns(T.nilable(String)) }
def http_body
@response.error || @response.body.to_s
@response&.error || @response&.body&.to_s
end

sig { returns(T::Hash[Symbol, String]) }
sig { returns(T.nilable(T::Hash[Symbol, String])) }
def http_headers
@response.headers
@response&.headers
end

sig { returns(Integer) }
sig { returns(T.nilable(Integer)) }
def http_status
@response.status
@response&.http_status
end

sig { returns(String) }
sig { returns(T.nilable(String)) }
def url
@response.url.to_s
@response&.url&.to_s
end
end
end
10 changes: 5 additions & 5 deletions lib/eml/lib/basic_auth/generate.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ class Generate
).returns(String)
end
def self.call(username, password, prefix: "")
new(username, password, prefix).call
new(username, password, prefix: prefix).call
end

sig do
Expand All @@ -26,10 +26,10 @@ def self.call(username, password, prefix: "")
prefix: String
).void
end
def initialize(username, password, prefix)
@username = username
@password = password
@prefix = prefix
def initialize(username, password, prefix: "")
@username = T.let(username, String)
@password = T.let(password, String)
@prefix = T.let(prefix, String)
end

sig { returns(String) }
Expand Down
11 changes: 6 additions & 5 deletions lib/eml/lib/basic_auth/verify.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ class Verify

sig do
params(
auth_token: String,
auth_token: T.nilable(String),
username: String,
password: String
).returns(T::Boolean)
Expand All @@ -19,15 +19,15 @@ def self.call(auth_token, username, password)

sig do
params(
auth_token: String,
auth_token: T.nilable(String),
username: String,
password: String
).void
end
def initialize(auth_token, username, password)
@auth_token = auth_token
@username = username
@password = password
@auth_token = T.let(auth_token || "", String)
@username = T.let(username, String)
@password = T.let(password, String)
end

sig { returns(T::Boolean) }
Expand All @@ -40,6 +40,7 @@ def call

private

sig { returns(String) }
def parse_auth_token
@auth_token.sub(/^[^\s]+\s/, "")
end
Expand Down
8 changes: 4 additions & 4 deletions lib/eml/lib/constant_time_compare.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ def self.call(comparison, expected)

sig { params(comparison: String, expected: String).void }
def initialize(comparison, expected)
@comparison = comparison
@expected = expected
@comparison = T.let(comparison, String)
@expected = T.let(expected, String)
end

sig { returns(T::Boolean) }
Expand All @@ -22,8 +22,8 @@ def call

result = 0

Hash[[@comparison.bytes, @expected.bytes].transpose].
each { |x, y| result |= x ^ y }
transposed = [@comparison.bytes, @expected.bytes].transpose
Hash[transposed].each { |x, y| result |= x ^ y }

result.zero?
end
Expand Down
18 changes: 15 additions & 3 deletions lib/eml/lib/endpoint_class.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ module EML
class EndpointClass
extend T::Sig

sig do
params(class_type: String, resource_class: T.untyped, endpoint: String).
returns(T.untyped)
end
def self.call(class_type:, resource_class:, endpoint:)
new(
class_type: class_type,
Expand All @@ -13,18 +17,25 @@ def self.call(class_type:, resource_class:, endpoint:)
).call
end

sig do
params(class_type: String, resource_class: T.untyped, endpoint: String).
void
end
def initialize(class_type:, resource_class:, endpoint:)
@class_type = class_type
@resource_class = resource_class
@endpoint = endpoint
@class_type = T.let(class_type, String)
@resource_class = T.let(resource_class, T.untyped)
@endpoint = T.let(endpoint, String)
@class_name = T.let(nil, T.nilable(String))
end

sig { returns(T.untyped) }
def call
Object.const_get(class_name) if Object.const_defined?(class_name)
end

private

sig { returns(String) }
def class_name
@class_name ||= begin
name_parts = @resource_class.name.split("::")
Expand All @@ -35,6 +46,7 @@ def class_name
end
end

sig { returns(String) }
def action_class_name
@endpoint.capitalize.sub(/s$/, "")
end
Expand Down
54 changes: 54 additions & 0 deletions lib/eml/lib/model_hash.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# typed: strict
# frozen_string_literal: true

module EML
class ModelHash
extend T::Sig

sig { params(model: EML::Model).returns(T::Hash[Symbol, T.untyped]) }
def self.call(model)
new(model).call
end

sig { params(model: EML::Model).void }
def initialize(model)
@model = T.let(model, EML::Model)
@hash = T.let({}, T::Hash[Symbol, T.untyped])
end

sig { returns(T::Hash[Symbol, T.untyped]) }
def call
@model.class.enumerate_fields do |_, local_name|
value = @model.public_send(local_name)
add_value(local_name, value)
end

@hash
end

private

sig { params(name: Symbol, value: T.untyped).void }
def add_value(name, value)
@hash[name] = stored_value(value)
end

sig { params(value: T.untyped).returns(T.untyped) }
def stored_value(value)
if value.is_a?(Array)
array_value(value)
elsif value.respond_to?(:to_h)
value.to_h
else
value
end
end

sig { params(value: T::Array[T.untyped]).returns(T::Array[T.untyped]) }
def array_value(value)
value.each_with_object([]) do |item, array|
array << stored_value(item)
end
end
end
end
33 changes: 29 additions & 4 deletions lib/eml/model.rb
Original file line number Diff line number Diff line change
@@ -1,27 +1,52 @@
# typed: true
# frozen_string_literal: true

require "json"

module EML
class Model
extend T::Sig
extend T::Helpers
abstract!

sig do
params(
fields: T.any(T::Hash[String, T.untyped], T::Array[String])
).void
end
def self.fields(fields)
const_set(:FIELDS, fields.freeze)
fields.each do |response_name, local_name|
local_name ||= response_name
enumerate_fields do |_, local_name|
__send__(:attr_reader, :"#{local_name}")
end
end

sig { void }
def self.enumerate_fields
const_get(:FIELDS).each do |response_name, local_name|
local_name ||= response_name.to_sym
yield(response_name, local_name)
end
end

sig { params(raw_values: T::Hash[String, T.untyped]).void }
def initialize(raw_values)
self.class::FIELDS.each do |response_name, local_name|
local_name ||= response_name
self.class.enumerate_fields do |response_name, local_name|
value = field_value(response_name, raw_values[response_name])
instance_variable_set(:"@#{local_name}", value)
end
end

sig { returns(T::Hash[Symbol, T.untyped]) }
def to_h
ModelHash.(self)
end

sig { params(_args: T.nilable(Array)).returns(String) }
def to_json(*_args)
to_h.to_json
end

protected

sig { params(name: String, raw_value: T.untyped).returns(T.untyped) }
Expand Down
3 changes: 2 additions & 1 deletion lib/eml/parameters.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ class << self
params: T::Hash[Symbol, T.untyped]
).returns(T.untyped)
end
def self.convert(resource_class, endpoint, params)
def convert(resource_class, endpoint, params)
endpoint_class = EML::EndpointClass.(
class_type: ENDPOINT_CLASS_TYPE, resource_class: resource_class,
endpoint: endpoint
Expand Down Expand Up @@ -112,6 +112,7 @@ def validate_max_length(param_name, param_value, length)

private

sig { params(array: T::Array[String]).returns(String) }
def array_as_string(array)
array.dup.tap { |vals| vals[-1] = "or #{vals.last}" }.join(", ")
end
Expand Down
2 changes: 1 addition & 1 deletion lib/eml/response.rb
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ def raise_error(id: nil)

sig { returns(T.nilable(EML::RESTError)) }
def check_for_incorrectly_categorised_errors
if error.match?(/is not in (the proper|a valid) status/)
if error&.match?(/is not in (the proper|a valid) status/)
raise T.unsafe(EML::REST::UnprocessableEntityError).new(error, self)
end
end
Expand Down
5 changes: 3 additions & 2 deletions lib/eml/uk.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ module UK
require "eml/uk/lib/endpoint_class"
require "eml/uk/lib/parse_date"

require "eml/uk/models/tns_card"
require "eml/uk/models/tns_transaction"
require "eml/uk/models/tns/card"
require "eml/uk/models/tns/message"
require "eml/uk/models/tns/transaction"
require "eml/uk/models/transaction"

require "eml/uk/parameters"
Expand Down
11 changes: 6 additions & 5 deletions lib/eml/uk/api_resource.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ class APIResource
def initialize(id: nil)
@id = T.let(id, T.nilable(String))
@headers = T.let(nil, T.nilable(T::Hash[String, String]))
@credentials = T.let(nil, T.nilable(T::Hash[Symbol, String]))
end

sig do
Expand Down Expand Up @@ -50,13 +51,13 @@ def resource_url(endpoint)

private

sig { returns(T::Hash[Symbol, T.nilable(String)]) }
sig { returns(T::Hash[Symbol, String]) }
def credentials
@credentials ||= begin
config = EML::UK.config
{
username: config.rest_username,
password: config.rest_password,
username: config.rest_username || "",
password: config.rest_password || "",
}
end
end
Expand All @@ -74,8 +75,8 @@ def domain
def headers
@headers ||= {
"Authorization" => ::EML::BasicAuth::Generate.(
credentials[:username],
credentials[:password],
T.must(credentials[:username]),
T.must(credentials[:password]),
prefix: "Basic "
),
}
Expand Down
Loading

0 comments on commit adb7359

Please sign in to comment.