serdee 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +11 -0
- data/.rspec +3 -0
- data/.travis.yml +5 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +54 -0
- data/README.md +35 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/examples/address.rb +31 -0
- data/examples/request_message.json +31 -0
- data/examples/request_message.rb +9 -0
- data/examples/response_code.rb +79 -0
- data/examples/response_message.json +36 -0
- data/examples/response_message.rb +24 -0
- data/examples/unified_message.rb +96 -0
- data/lib/serdee/attribute.rb +100 -0
- data/lib/serdee/attributes.rb +154 -0
- data/lib/serdee/version.rb +3 -0
- data/lib/serdee.rb +10 -0
- data/serdee.gemspec +30 -0
- metadata +135 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: d32e12c6af56fcee2b79e0d1c3390410459d5c23
|
4
|
+
data.tar.gz: 0e8003cbd25ad282db4749c35119326c3cb23f0e
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 97c96e37529521be72bd5ffe824a6f0cc11f07e64e6c838ae7719c32989cfe2b12be649d11b52a4f8144eee88f33dfe8620051d7b8ccff352a5b128d8598f7fe
|
7
|
+
data.tar.gz: '0818da033b2801fcf157d2be3f753c5bb61b87385131694c4ae3ba734086d54063bf9a7ac56793a5c68bb9beb7c95809e16255607a8a5ac56f2810db18ad02fb'
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
serdee (0.1.0)
|
5
|
+
activesupport
|
6
|
+
|
7
|
+
GEM
|
8
|
+
remote: https://rubygems.org/
|
9
|
+
specs:
|
10
|
+
activesupport (5.2.1)
|
11
|
+
concurrent-ruby (~> 1.0, >= 1.0.2)
|
12
|
+
i18n (>= 0.7, < 2)
|
13
|
+
minitest (~> 5.1)
|
14
|
+
tzinfo (~> 1.1)
|
15
|
+
coderay (1.1.2)
|
16
|
+
concurrent-ruby (1.0.5)
|
17
|
+
diff-lcs (1.3)
|
18
|
+
i18n (1.1.0)
|
19
|
+
concurrent-ruby (~> 1.0)
|
20
|
+
method_source (0.9.0)
|
21
|
+
minitest (5.11.3)
|
22
|
+
pry (0.11.2)
|
23
|
+
coderay (~> 1.1.0)
|
24
|
+
method_source (~> 0.9.0)
|
25
|
+
rake (10.5.0)
|
26
|
+
rspec (3.8.0)
|
27
|
+
rspec-core (~> 3.8.0)
|
28
|
+
rspec-expectations (~> 3.8.0)
|
29
|
+
rspec-mocks (~> 3.8.0)
|
30
|
+
rspec-core (3.8.0)
|
31
|
+
rspec-support (~> 3.8.0)
|
32
|
+
rspec-expectations (3.8.1)
|
33
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
34
|
+
rspec-support (~> 3.8.0)
|
35
|
+
rspec-mocks (3.8.0)
|
36
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
37
|
+
rspec-support (~> 3.8.0)
|
38
|
+
rspec-support (3.8.0)
|
39
|
+
thread_safe (0.3.6)
|
40
|
+
tzinfo (1.2.5)
|
41
|
+
thread_safe (~> 0.1)
|
42
|
+
|
43
|
+
PLATFORMS
|
44
|
+
ruby
|
45
|
+
|
46
|
+
DEPENDENCIES
|
47
|
+
bundler (~> 1.16)
|
48
|
+
pry
|
49
|
+
rake (~> 10.0)
|
50
|
+
rspec (~> 3.0)
|
51
|
+
serdee!
|
52
|
+
|
53
|
+
BUNDLED WITH
|
54
|
+
1.16.0
|
data/README.md
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
# Serdee
|
2
|
+
|
3
|
+
Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/serdee`. To experiment with that code, run `bin/console` for an interactive prompt.
|
4
|
+
|
5
|
+
TODO: Delete this and the text above, and describe your gem
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
gem 'serdee'
|
13
|
+
```
|
14
|
+
|
15
|
+
And then execute:
|
16
|
+
|
17
|
+
$ bundle
|
18
|
+
|
19
|
+
Or install it yourself as:
|
20
|
+
|
21
|
+
$ gem install serdee
|
22
|
+
|
23
|
+
## Usage
|
24
|
+
|
25
|
+
TODO: Write usage instructions here
|
26
|
+
|
27
|
+
## Development
|
28
|
+
|
29
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
30
|
+
|
31
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
32
|
+
|
33
|
+
## Contributing
|
34
|
+
|
35
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/serdee.
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "serdee"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
data/examples/address.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
class Address
|
3
|
+
BYTES = {
|
4
|
+
address: 22,
|
5
|
+
city: 13,
|
6
|
+
state: 3,
|
7
|
+
country: 2
|
8
|
+
}.freeze
|
9
|
+
attr_accessor :address, :city, :state, :country
|
10
|
+
def initialize(address:, city:, state:, country:)
|
11
|
+
@address = address
|
12
|
+
@city = city
|
13
|
+
@state = state
|
14
|
+
@country = country
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.serialize(address)
|
18
|
+
return if address.nil?
|
19
|
+
BYTES.map do |field, bytes|
|
20
|
+
address.send(field)[0...bytes].ljust(bytes, " ")
|
21
|
+
end.join
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.deserialize(string)
|
25
|
+
return if string.nil?
|
26
|
+
string = string.dup
|
27
|
+
new(
|
28
|
+
BYTES.transform_values { |bytes| string.slice!(0...bytes) }
|
29
|
+
)
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
{
|
2
|
+
"request":{
|
3
|
+
"header":{
|
4
|
+
"signature":"BHNUMS",
|
5
|
+
"details":{
|
6
|
+
"productCategoryCode":"01",
|
7
|
+
"specVersion":"04"
|
8
|
+
}
|
9
|
+
},
|
10
|
+
"transaction":{
|
11
|
+
"primaryAccountNumber":"9877890000000000",
|
12
|
+
"processingCode":"315400",
|
13
|
+
"transactionAmount":"000000000000",
|
14
|
+
"transmissionDateTime":"131121105414",
|
15
|
+
"systemTraceAuditNumber":"000110",
|
16
|
+
"localTransactionTime":"105414",
|
17
|
+
"localTransactionDate":"131121",
|
18
|
+
"merchantCategoryCode":"5411",
|
19
|
+
"pointOfServiceEntryMode":"011",
|
20
|
+
"acquiringInstitutionIdentifier":"60300000063",
|
21
|
+
"retrievalReferenceNumber":"000000002962",
|
22
|
+
"merchantTerminalId":"06220 600 ",
|
23
|
+
"merchantIdentifier":"60300000063 ",
|
24
|
+
"merchantLocation":"BLACKHAWK SIMULATOR PLEASANTON CA US",
|
25
|
+
"transactionCurrencyCode":"840",
|
26
|
+
"additionalTxnFields":{
|
27
|
+
"productId":"07675004390"
|
28
|
+
}
|
29
|
+
}
|
30
|
+
}
|
31
|
+
}
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
class ResponseCode
|
3
|
+
attr_reader :code, :type, :message
|
4
|
+
def initialize(code:, type:, message:)
|
5
|
+
@code = code
|
6
|
+
@type = type
|
7
|
+
@message = message
|
8
|
+
end
|
9
|
+
|
10
|
+
def approved?
|
11
|
+
type == :approved
|
12
|
+
end
|
13
|
+
|
14
|
+
def soft_declined?
|
15
|
+
type == :soft_declined
|
16
|
+
end
|
17
|
+
|
18
|
+
def hard_declined?
|
19
|
+
type == :hard_declined
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.find(code)
|
23
|
+
response = CODES.fetch(code)
|
24
|
+
new(
|
25
|
+
code: code,
|
26
|
+
type: response[0],
|
27
|
+
message: response[1]
|
28
|
+
)
|
29
|
+
end
|
30
|
+
|
31
|
+
CODES = {
|
32
|
+
"00" => [:approved, "Approved – balance available"],
|
33
|
+
"01" => [:approved, "Approved – balance unavailable"],
|
34
|
+
"03" => [:approved, "Approved – balance unavailable on external account number"],
|
35
|
+
"74" => [:soft_declined, "Unable to route / System Error"],
|
36
|
+
"15" => [:soft_declined, "Time Out occurred- Auth Server not available /responding"],
|
37
|
+
"02" => [:hard_declined, "Refer to card issuer"],
|
38
|
+
"04" => [:hard_declined, "Already Redeemed"],
|
39
|
+
"05" => [:hard_declined, "Error account problem"],
|
40
|
+
"06" => [:hard_declined, "Invalid expiration date"],
|
41
|
+
"07" => [:hard_declined, "Unable to process"],
|
42
|
+
"08" => [:hard_declined, "Card not found"],
|
43
|
+
"12" => [:hard_declined, "Invalid transaction"],
|
44
|
+
"13" => [:hard_declined, "Invalid amount"],
|
45
|
+
"14" => [:hard_declined, "Invalid Product"],
|
46
|
+
"16" => [:hard_declined, "Invalid status change"],
|
47
|
+
"17" => [:hard_declined, "Invalid merchant"],
|
48
|
+
"18" => [:hard_declined, "Invalid Phone Number"],
|
49
|
+
"20" => [:hard_declined, "Invalid Pin"],
|
50
|
+
"21" => [:hard_declined, "Card already active"],
|
51
|
+
"22" => [:hard_declined, "Card Already Associated"],
|
52
|
+
"30" => [:hard_declined, "Bad track2 – format error"],
|
53
|
+
"33" => [:hard_declined, "Expired card"],
|
54
|
+
"34" => [:hard_declined, "Already reversed"],
|
55
|
+
"35" => [:hard_declined, "Already voided"],
|
56
|
+
"36" => [:hard_declined, "Restricted card"],
|
57
|
+
"37" => [:hard_declined, "Restricted External Account"],
|
58
|
+
"38" => [:hard_declined, "Restricted Merchant"],
|
59
|
+
"41" => [:hard_declined, "Lost card"],
|
60
|
+
"42" => [:hard_declined, "Lost External Account"],
|
61
|
+
"43" => [:hard_declined, "Stolen card"],
|
62
|
+
"44" => [:hard_declined, "Stolen External Account"],
|
63
|
+
"51" => [:hard_declined, "Insufficient funds"],
|
64
|
+
"54" => [:hard_declined, "Expired External Account"],
|
65
|
+
"55" => [:hard_declined, "Max recharge reached"],
|
66
|
+
"56" => [:hard_declined, "Advance less amount / enter lesser amount"],
|
67
|
+
"58" => [:hard_declined, "Request not permitted by merchant location"],
|
68
|
+
"59" => [:hard_declined, "Request not permitted by processor"],
|
69
|
+
"61" => [:hard_declined, "Exceeds withdrawal amt / over limit"],
|
70
|
+
"62" => [:hard_declined, "Exceeds financial limit"],
|
71
|
+
"65" => [:hard_declined, "Exceeds withdrawal frequency limit"],
|
72
|
+
"66" => [:hard_declined, "Exceeds transaction count limit"],
|
73
|
+
"69" => [:hard_declined, "Format error –bad data"],
|
74
|
+
"71" => [:hard_declined, "Invalid External Account number"],
|
75
|
+
"94" => [:hard_declined, "Duplicate transaction"],
|
76
|
+
"95" => [:hard_declined, "Cannot Reverse the Original Transaction"],
|
77
|
+
"99" => [:hard_declined, "General decline"],
|
78
|
+
}.freeze
|
79
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
{
|
2
|
+
"response":{
|
3
|
+
"header":{
|
4
|
+
"signature":"BHNUMS",
|
5
|
+
"details":{
|
6
|
+
"productCategoryCode":"01",
|
7
|
+
"statusCode":"00",
|
8
|
+
"specVersion":"04"
|
9
|
+
}
|
10
|
+
},
|
11
|
+
"transaction":{
|
12
|
+
"primaryAccountNumber":"9877890000000000",
|
13
|
+
"processingCode":"315400",
|
14
|
+
"transactionAmount":"000000000000",
|
15
|
+
"transmissionDateTime":"131121105414",
|
16
|
+
"systemTraceAuditNumber":"000110",
|
17
|
+
"localTransactionTime":"105414",
|
18
|
+
"localTransactionDate":"131121",
|
19
|
+
"merchantCategoryCode":"5411",
|
20
|
+
"pointOfServiceEntryMode":"011",
|
21
|
+
"acquiringInstitutionIdentifier":"60300000063",
|
22
|
+
"authidentificationResponse":"123456",
|
23
|
+
"responseCode":"00",
|
24
|
+
"retrievalReferenceNumber":"000000002962",
|
25
|
+
"merchantTerminalId":"06220 600 ",
|
26
|
+
"merchantIdentifier":"60300000063 ",
|
27
|
+
"transactionCurrencyCode":"840",
|
28
|
+
"additionalTxnFields":{
|
29
|
+
"productId":"07675004390",
|
30
|
+
"balanceAmount":"C000000002500",
|
31
|
+
"transactionUniqueId": "0JPKA8FKHT2H011784WA0524PM",
|
32
|
+
"correlatedTransactionUniqueId": "0JPKA8FKHT2H011784WA0524PM"
|
33
|
+
}
|
34
|
+
}
|
35
|
+
}
|
36
|
+
}
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require_relative "./unified_message"
|
3
|
+
require_relative "./response_code"
|
4
|
+
class ResponseMessage
|
5
|
+
include Serdee::Attributes
|
6
|
+
set_key_transform :camel_lower
|
7
|
+
|
8
|
+
attr_accessor :request_id
|
9
|
+
|
10
|
+
nested :response, UnifiedMessage
|
11
|
+
|
12
|
+
def parsed_message?
|
13
|
+
status_code == "00" if status_code
|
14
|
+
end
|
15
|
+
|
16
|
+
def status
|
17
|
+
return unless response_code
|
18
|
+
@status ||= ResponseCode.find(response_code)
|
19
|
+
end
|
20
|
+
|
21
|
+
def approved?
|
22
|
+
status&.approved?
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require_relative "./address"
|
3
|
+
require_relative "./response_code"
|
4
|
+
|
5
|
+
class UnifiedMessage
|
6
|
+
include Serdee::Attributes
|
7
|
+
set_key_transform :camel_lower
|
8
|
+
|
9
|
+
PROCESSING_CODES = {
|
10
|
+
balance_inquiry: "315400",
|
11
|
+
redemption: "615400"
|
12
|
+
}.freeze
|
13
|
+
|
14
|
+
nested :header do
|
15
|
+
attribute :signature, default: "BHNUMS"
|
16
|
+
nested :details do
|
17
|
+
attribute :product_category_code, default: "01"
|
18
|
+
attribute :spec_version, default: "43"
|
19
|
+
attribute :status_code
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
nested :transaction do
|
24
|
+
attribute :local_transaction_at do
|
25
|
+
insert do |time, hash|
|
26
|
+
if time
|
27
|
+
hash[:local_transaction_time] = time.strftime("%H%M%S")
|
28
|
+
hash[:local_transaction_date] = time.strftime("%y%m%d")
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
extract do |data|
|
33
|
+
if data[:local_transaction_date] && data[:local_transaction_time]
|
34
|
+
time_string = "#{data[:local_transaction_date]}-#{data[:local_transaction_time]}"
|
35
|
+
DateTime.strptime(time_string, "%y%m%d-%H%M%S")
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
attribute :retrieval_reference_number do
|
40
|
+
serialize { |int| format("%012d", int) }
|
41
|
+
deserialize { |string| string&.to_i }
|
42
|
+
end
|
43
|
+
attribute :system_trace_audit_number do
|
44
|
+
serialize { |int| format("%06d", int) }
|
45
|
+
deserialize { |string| string&.to_i }
|
46
|
+
end
|
47
|
+
attribute :transaction_amount do
|
48
|
+
serialize { |int| format("%012d", int) }
|
49
|
+
deserialize { |string| string&.to_i }
|
50
|
+
end
|
51
|
+
attribute :transmission_date_time do
|
52
|
+
serialize { |time| time&.strftime("%y%m%d%H%M%S") }
|
53
|
+
deserialize { |time_string| DateTime.strptime(time_string, "%y%m%d%H%M%S") if time_string }
|
54
|
+
end
|
55
|
+
attribute :merchant_identifier do
|
56
|
+
serialize { |id| id&.ljust(15, " ") }
|
57
|
+
deserialize { |id| id&.strip }
|
58
|
+
end
|
59
|
+
attribute :action, as: :processing_code do
|
60
|
+
serialize { |action| PROCESSING_CODES.fetch(action) }
|
61
|
+
deserialize { |code| PROCESSING_CODES.key(code) }
|
62
|
+
end
|
63
|
+
attribute :merchant_location, serializer: Address
|
64
|
+
|
65
|
+
attribute :authidentification_response
|
66
|
+
attribute :acquiring_institution_identifier
|
67
|
+
attribute :merchant_category_code
|
68
|
+
attribute :merchant_terminal_id
|
69
|
+
attribute :point_of_service_entry_mode
|
70
|
+
attribute :primary_account_number
|
71
|
+
attribute :response_code
|
72
|
+
attribute :transaction_currency_code
|
73
|
+
|
74
|
+
nested :additional_txn_fields do
|
75
|
+
attribute :balance_amount do
|
76
|
+
serialize do |int|
|
77
|
+
return if int.nil?
|
78
|
+
(int >= 0 ? "C" : "D") + int.abs.to_s.rjust(12, "0")
|
79
|
+
end
|
80
|
+
deserialize do |string|
|
81
|
+
return if string.nil?
|
82
|
+
int = string[1..-1].to_i
|
83
|
+
return -int if string[0] == "D"
|
84
|
+
int
|
85
|
+
end
|
86
|
+
end
|
87
|
+
attribute :correlated_transaction_unique_id
|
88
|
+
attribute :product_id
|
89
|
+
attribute :transaction_unique_id
|
90
|
+
attribute :redemption_pin do
|
91
|
+
serialize { |pin| pin&.ljust(16, " ") }
|
92
|
+
deserialize { |pin| pin&.strip }
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Serdee
|
3
|
+
class Attribute
|
4
|
+
attr_reader :key, :default
|
5
|
+
attr_writer :as
|
6
|
+
attr_accessor :default, :allow_blank, :serializer, :optional
|
7
|
+
|
8
|
+
def initialize(key, **options, &block)
|
9
|
+
@key = key
|
10
|
+
{ allow_blank: false }.merge(options).each do |option, setting|
|
11
|
+
config.public_send(option, setting)
|
12
|
+
end
|
13
|
+
config.instance_eval(&block) if block_given?
|
14
|
+
end
|
15
|
+
|
16
|
+
def config
|
17
|
+
@config ||= Config.new(self)
|
18
|
+
end
|
19
|
+
|
20
|
+
def as
|
21
|
+
@as || key
|
22
|
+
end
|
23
|
+
|
24
|
+
def default=(val)
|
25
|
+
@optional = true
|
26
|
+
@default = val
|
27
|
+
end
|
28
|
+
|
29
|
+
def insert(value, hash)
|
30
|
+
new_value = serialize(value)
|
31
|
+
hash[as] = new_value if new_value.present? || allow_blank
|
32
|
+
end
|
33
|
+
|
34
|
+
def insert_to(obj, hash)
|
35
|
+
insert(obj.send(key), hash)
|
36
|
+
hash
|
37
|
+
end
|
38
|
+
|
39
|
+
def extract_to(data, obj)
|
40
|
+
obj.send("#{key}=", extract(data))
|
41
|
+
obj
|
42
|
+
end
|
43
|
+
|
44
|
+
def extract(hash)
|
45
|
+
deserialize(hash[as])
|
46
|
+
end
|
47
|
+
|
48
|
+
def serialize(value)
|
49
|
+
value
|
50
|
+
end
|
51
|
+
|
52
|
+
def deserialize(value)
|
53
|
+
value
|
54
|
+
end
|
55
|
+
|
56
|
+
class Config
|
57
|
+
attr_reader :attribute
|
58
|
+
def initialize(attribute)
|
59
|
+
@attribute = attribute
|
60
|
+
end
|
61
|
+
|
62
|
+
def serializer(obj)
|
63
|
+
return unless obj
|
64
|
+
|
65
|
+
attribute.serializer = obj
|
66
|
+
serialize { |value| obj.serialize(value) }
|
67
|
+
deserialize { |value| obj.deserialize(value) }
|
68
|
+
end
|
69
|
+
|
70
|
+
[
|
71
|
+
:optional,
|
72
|
+
:default,
|
73
|
+
:allow_blank,
|
74
|
+
:as
|
75
|
+
].each do |key|
|
76
|
+
define_method(key) do |value|
|
77
|
+
attribute.send("#{key}=", value)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
[
|
82
|
+
:extract_to,
|
83
|
+
:insert_to,
|
84
|
+
:extract,
|
85
|
+
:insert,
|
86
|
+
:serialize,
|
87
|
+
:deserialize
|
88
|
+
].each do |key|
|
89
|
+
expected_arg_size = Attribute.instance_method(key).parameters.size
|
90
|
+
define_method(key) do |&block|
|
91
|
+
if block.parameters.size > expected_arg_size || block.parameters.any? { |type, _key| type == :keyreq }
|
92
|
+
raise ArgumentError,
|
93
|
+
"wrong number of arguments defined for ##{key} (given #{block.parameters}, expected #{args})"
|
94
|
+
end
|
95
|
+
attribute.singleton_class.send(:define_method, key, &block)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,154 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require "active_support/core_ext/hash"
|
3
|
+
|
4
|
+
module Serdee
|
5
|
+
module Attributes
|
6
|
+
def self.included(base)
|
7
|
+
base.extend ClassMethods
|
8
|
+
end
|
9
|
+
|
10
|
+
attr_accessor :deserialized_from
|
11
|
+
|
12
|
+
def initialize(**values)
|
13
|
+
self.class.attributes.each do |key, attribute|
|
14
|
+
if !attribute.optional && !values.key?(key)
|
15
|
+
raise ArgumentError, "#{key} is required parameter"
|
16
|
+
end
|
17
|
+
|
18
|
+
if values.key?(key)
|
19
|
+
send("#{key}=", values[key])
|
20
|
+
elsif attribute.default
|
21
|
+
send("#{key}=", attribute.default)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def attributes
|
27
|
+
self.class.attributes.keys.each_with_object({}) do |key, hash|
|
28
|
+
value = send(key)
|
29
|
+
hash[key] = value if value
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def as_json
|
34
|
+
self.class.insert_to(self, {}).deep_transform_keys! do |key|
|
35
|
+
if self.class.key_transform
|
36
|
+
self.class.key_transform.call(key.to_s)
|
37
|
+
else
|
38
|
+
key.to_s
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def to_json
|
44
|
+
as_json.to_json
|
45
|
+
end
|
46
|
+
|
47
|
+
module ClassMethods
|
48
|
+
def attributes
|
49
|
+
@attributes ||= {}
|
50
|
+
end
|
51
|
+
|
52
|
+
def serializers
|
53
|
+
@serializers ||= {}
|
54
|
+
end
|
55
|
+
|
56
|
+
def set_key_transform(transform_name = nil, &block)
|
57
|
+
if transform_name
|
58
|
+
mapping = {
|
59
|
+
camel: :camelize.to_proc,
|
60
|
+
camel_lower: ->(key) { key.camelize(:lower) },
|
61
|
+
dash: :dasherize.to_proc,
|
62
|
+
underscore: :underscore.to_proc
|
63
|
+
}
|
64
|
+
|
65
|
+
@key_transform = mapping[transform_name.to_sym]
|
66
|
+
elsif block
|
67
|
+
@key_transform = block
|
68
|
+
else
|
69
|
+
raise ArgumentError, "must supply at least transform_name or block"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def key_transform
|
74
|
+
@key_transform || Serdee.default_key_transform
|
75
|
+
end
|
76
|
+
|
77
|
+
def attributes_class
|
78
|
+
self
|
79
|
+
end
|
80
|
+
|
81
|
+
def attribute(key, *args, &block)
|
82
|
+
attributes_class.attributes[key] = serializers[key] =
|
83
|
+
Attribute.new(key, *args, &block)
|
84
|
+
|
85
|
+
attributes_class.send(:attr_accessor, key)
|
86
|
+
end
|
87
|
+
|
88
|
+
def nested(key, nested_class = nil, &block)
|
89
|
+
serializers[key] = Nested.new(
|
90
|
+
attributes_class,
|
91
|
+
key,
|
92
|
+
nested_class&.serializers,
|
93
|
+
&block
|
94
|
+
)
|
95
|
+
|
96
|
+
return unless nested_class
|
97
|
+
|
98
|
+
attributes_class.attributes.merge!(nested_class.attributes)
|
99
|
+
attributes_class.send(:attr_accessor, *nested_class.attributes.keys)
|
100
|
+
end
|
101
|
+
|
102
|
+
def from_json(json)
|
103
|
+
of_json(JSON.parse(json))
|
104
|
+
end
|
105
|
+
|
106
|
+
def of_json(json)
|
107
|
+
allocate.tap do |obj|
|
108
|
+
data = json.deep_transform_keys do |key|
|
109
|
+
key.underscore.to_sym
|
110
|
+
end
|
111
|
+
extract_to(data, obj)
|
112
|
+
obj.deserialized_from = json
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def extract_to(data, obj)
|
117
|
+
return if obj.nil?
|
118
|
+
serializers.each do |_key, attribute|
|
119
|
+
attribute.extract_to(data, obj)
|
120
|
+
end
|
121
|
+
obj
|
122
|
+
end
|
123
|
+
|
124
|
+
def insert_to(obj, data)
|
125
|
+
serializers.each do |_key, attribute|
|
126
|
+
attribute.insert_to(obj, data)
|
127
|
+
end
|
128
|
+
data
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
class Nested
|
133
|
+
include Attributes::ClassMethods
|
134
|
+
|
135
|
+
attr_reader :key, :attributes_class
|
136
|
+
def initialize(attributes_class, key, serializers, &block)
|
137
|
+
@attributes_class = attributes_class
|
138
|
+
@key = key
|
139
|
+
@serializers = serializers
|
140
|
+
instance_eval(&block) if block_given?
|
141
|
+
end
|
142
|
+
|
143
|
+
def extract_to(data, obj)
|
144
|
+
return if data.nil? || data[key].nil?
|
145
|
+
super(data[key], obj)
|
146
|
+
end
|
147
|
+
|
148
|
+
def insert_to(obj, data)
|
149
|
+
data[key] = {}
|
150
|
+
super(obj, data[key])
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
data/lib/serdee.rb
ADDED
data/serdee.gemspec
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
|
2
|
+
lib = File.expand_path("../lib", __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require "serdee/version"
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "serdee"
|
8
|
+
spec.version = Serdee::VERSION
|
9
|
+
spec.authors = ["Ryan Ong"]
|
10
|
+
spec.email = ["ryanong@gmail.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{Serdee is a serialization/deserialization library for mostly json}
|
13
|
+
spec.description = %q{Serdee is a highly customizable serialization and deserialization library that is strict by default.}
|
14
|
+
spec.homepage = "https://github.com/ryanong/serdee"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
17
|
+
f.match(%r{^(test|spec|features)/})
|
18
|
+
end
|
19
|
+
spec.bindir = "exe"
|
20
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
21
|
+
spec.require_paths = ["lib"]
|
22
|
+
|
23
|
+
|
24
|
+
spec.add_dependency "activesupport"
|
25
|
+
|
26
|
+
spec.add_development_dependency "bundler", "~> 1.16"
|
27
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
28
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
29
|
+
spec.add_development_dependency "pry"
|
30
|
+
end
|
metadata
ADDED
@@ -0,0 +1,135 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: serdee
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Ryan Ong
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-09-06 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activesupport
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.16'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.16'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '10.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '10.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '3.0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '3.0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: pry
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
description: Serdee is a highly customizable serialization and deserialization library
|
84
|
+
that is strict by default.
|
85
|
+
email:
|
86
|
+
- ryanong@gmail.com
|
87
|
+
executables: []
|
88
|
+
extensions: []
|
89
|
+
extra_rdoc_files: []
|
90
|
+
files:
|
91
|
+
- ".gitignore"
|
92
|
+
- ".rspec"
|
93
|
+
- ".travis.yml"
|
94
|
+
- Gemfile
|
95
|
+
- Gemfile.lock
|
96
|
+
- README.md
|
97
|
+
- Rakefile
|
98
|
+
- bin/console
|
99
|
+
- bin/setup
|
100
|
+
- examples/address.rb
|
101
|
+
- examples/request_message.json
|
102
|
+
- examples/request_message.rb
|
103
|
+
- examples/response_code.rb
|
104
|
+
- examples/response_message.json
|
105
|
+
- examples/response_message.rb
|
106
|
+
- examples/unified_message.rb
|
107
|
+
- lib/serdee.rb
|
108
|
+
- lib/serdee/attribute.rb
|
109
|
+
- lib/serdee/attributes.rb
|
110
|
+
- lib/serdee/version.rb
|
111
|
+
- serdee.gemspec
|
112
|
+
homepage: https://github.com/ryanong/serdee
|
113
|
+
licenses: []
|
114
|
+
metadata: {}
|
115
|
+
post_install_message:
|
116
|
+
rdoc_options: []
|
117
|
+
require_paths:
|
118
|
+
- lib
|
119
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
120
|
+
requirements:
|
121
|
+
- - ">="
|
122
|
+
- !ruby/object:Gem::Version
|
123
|
+
version: '0'
|
124
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
125
|
+
requirements:
|
126
|
+
- - ">="
|
127
|
+
- !ruby/object:Gem::Version
|
128
|
+
version: '0'
|
129
|
+
requirements: []
|
130
|
+
rubyforge_project:
|
131
|
+
rubygems_version: 2.6.11
|
132
|
+
signing_key:
|
133
|
+
specification_version: 4
|
134
|
+
summary: Serdee is a serialization/deserialization library for mostly json
|
135
|
+
test_files: []
|