tincheck 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/LICENSE.txt +18 -0
- data/README.md +95 -0
- data/lib/tincheck.rb +33 -0
- data/lib/tincheck/config.rb +91 -0
- data/lib/tincheck/request.rb +111 -0
- data/lib/tincheck/response.rb +67 -0
- data/lib/tincheck/version.rb +12 -0
- data/lib/tincheck/xml.rb +29 -0
- data/lib/tincheck/xml/nokogiri.rb +45 -0
- data/lib/tincheck/xml/ox.rb +46 -0
- data/lib/tincheck/xml/parser.rb +38 -0
- data/lib/tincheck/xml/rexml.rb +52 -0
- data/lib/tincheck/xml/serializer.rb +47 -0
- metadata +142 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: e28521acf6e657f3ab1d686db3846d7fdf756a9f
|
4
|
+
data.tar.gz: 0e30b296dc379f2de12352451d50913bc2480cd5
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: d6c12cda7b4bfaf7e1f829d157a96a934fc30bf2c9851622c45aadf37203a0083a90cdbb041a6478114e079bb04687df3a0242e67eb62430443a72ca3914d9d8
|
7
|
+
data.tar.gz: 34077a7189cb3ccace190768a33dd77af1adceb874f787f20cb7e6f4d578020664e50b94b2420b1822edb084045b018b3bdc3dda246799a22a97dc5b35ccb845
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
Copyright (c) 2019 Joshua Hansen
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to
|
5
|
+
deal in the Software without restriction, including without limitation the
|
6
|
+
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
7
|
+
sell copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
16
|
+
THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
17
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
18
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
# TINCheck
|
2
|
+
|
3
|
+
## Overview
|
4
|
+
|
5
|
+
This gem provides a simple interface for interacting with TINCheck's SOAP API. It doesn't use any SOAP library and the response object is a glorified hash that merges all the keys from the various Result elements.
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Pretty standard gem stuff.
|
10
|
+
|
11
|
+
$ gem install tincheck
|
12
|
+
|
13
|
+
When using [Bundler](https://bundler.io) or requiring this library in general, it's important to note that this gem will attempt to load its XML add-ons by default if `Ox` or `Nokogiri` is already defined, it will use them in that order. Otherwise, it will use the default of `REXML`. The only consideration is that `REXML` will get required if neither optional library is already required.
|
14
|
+
|
15
|
+
So, ensure you load your project's XML libs (if you're using them) first.
|
16
|
+
|
17
|
+
## Configuration
|
18
|
+
|
19
|
+
Generally, you'll just configure a username and password. Unfortunately, TINCheck doesn't have any sort of a sandbox. Happy testing.
|
20
|
+
|
21
|
+
### `ENV`
|
22
|
+
|
23
|
+
Prefix any configuration option with `tincheck_` and it will be automatically set:
|
24
|
+
|
25
|
+
* `ENV['tincheck_password']`
|
26
|
+
* `ENV['tincheck_proxy_url']`
|
27
|
+
* `ENV['tincheck_username']`
|
28
|
+
* `ENV['tincheck_xml_lib']`
|
29
|
+
|
30
|
+
You can return the configuration set by the environment with `TINCheck.env_config` which might be useful in situations where you want multiple configurations modified from a default set by the environment.
|
31
|
+
|
32
|
+
### With a Hash
|
33
|
+
|
34
|
+
```ruby
|
35
|
+
TINCheck.configure(
|
36
|
+
password: 'password',
|
37
|
+
username: 'user@example.com'
|
38
|
+
)
|
39
|
+
```
|
40
|
+
|
41
|
+
### With a Block
|
42
|
+
|
43
|
+
```ruby
|
44
|
+
TINCheck.configure do
|
45
|
+
password 'password'
|
46
|
+
username 'user@example.com'
|
47
|
+
end
|
48
|
+
```
|
49
|
+
|
50
|
+
## Making Requests
|
51
|
+
|
52
|
+
Basic requests can be made using `TINCheck.request`. This will take the supplied hash, wrap it in a SOAP envelop and inject the proper credentials. You should probably never do this directly.
|
53
|
+
|
54
|
+
TINCheck only supports for different services and this gem currently supports only two of those, each with its own method.
|
55
|
+
|
56
|
+
### Check Service Status
|
57
|
+
|
58
|
+
If you want to just make sure everything is working:
|
59
|
+
|
60
|
+
```ruby
|
61
|
+
response = TINCheck.status # => #<TINCheck::Response>
|
62
|
+
```
|
63
|
+
|
64
|
+
### Check a TIN and a Name
|
65
|
+
|
66
|
+
```ruby
|
67
|
+
response = TINCheck.tin_name(name: 'John Q Person', tin: '000000000') # => #<TINCheck::Response>
|
68
|
+
response.name_and_tin_match? # => true
|
69
|
+
response.death_record? # => false
|
70
|
+
```
|
71
|
+
|
72
|
+
## Contributing
|
73
|
+
|
74
|
+
### Issue Guidelines
|
75
|
+
|
76
|
+
GitHub issues are for bugs, not support. As of right now, there is no official support for this gem. You can try reaching out to the author, [Joshua Hansen](mailto:joshua@epicbanality.com?subject=TINCheck) if you're really stuck, but there's a pretty high chance that won't go anywhere at the moment or you'll get a response like this:
|
77
|
+
|
78
|
+
> Hi. I'm super busy. It's nothing personal. Check the README first if you haven't already. If you don 't find your answer there, it's time to start reading the source. Have fun! Let me know if I screwed something up.
|
79
|
+
|
80
|
+
### Pull Request Guidelines
|
81
|
+
|
82
|
+
* Include tests with your PRs. (Wouldn't it be nice if I included some?)
|
83
|
+
* Run `rubocop` to ensure your style fits with the rest of the project.
|
84
|
+
|
85
|
+
### Code of Conduct
|
86
|
+
|
87
|
+
Be nice. After all, this is free code. I have a day job.
|
88
|
+
|
89
|
+
## License
|
90
|
+
|
91
|
+
See [`LICENSE.txt`](LICENSE.txt).
|
92
|
+
|
93
|
+
## What if I stop maintaining this?
|
94
|
+
|
95
|
+
The codebase isn't huge. If you opt to rely on this code and I die/get bored/find enlightenment you should be able to maintain it. Sadly, that's the only guarantee at the moment!
|
data/lib/tincheck.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
|
3
|
+
require 'tincheck/config'
|
4
|
+
require 'tincheck/request'
|
5
|
+
|
6
|
+
module TINCheck
|
7
|
+
class << self
|
8
|
+
attr_reader :default_config, :default_request
|
9
|
+
|
10
|
+
def configure(config = env_config, &blk)
|
11
|
+
@default_config = block_given? ? Config.build(&blk) : Config.with_obj(config)
|
12
|
+
@default_request = Request.new(@default_config)
|
13
|
+
end
|
14
|
+
|
15
|
+
def env_config
|
16
|
+
opts = Config::OPTS.each_with_object({}) do |k, h|
|
17
|
+
env_key = "tincheck_#{k}"
|
18
|
+
h[k] = ENV[env_key] if ENV.key?(env_key)
|
19
|
+
end
|
20
|
+
Config.new(opts)
|
21
|
+
end
|
22
|
+
|
23
|
+
def request(request_hash)
|
24
|
+
default_request.(request_hash)
|
25
|
+
end
|
26
|
+
alias call request
|
27
|
+
|
28
|
+
Request::SERVICES.keys.each do |k|
|
29
|
+
define_method(k) { |**kwargs| default_request.public_send(k, kwargs) }
|
30
|
+
end
|
31
|
+
end
|
32
|
+
configure
|
33
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
|
3
|
+
require 'net/http'
|
4
|
+
require 'uri'
|
5
|
+
|
6
|
+
module TINCheck
|
7
|
+
class Config
|
8
|
+
OPTS = %i[password proxy_url url username xml_lib].freeze
|
9
|
+
SERVICE_URI = URI.parse('https://www.tincheck.com/pvsws/pvsservice.asmx').freeze
|
10
|
+
|
11
|
+
class Builder
|
12
|
+
def self.call(&blk)
|
13
|
+
new.(&blk)
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize
|
17
|
+
@opts = {}
|
18
|
+
end
|
19
|
+
|
20
|
+
OPTS.each { |o| define_method(o) { |val| @opts[o] = val.to_s } }
|
21
|
+
|
22
|
+
def call(&blk)
|
23
|
+
instance_eval(&blk) if block_given?
|
24
|
+
Config.new(@opts)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class << self
|
29
|
+
def build(&blk)
|
30
|
+
Config::Builder.(&blk)
|
31
|
+
end
|
32
|
+
|
33
|
+
def with_obj(config)
|
34
|
+
config.is_a?(self) ? config : new(config)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
attr_reader :proxy_uri
|
39
|
+
|
40
|
+
def initialize(**opts)
|
41
|
+
@opts = opts.each_with_object({}) do |(k, v), h|
|
42
|
+
OPTS.include?(k = k.to_sym) && h[k] = v.to_s
|
43
|
+
end
|
44
|
+
defaults!
|
45
|
+
load_xml_lib
|
46
|
+
proxy_uri!
|
47
|
+
end
|
48
|
+
|
49
|
+
def opts
|
50
|
+
@opts.dup
|
51
|
+
end
|
52
|
+
|
53
|
+
def proxy_args
|
54
|
+
@proxy_uri ? [@proxy_uri.host, @proxy_uri.port, @proxy_uri.user, @proxy_uri.password] : []
|
55
|
+
end
|
56
|
+
|
57
|
+
def with(**opts)
|
58
|
+
self.class.new(@opts.merge(opts))
|
59
|
+
end
|
60
|
+
|
61
|
+
(OPTS + [:uri]).each { |o| define_method(o) { @opts[o] } }
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
def default_uri(url)
|
66
|
+
url ? URI(url) : SERVICE_URI
|
67
|
+
end
|
68
|
+
|
69
|
+
def default_xml_lib
|
70
|
+
return 'Ox' if defined?(::Ox)
|
71
|
+
return 'Nokogiri' if defined?(::Nokogiri)
|
72
|
+
'REXML'
|
73
|
+
end
|
74
|
+
|
75
|
+
def defaults!
|
76
|
+
@opts[:xml_lib] ||= default_xml_lib
|
77
|
+
@opts[:password] ||= ENV['tincheck_password']
|
78
|
+
@opts[:username] ||= ENV['tincheck_username']
|
79
|
+
@opts[:uri] ||= default_uri(@opts[:url])
|
80
|
+
end
|
81
|
+
|
82
|
+
def load_xml_lib
|
83
|
+
require "tincheck/xml/#{@opts[:xml_lib].downcase}"
|
84
|
+
end
|
85
|
+
|
86
|
+
def proxy_uri!
|
87
|
+
return unless (url = @opts[:proxy_url] || ENV['HTTP_PROXY'] || ENV['http_proxy'])
|
88
|
+
@proxy_uri = URI(url)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
|
3
|
+
require 'tincheck/response'
|
4
|
+
require 'tincheck/xml'
|
5
|
+
|
6
|
+
module TINCheck
|
7
|
+
class Request
|
8
|
+
SERVICES = {
|
9
|
+
list_match: 'ValidateListMatch',
|
10
|
+
status: 'ServiceStatus',
|
11
|
+
tin_name: 'ValidateTinName',
|
12
|
+
validate_all: 'ValidateTinNameAddressListMatch'
|
13
|
+
}.freeze
|
14
|
+
|
15
|
+
SERVICE_XMLNS = 'http://www.TinCheck.com/WebServices/PVSService/'.freeze
|
16
|
+
|
17
|
+
InvalidConfig = Class.new(StandardError)
|
18
|
+
ResponseError = Class.new(StandardError)
|
19
|
+
|
20
|
+
attr_reader :config, :http, :serializer
|
21
|
+
|
22
|
+
def initialize(config = TINCheck.default_config, http: nil, serializer: nil)
|
23
|
+
raise InvalidConfig, 'invalid or missing config' unless config.is_a?(Config)
|
24
|
+
@config = config
|
25
|
+
@http = http || _http
|
26
|
+
@parser = XML.parser_with(config.xml_lib)
|
27
|
+
@serializer = serializer || XML.serializer_with(config.xml_lib)
|
28
|
+
end
|
29
|
+
|
30
|
+
def call(request_hash)
|
31
|
+
r = post(serializer.(format_request(request_hash)))
|
32
|
+
Response.with_http_response(r, parser: @parser)
|
33
|
+
end
|
34
|
+
|
35
|
+
def post(xml)
|
36
|
+
http.dup.start { |h| h.request(post_request(xml)) }
|
37
|
+
end
|
38
|
+
|
39
|
+
def status(*)
|
40
|
+
call(SERVICES[:status] => {})
|
41
|
+
end
|
42
|
+
|
43
|
+
def tin_name(**kwargs)
|
44
|
+
require_arguments!(kwargs, :name, :tin)
|
45
|
+
call(SERVICES[:tin_name] => tin_name_arg(**kwargs))
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def _http
|
51
|
+
Net::HTTP.new(config.uri.host, config.uri.port, *config.proxy_args).tap do |h|
|
52
|
+
h.use_ssl = true if config.uri.scheme == 'https'
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def auth_args
|
57
|
+
raise InvalidConfig, 'no username or password specified in config' unless
|
58
|
+
config.username && config.password
|
59
|
+
{
|
60
|
+
'CurUser' => {
|
61
|
+
'UserLogin' => config.username,
|
62
|
+
'UserPassword' => config.password
|
63
|
+
}
|
64
|
+
}
|
65
|
+
end
|
66
|
+
|
67
|
+
def format_request(request_hash)
|
68
|
+
soap_envelop(inject_auth_args(request_hash))
|
69
|
+
end
|
70
|
+
|
71
|
+
def inject_auth_args(request_hash)
|
72
|
+
k, h = request_hash.first
|
73
|
+
return request_hash if h.key?('CurUser')
|
74
|
+
request_hash.merge(k => h.merge(auth_args))
|
75
|
+
end
|
76
|
+
|
77
|
+
def post_request(xml)
|
78
|
+
Net::HTTP::Post.new(config.uri.path).tap do |r|
|
79
|
+
r['Content-Type'] ||= 'text/xml; charset=UTF-8'
|
80
|
+
r.body = xml
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def require_arguments!(kwargs, *keys)
|
85
|
+
keys.each { |k| raise ArgumentError, "missing keyword: #{k}" unless kwargs.key?(k) }
|
86
|
+
kwargs
|
87
|
+
end
|
88
|
+
|
89
|
+
def soap_envelop(request_hash)
|
90
|
+
{
|
91
|
+
'SOAP-ENV:Envelope' => {
|
92
|
+
'xmlns:SOAP-ENC' => 'http://schemas.xmlsoap.org/soap/encoding/',
|
93
|
+
'xmlns:SOAP-ENV' => 'http://schemas.xmlsoap.org/soap/envelope/',
|
94
|
+
'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema',
|
95
|
+
'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance',
|
96
|
+
'SOAP-ENV:Body' => request_hash.merge('xmlns' => SERVICE_XMLNS)
|
97
|
+
}
|
98
|
+
}
|
99
|
+
end
|
100
|
+
|
101
|
+
def tin_name_arg(giin: nil, name:, tin: nil, **)
|
102
|
+
{
|
103
|
+
'TinName' => {
|
104
|
+
'TIN' => tin,
|
105
|
+
'LName' => name,
|
106
|
+
'GIIN' => giin
|
107
|
+
}
|
108
|
+
}
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
|
3
|
+
require 'tincheck/xml'
|
4
|
+
|
5
|
+
module TINCheck
|
6
|
+
class Response
|
7
|
+
APIError = Class.new(RuntimeError)
|
8
|
+
ConfigError = Class.new(RuntimeError)
|
9
|
+
HTTPError = Class.new(RuntimeError)
|
10
|
+
|
11
|
+
include Enumerable
|
12
|
+
|
13
|
+
class << self
|
14
|
+
def with_http_response(http_response, parser:)
|
15
|
+
http_ok?(http_response)
|
16
|
+
new(http_response.body, parser: parser)
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def http_error(http_response)
|
22
|
+
"server responded with #{http_response.code} instead of 200"
|
23
|
+
end
|
24
|
+
|
25
|
+
def http_ok?(http_response)
|
26
|
+
raise HTTPError, http_error(http_response) unless http_response.code == '200'
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def initialize(xml, parser:)
|
31
|
+
@to_h = merge_results(parser.(xml).values)
|
32
|
+
end
|
33
|
+
|
34
|
+
def [](key)
|
35
|
+
@to_h[key]
|
36
|
+
end
|
37
|
+
|
38
|
+
def calls_remaining
|
39
|
+
self['CallsRemaining'] == 'No Limit' ? nil : self['CallsRemaining'].to_i
|
40
|
+
end
|
41
|
+
|
42
|
+
def death_record?
|
43
|
+
self['DMF_CODE'] == '1'
|
44
|
+
end
|
45
|
+
|
46
|
+
def each(*args, &blk)
|
47
|
+
@to_h.each(*args, &blk)
|
48
|
+
end
|
49
|
+
|
50
|
+
def merge_results(results)
|
51
|
+
results.reduce({}) { |h, rs| h.merge(rs) }
|
52
|
+
end
|
53
|
+
|
54
|
+
def name_and_tin_match?
|
55
|
+
%w[1 6 7 8].include?(self['TINNAME_CODE'])
|
56
|
+
end
|
57
|
+
|
58
|
+
def to_h
|
59
|
+
@to_h.dup
|
60
|
+
end
|
61
|
+
alias to_hash to_h
|
62
|
+
|
63
|
+
def watch_lists?
|
64
|
+
self['LISTSMATCH_CODE'] == '1'
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
data/lib/tincheck/xml.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
|
3
|
+
require 'tincheck/xml/parser'
|
4
|
+
require 'tincheck/xml/serializer'
|
5
|
+
|
6
|
+
module TINCheck
|
7
|
+
module XML
|
8
|
+
class << self
|
9
|
+
def hash_or_array(obj)
|
10
|
+
case obj
|
11
|
+
when Hash
|
12
|
+
yield(obj)
|
13
|
+
when Array
|
14
|
+
obj.map { |o| yield(o) }
|
15
|
+
else
|
16
|
+
obj
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def parser_with(name)
|
21
|
+
const_get(name)::Parser.new
|
22
|
+
end
|
23
|
+
|
24
|
+
def serializer_with(name, attributes: Serializer::ATTRIBUTES)
|
25
|
+
const_get(name)::Serializer.new(attributes: attributes)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
|
3
|
+
require 'nokogiri'
|
4
|
+
require 'tincheck/xml'
|
5
|
+
|
6
|
+
module TINCheck
|
7
|
+
module XML
|
8
|
+
module Nokogiri
|
9
|
+
class Parser
|
10
|
+
include XML::Parser
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def root(xml)
|
15
|
+
::Nokogiri::XML(xml) { |c| c.options = ::Nokogiri::XML::ParseOptions::NOBLANKS }
|
16
|
+
end
|
17
|
+
|
18
|
+
def value_with!(element)
|
19
|
+
element.elements.empty? ? text_or_nil(element.text) : hash_with(*element.elements)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class Serializer
|
24
|
+
include XML::Serializer
|
25
|
+
|
26
|
+
def call(hash)
|
27
|
+
::Nokogiri::XML::Document.new.tap { |d| add_xml_elements!(d, hash) }.to_s
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def attributes_or_elements!(parent, key, value)
|
33
|
+
return parent[key] = text_with(value) if attributes.include?(key)
|
34
|
+
e = ::Nokogiri::XML::Element.new(key, parent)
|
35
|
+
parent.add_child(e)
|
36
|
+
add_xml_elements!(e, value)
|
37
|
+
end
|
38
|
+
|
39
|
+
def insert_text!(element, text)
|
40
|
+
element.add_child(text_with(text))
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
|
3
|
+
require 'ox'
|
4
|
+
require 'tincheck/xml'
|
5
|
+
|
6
|
+
module TINCheck
|
7
|
+
module XML
|
8
|
+
module Ox
|
9
|
+
class Parser
|
10
|
+
include XML::Parser
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def root(xml)
|
15
|
+
::Ox.load(xml, symbolize_keys: false)
|
16
|
+
end
|
17
|
+
|
18
|
+
def value_with!(node)
|
19
|
+
return if node.nodes.empty?
|
20
|
+
node.text || hash_with(*node.nodes)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class Serializer
|
25
|
+
include XML::Serializer
|
26
|
+
|
27
|
+
def call(hash)
|
28
|
+
::Ox.dump(add_xml_elements!(::Ox::Document.new(version: '1.0'), hash))
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def attributes_or_elements!(parent, key, value)
|
34
|
+
return parent[key] = text_with(value) if attributes.include?(key)
|
35
|
+
e = ::Ox::Element.new(key)
|
36
|
+
parent << e
|
37
|
+
add_xml_elements!(e, value)
|
38
|
+
end
|
39
|
+
|
40
|
+
def insert_text!(node, text)
|
41
|
+
node << text_with(text)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
|
3
|
+
module TINCheck
|
4
|
+
module XML
|
5
|
+
module Parser
|
6
|
+
def call(xml)
|
7
|
+
at_depth(3, hash_with(root(xml)))
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def at_depth(num, hash)
|
13
|
+
return unless hash.is_a?(Hash)
|
14
|
+
nh = hash.values.first
|
15
|
+
num.zero? ? nh : at_depth(num - 1, nh)
|
16
|
+
end
|
17
|
+
|
18
|
+
def hash_with(*nodes)
|
19
|
+
nodes.each_with_object({}) do |n, h|
|
20
|
+
inject_or_merge!(h, n.name, value_with!(n))
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def inject_or_merge!(hash, key, value)
|
25
|
+
if hash.key?(key)
|
26
|
+
cv = hash[key]
|
27
|
+
value = cv.is_a?(Array) ? cv.push(value) : [cv, value]
|
28
|
+
end
|
29
|
+
hash[key] = value
|
30
|
+
end
|
31
|
+
|
32
|
+
def text_or_nil(text)
|
33
|
+
return if !text || text.empty? || text[0] == "\n"
|
34
|
+
text
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
|
3
|
+
require 'rexml/document'
|
4
|
+
require 'tincheck/xml'
|
5
|
+
|
6
|
+
module TINCheck
|
7
|
+
module XML
|
8
|
+
module REXML
|
9
|
+
class Parser
|
10
|
+
include XML::Parser
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def attribute_hash(element)
|
15
|
+
element.attributes.each_with_object({}) { |(k, v), h| h[k] = v.to_s }
|
16
|
+
end
|
17
|
+
|
18
|
+
def root(xml)
|
19
|
+
::REXML::Document.new(xml).root
|
20
|
+
end
|
21
|
+
|
22
|
+
def value_with!(element)
|
23
|
+
children = element.elements.to_a
|
24
|
+
return element.text if element.attributes.empty? && children.empty?
|
25
|
+
attribute_hash(element).merge(hash_with(*children))
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class Serializer
|
30
|
+
include XML::Serializer
|
31
|
+
|
32
|
+
def call(hash)
|
33
|
+
::REXML::Document.new(nil, attribute_quote: :quote).tap do |d|
|
34
|
+
d.add(::REXML::XMLDecl.new)
|
35
|
+
add_xml_elements!(d, hash)
|
36
|
+
end.to_s
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def attributes_or_elements!(parent, key, value)
|
42
|
+
return parent.add_attribute(key, text_with(value)) if attributes.include?(key)
|
43
|
+
add_xml_elements!(::REXML::Element.new(key, parent, parent.context), value)
|
44
|
+
end
|
45
|
+
|
46
|
+
def insert_text!(element, text)
|
47
|
+
element.add_text(text_with(text))
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
|
3
|
+
module TINCheck
|
4
|
+
module XML
|
5
|
+
module Serializer
|
6
|
+
ATTRIBUTES = %w[
|
7
|
+
xmlns
|
8
|
+
xmlns:SOAP-ENC
|
9
|
+
xmlns:SOAP-ENV
|
10
|
+
xmlns:xsd
|
11
|
+
xmlns:xsi
|
12
|
+
].freeze
|
13
|
+
|
14
|
+
@@type_coercions = {}
|
15
|
+
def self.coerce(type, obj = nil, &blk)
|
16
|
+
raise TypeError, '`type` must be a `Class`' unless type.is_a?(Class)
|
17
|
+
obj ||= blk
|
18
|
+
raise TypeError, '`obj` must respond to `call`' unless obj.respond_to?(:call)
|
19
|
+
@@type_coercions[type] = obj
|
20
|
+
end
|
21
|
+
|
22
|
+
attr_reader :attributes
|
23
|
+
|
24
|
+
def initialize(attributes: ATTRIBUTES)
|
25
|
+
@attributes = attributes
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def add_xml_elements!(parent, obj)
|
31
|
+
case obj
|
32
|
+
when Hash
|
33
|
+
obj.each { |k, v| attributes_or_elements!(parent, k, v) }
|
34
|
+
when Array
|
35
|
+
obj.each { |v| add_xml_elements!(parent, v) }
|
36
|
+
else
|
37
|
+
insert_text!(parent, obj)
|
38
|
+
end
|
39
|
+
parent
|
40
|
+
end
|
41
|
+
|
42
|
+
def text_with(obj)
|
43
|
+
(callable = @@type_coercions[obj.class]) ? callable.call(obj) : obj.to_s
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
metadata
ADDED
@@ -0,0 +1,142 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: tincheck
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Joshua
|
8
|
+
- Hansen
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2019-01-18 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: bundler
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - "~>"
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: '1.16'
|
21
|
+
type: :development
|
22
|
+
prerelease: false
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - "~>"
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: '1.16'
|
28
|
+
- !ruby/object:Gem::Dependency
|
29
|
+
name: minitest
|
30
|
+
requirement: !ruby/object:Gem::Requirement
|
31
|
+
requirements:
|
32
|
+
- - "~>"
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: '5.0'
|
35
|
+
type: :development
|
36
|
+
prerelease: false
|
37
|
+
version_requirements: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - "~>"
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '5.0'
|
42
|
+
- !ruby/object:Gem::Dependency
|
43
|
+
name: nokogiri
|
44
|
+
requirement: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - "~>"
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: '1.8'
|
49
|
+
type: :development
|
50
|
+
prerelease: false
|
51
|
+
version_requirements: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - "~>"
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '1.8'
|
56
|
+
- !ruby/object:Gem::Dependency
|
57
|
+
name: ox
|
58
|
+
requirement: !ruby/object:Gem::Requirement
|
59
|
+
requirements:
|
60
|
+
- - "~>"
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '2.9'
|
63
|
+
type: :development
|
64
|
+
prerelease: false
|
65
|
+
version_requirements: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - "~>"
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '2.9'
|
70
|
+
- !ruby/object:Gem::Dependency
|
71
|
+
name: rake
|
72
|
+
requirement: !ruby/object:Gem::Requirement
|
73
|
+
requirements:
|
74
|
+
- - "~>"
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '10.0'
|
77
|
+
type: :development
|
78
|
+
prerelease: false
|
79
|
+
version_requirements: !ruby/object:Gem::Requirement
|
80
|
+
requirements:
|
81
|
+
- - "~>"
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: '10.0'
|
84
|
+
- !ruby/object:Gem::Dependency
|
85
|
+
name: rubocop
|
86
|
+
requirement: !ruby/object:Gem::Requirement
|
87
|
+
requirements:
|
88
|
+
- - "~>"
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: '0.56'
|
91
|
+
type: :development
|
92
|
+
prerelease: false
|
93
|
+
version_requirements: !ruby/object:Gem::Requirement
|
94
|
+
requirements:
|
95
|
+
- - "~>"
|
96
|
+
- !ruby/object:Gem::Version
|
97
|
+
version: '0.56'
|
98
|
+
description: A Ruby implementation of the TINCheck API
|
99
|
+
email:
|
100
|
+
- joshua@epicbanality.com
|
101
|
+
executables: []
|
102
|
+
extensions: []
|
103
|
+
extra_rdoc_files: []
|
104
|
+
files:
|
105
|
+
- LICENSE.txt
|
106
|
+
- README.md
|
107
|
+
- lib/tincheck.rb
|
108
|
+
- lib/tincheck/config.rb
|
109
|
+
- lib/tincheck/request.rb
|
110
|
+
- lib/tincheck/response.rb
|
111
|
+
- lib/tincheck/version.rb
|
112
|
+
- lib/tincheck/xml.rb
|
113
|
+
- lib/tincheck/xml/nokogiri.rb
|
114
|
+
- lib/tincheck/xml/ox.rb
|
115
|
+
- lib/tincheck/xml/parser.rb
|
116
|
+
- lib/tincheck/xml/rexml.rb
|
117
|
+
- lib/tincheck/xml/serializer.rb
|
118
|
+
homepage: https://github.com/binarypaladin/tincheck-ruby
|
119
|
+
licenses:
|
120
|
+
- MIT
|
121
|
+
metadata: {}
|
122
|
+
post_install_message:
|
123
|
+
rdoc_options: []
|
124
|
+
require_paths:
|
125
|
+
- lib
|
126
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
127
|
+
requirements:
|
128
|
+
- - ">="
|
129
|
+
- !ruby/object:Gem::Version
|
130
|
+
version: 2.2.0
|
131
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
132
|
+
requirements:
|
133
|
+
- - ">="
|
134
|
+
- !ruby/object:Gem::Version
|
135
|
+
version: '0'
|
136
|
+
requirements: []
|
137
|
+
rubyforge_project:
|
138
|
+
rubygems_version: 2.4.5.4
|
139
|
+
signing_key:
|
140
|
+
specification_version: 4
|
141
|
+
summary: A Ruby implementation of the TINCheck API
|
142
|
+
test_files: []
|