reso_transport 1.5.4 → 1.5.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +2 -0
- data/.gitpod.yml +2 -0
- data/.rubocop.yml +33 -0
- data/Gemfile.lock +1 -1
- data/README.md +44 -8
- data/bin/console +10 -10
- data/bin/rake +29 -0
- data/lib/reso_transport/authentication/fetch_token_auth.rb +39 -16
- data/lib/reso_transport/authentication/middleware.rb +5 -4
- data/lib/reso_transport/base_metadata.rb +64 -0
- data/lib/reso_transport/client.rb +33 -17
- data/lib/reso_transport/datasystem.rb +25 -0
- data/lib/reso_transport/datasystem_parser.rb +26 -0
- data/lib/reso_transport/entity_set.rb +2 -4
- data/lib/reso_transport/entity_type.rb +11 -13
- data/lib/reso_transport/errors.rb +69 -0
- data/lib/reso_transport/metadata.rb +15 -30
- data/lib/reso_transport/metadata_cache.rb +4 -5
- data/lib/reso_transport/metadata_parser.rb +31 -22
- data/lib/reso_transport/query.rb +50 -52
- data/lib/reso_transport/resource.rb +28 -8
- data/lib/reso_transport/version.rb +1 -1
- data/lib/reso_transport.rb +22 -36
- data/reso_transport.gemspec +20 -20
- metadata +21 -14
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 49e97dc43976dfe814ab0bdc7ce7aea16ae5f03e00c94d10d60de3319cb4d388
|
4
|
+
data.tar.gz: f15dd31a948a33dbd8008447be68828ba953114214b7633ff8a4628afa2bdfeb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 375f0962243d54dd27b80da550281fbc1a3b2e9a92affc6cb3a00e94104f5be0fddf6b3e1e5e636fc77a557d2a21438427867af8a66b1b18ae4a897c1027d5f1
|
7
|
+
data.tar.gz: 272dedeb65d29749ff3abeacb955c900a9e7e7dcf2449b53ab1bf9c6f7b996e84b78773f635868e603cc86eb34812a7e3b88f7e981ae41ce671055ddc9fd02a2
|
data/.gitignore
CHANGED
data/.gitpod.yml
ADDED
data/.rubocop.yml
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
AllCops:
|
2
|
+
TargetRubyVersion: 2.6
|
3
|
+
|
4
|
+
Layout/FirstHashElementIndentation:
|
5
|
+
Enabled: false
|
6
|
+
|
7
|
+
Layout/MultilineMethodCallIndentation:
|
8
|
+
Enabled: false
|
9
|
+
|
10
|
+
Metrics/AbcSize:
|
11
|
+
Max: 20
|
12
|
+
|
13
|
+
Metrics/BlockLength:
|
14
|
+
Exclude:
|
15
|
+
- test/**/*
|
16
|
+
|
17
|
+
Metrics/ClassLength:
|
18
|
+
Max: 150
|
19
|
+
|
20
|
+
Metrics/MethodLength:
|
21
|
+
Max: 15
|
22
|
+
|
23
|
+
Metrics/ModuleLength:
|
24
|
+
Max: 150
|
25
|
+
|
26
|
+
Style/ColonMethodCall:
|
27
|
+
Enabled: false
|
28
|
+
|
29
|
+
Style/Documentation:
|
30
|
+
Enabled: false
|
31
|
+
|
32
|
+
Style/FrozenStringLiteralComment:
|
33
|
+
Enabled: false
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -1,3 +1,6 @@
|
|
1
|
+
[](https://gitpod.io/#https://github.com/wrstudios/reso_transport)
|
2
|
+
[](https://badge.fury.io/rb/reso_transport)
|
3
|
+
|
1
4
|
# ResoTransport
|
2
5
|
|
3
6
|
A Ruby gem for connecting to and interacting with RESO WebAPI services. Learn more about what that is by checking out the [RESO WebAPI](https://www.reso.org/reso-web-api/) Documentation.
|
@@ -51,7 +54,7 @@ Or you can set a logger for each specific instance of a client which can be usef
|
|
51
54
|
|
52
55
|
### Getting Connected
|
53
56
|
|
54
|
-
There are 2 strategies for authentication.
|
57
|
+
There are 2 strategies for authentication.
|
55
58
|
|
56
59
|
**Bearer Token**
|
57
60
|
|
@@ -60,7 +63,8 @@ It's simple to use a static access token if your token never expires:
|
|
60
63
|
```ruby
|
61
64
|
@client = ResoTransport::Client.new({
|
62
65
|
md_file: METADATA_CACHE,
|
63
|
-
endpoint: ENDPOINT_URL
|
66
|
+
endpoint: ENDPOINT_URL,
|
67
|
+
use_replication_endpoint: false # this is the default and can be ommitted
|
64
68
|
authentication: {
|
65
69
|
access_token: TOKEN,
|
66
70
|
token_type: "Bearer" # this is the default and can be ommitted
|
@@ -89,7 +93,13 @@ If the connection requires requesting a new token periodically, it's easy to pro
|
|
89
93
|
|
90
94
|
This will pre-fetch a token from the provided endpoint when the current token is either non-existent or has expired.
|
91
95
|
|
96
|
+
The `use_replication_endpoint` flag will append `/replication` to all resource queries if set to `true`. This is required by some data sources to query resources beyond 10,000 records.
|
92
97
|
|
98
|
+
When using this feature, you can retrieve the next link by accessing `next_link` after gettings results:
|
99
|
+
```
|
100
|
+
results = @client.resources["Property"].query.results
|
101
|
+
next_link = @client.resources["Property"].query.next_link
|
102
|
+
```
|
93
103
|
|
94
104
|
### Caching Metadata
|
95
105
|
|
@@ -108,9 +118,9 @@ If you don't have access to the file system, like on Heroku, or you just don't w
|
|
108
118
|
|
109
119
|
```ruby
|
110
120
|
class MyCacheStore < ResoTransport::MetadataCache
|
111
|
-
|
121
|
+
|
112
122
|
def read
|
113
|
-
# read `name` from somewhere
|
123
|
+
# read `name` from somewhere
|
114
124
|
end
|
115
125
|
|
116
126
|
def write(data)
|
@@ -158,12 +168,25 @@ Once you have a successful connection you can explore what resources are availab
|
|
158
168
|
#=> {"Property"=>#<ResoTransport::Resource entity_set="Property", schema="ODataService">, "Office"=>#<ResoTransport::Resource entity_set="Office", schema="ODataService">, "Member"=>#<ResoTransport::Resource entity_set="Member", schema="ODataService">}
|
159
169
|
|
160
170
|
@client.resources["Property"]
|
161
|
-
#=> #<ResoTransport::Resource entity_set="Property", schema="ODataService">
|
171
|
+
#=> #<ResoTransport::Resource entity_set="Property", schema="ODataService">
|
162
172
|
|
163
173
|
@client.resources["Property"].query.limit(1).results
|
164
174
|
#=> Results Array
|
165
175
|
```
|
166
176
|
|
177
|
+
If the resource contains localizations you can access those as well.
|
178
|
+
|
179
|
+
```ruby
|
180
|
+
@client.resources["Property"].localizations
|
181
|
+
#=> {"CommercialSale"=>{"Name"=>"CommercialSale", "ResourcePath"=>"/Property?Class=CommercialSale", "Description"=>"Contains data for Commercial searches.", "DateTimeStamp"=>"2021-05-03T18:13:20.643-07:00"}, "Residential"=>{"Name"=>"Residential", "ResourcePath"=>"/Property?Class=Residential", "Description"=>"Contains data for Residential searches.", "DateTimeStamp"=>"2021-05-03T18:13:20.643-07:00"}}
|
182
|
+
```
|
183
|
+
|
184
|
+
If a resource contains localizations you must select one by name, before querying, like so:
|
185
|
+
|
186
|
+
```ruby
|
187
|
+
@client.resources["Property"].localization('Residential').query.limit(1).results
|
188
|
+
```
|
189
|
+
|
167
190
|
#### Querying
|
168
191
|
|
169
192
|
ResoTransport provides powerful querying capabilities:
|
@@ -196,7 +219,7 @@ To see what child records can be expanded look at `expandable`:
|
|
196
219
|
|
197
220
|
```ruby
|
198
221
|
@resource.expandable
|
199
|
-
#=> [#<struct ResoTransport::Property name="Media", data_type="Collection(RESO.Media)", attrs={"Name"=>"Media", "Type"=>"Collection(RESO.Media)"}, multi=true, enum=nil, complex_type=nil, entity_type=#<struct ResoTransport::EntityType name="Media", base_type=nil, primary_key="MediaKey", schema="CoreLogic.DataStandard.RESO.DD">> ...]
|
222
|
+
#=> [#<struct ResoTransport::Property name="Media", data_type="Collection(RESO.Media)", attrs={"Name"=>"Media", "Type"=>"Collection(RESO.Media)"}, multi=true, enum=nil, complex_type=nil, entity_type=#<struct ResoTransport::EntityType name="Media", base_type=nil, primary_key="MediaKey", schema="CoreLogic.DataStandard.RESO.DD">> ...]
|
200
223
|
```
|
201
224
|
|
202
225
|
Use `expand` to expand child records with the top level results.
|
@@ -210,7 +233,7 @@ You have several options to expand multiple child record sets. Each of these wil
|
|
210
233
|
|
211
234
|
```ruby
|
212
235
|
@resource.query.expand("Media", "Office").limit(10).results
|
213
|
-
|
236
|
+
|
214
237
|
@resource.query.expand(["Media", "Office"]).limit(10).results
|
215
238
|
|
216
239
|
@resource.query.expand("Media").expand("Office").limit(10).results
|
@@ -257,9 +280,22 @@ When querying for an enumeration value, you can provide either the system name,
|
|
257
280
|
|
258
281
|
```ruby
|
259
282
|
@resource.query.eq(StandardStatus: "Active Under Contract").limit(1).compile_params
|
260
|
-
#=> {"$top"=>1, "$filter"=>"StandardStatus eq 'ActiveUnderContract'"}
|
283
|
+
#=> {"$top"=>1, "$filter"=>"StandardStatus eq 'ActiveUnderContract'"}
|
261
284
|
```
|
262
285
|
|
286
|
+
### Troubleshooting
|
287
|
+
|
288
|
+
In the event there are connection issues, the following errors are raised:
|
289
|
+
|
290
|
+
* `ResoTransport::NoResponse` - The server did not respond to the request
|
291
|
+
* `ResoTransport::RequestError` - The server responded with a status code outside the 200 range
|
292
|
+
* `ResoTransport::ResponseError` - The server responded with errors in the body
|
293
|
+
* `ResoTransport::AccessDenied` - Check your authentication details
|
294
|
+
* `ResoTransport::LocalizationRequired` - Provide one of the required localizations through the `localization` method
|
295
|
+
* `ResoTransport::EncodeError` - No match was found for one or more of the properties
|
296
|
+
|
297
|
+
The Faraday Request hash is attached to the error for `NoResponse`, `RequestError`, `ResponseError`, and `AccessDenied`. A Faraday Response is attached on `RequestError`, `ResponseError`, and `AccessDenied`.
|
298
|
+
|
263
299
|
## Development
|
264
300
|
|
265
301
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
data/bin/console
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'reso_transport'
|
5
5
|
|
6
6
|
# You can add fixtures and/or initialization code here to make experimenting
|
7
7
|
# with your gem easier. You can also use a different console, if you like.
|
@@ -12,19 +12,19 @@ require "reso_transport"
|
|
12
12
|
#
|
13
13
|
|
14
14
|
ResoTransport.configure do |c|
|
15
|
-
c.logger = Logger.new(
|
15
|
+
c.logger = Logger.new('log/console.log')
|
16
16
|
end
|
17
17
|
|
18
|
-
|
19
|
-
require "irb"
|
18
|
+
require 'irb'
|
20
19
|
require 'yaml'
|
21
20
|
require 'byebug'
|
22
21
|
|
23
|
-
SECRETS = YAML.load_file(
|
22
|
+
SECRETS = YAML.load_file('secrets.yml')
|
24
23
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
24
|
+
SECRETS.each do |name, data|
|
25
|
+
data[:logger] = Logger.new($stdout)
|
26
|
+
client = ResoTransport::Client.new(data)
|
27
|
+
instance_variable_set("@#{name}", client)
|
28
|
+
end
|
29
29
|
|
30
30
|
IRB.start(__FILE__)
|
data/bin/rake
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
#
|
5
|
+
# This file was generated by Bundler.
|
6
|
+
#
|
7
|
+
# The application 'rake' is installed as part of a gem, and
|
8
|
+
# this file is here to facilitate running it.
|
9
|
+
#
|
10
|
+
|
11
|
+
require "pathname"
|
12
|
+
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
|
13
|
+
Pathname.new(__FILE__).realpath)
|
14
|
+
|
15
|
+
bundle_binstub = File.expand_path("../bundle", __FILE__)
|
16
|
+
|
17
|
+
if File.file?(bundle_binstub)
|
18
|
+
if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
|
19
|
+
load(bundle_binstub)
|
20
|
+
else
|
21
|
+
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
|
22
|
+
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
require "rubygems"
|
27
|
+
require "bundler/setup"
|
28
|
+
|
29
|
+
load Gem.bin_path("rake", "rake")
|
@@ -1,48 +1,71 @@
|
|
1
1
|
module ResoTransport
|
2
2
|
module Authentication
|
3
3
|
class FetchTokenAuth < AuthStrategy
|
4
|
-
attr_reader :
|
5
|
-
|
4
|
+
attr_reader :endpoint,
|
5
|
+
:client_id,
|
6
|
+
:client_secret,
|
7
|
+
:grant_type,
|
8
|
+
:scope,
|
9
|
+
:username,
|
10
|
+
:password
|
11
|
+
|
6
12
|
def initialize(options)
|
7
|
-
|
8
|
-
|
13
|
+
super()
|
14
|
+
|
15
|
+
@grant_type = options.fetch(:grant_type, 'client_credentials')
|
16
|
+
@scope = options.fetch(:scope, 'api')
|
9
17
|
@client_id = options.fetch(:client_id)
|
10
18
|
@client_secret = options.fetch(:client_secret)
|
11
19
|
@endpoint = options.fetch(:endpoint)
|
20
|
+
@username = options.fetch(:username, nil)
|
21
|
+
@password = options.fetch(:password, nil)
|
22
|
+
@request = nil
|
23
|
+
end
|
12
24
|
|
13
|
-
|
25
|
+
def connection
|
26
|
+
@connection ||= Faraday.new(@endpoint) do |faraday|
|
14
27
|
faraday.request :url_encoded
|
15
28
|
faraday.response :logger, ResoTransport.configuration.logger if ResoTransport.configuration.logger
|
16
29
|
faraday.adapter Faraday.default_adapter
|
17
|
-
faraday.basic_auth
|
30
|
+
faraday.basic_auth client_id, client_secret
|
18
31
|
end
|
19
32
|
end
|
20
33
|
|
21
34
|
def authenticate
|
22
|
-
response = connection.post
|
35
|
+
response = connection.post(nil, auth_params { |req| @request = req })
|
23
36
|
json = JSON.parse response.body
|
24
37
|
|
25
|
-
unless response.success?
|
26
|
-
message = "#{response.reason_phrase}: #{json['error'] || response.body}"
|
27
|
-
raise ResoTransport::AccessDenied, response: response, message: message
|
28
|
-
end
|
38
|
+
raise AccessDenied.new(request, response, 'token') unless response.success?
|
29
39
|
|
30
40
|
Access.new({
|
31
41
|
access_token: json.fetch('access_token'),
|
32
42
|
expires_in: json.fetch('expires_in', 1 << (1.size * 8 - 2) - 1),
|
33
|
-
token_type: json.fetch('token_type',
|
43
|
+
token_type: json.fetch('token_type', 'Bearer')
|
34
44
|
})
|
35
45
|
end
|
36
46
|
|
47
|
+
def request
|
48
|
+
return @request.to_h if @request.respond_to? :to_h
|
49
|
+
|
50
|
+
{}
|
51
|
+
end
|
52
|
+
|
37
53
|
private
|
38
54
|
|
39
55
|
def auth_params
|
40
|
-
{
|
41
|
-
client_id:
|
56
|
+
params = {
|
57
|
+
client_id: client_id,
|
42
58
|
client_secret: client_secret,
|
43
|
-
grant_type:
|
44
|
-
scope:
|
59
|
+
grant_type: grant_type,
|
60
|
+
scope: scope
|
45
61
|
}
|
62
|
+
|
63
|
+
if grant_type == 'password'
|
64
|
+
params[:username] = username
|
65
|
+
params[:password] = password
|
66
|
+
end
|
67
|
+
|
68
|
+
params
|
46
69
|
end
|
47
70
|
end
|
48
71
|
end
|
@@ -18,10 +18,11 @@ module ResoTransport
|
|
18
18
|
authorize_request(request_env)
|
19
19
|
|
20
20
|
@app.call(request_env).on_complete do |response_env|
|
21
|
-
raise_if_unauthorized(response_env)
|
21
|
+
raise_if_unauthorized(request_env, response_env)
|
22
22
|
end
|
23
23
|
rescue ResoTransport::AccessDenied
|
24
|
-
raise if retries
|
24
|
+
raise if retries.zero?
|
25
|
+
|
25
26
|
@auth.reset
|
26
27
|
retries -= 1
|
27
28
|
retry
|
@@ -38,8 +39,8 @@ module ResoTransport
|
|
38
39
|
)
|
39
40
|
end
|
40
41
|
|
41
|
-
def raise_if_unauthorized(response_env)
|
42
|
-
raise ResoTransport::AccessDenied if response_env[:status] == 401
|
42
|
+
def raise_if_unauthorized(request_env, response_env)
|
43
|
+
raise ResoTransport::AccessDenied.new(request_env.to_h, response_env) if response_env[:status] == 401
|
43
44
|
end
|
44
45
|
end
|
45
46
|
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module ResoTransport
|
2
|
+
class BaseMetadata
|
3
|
+
MIME_TYPES = {
|
4
|
+
xml: 'application/xml',
|
5
|
+
json: 'application/json'
|
6
|
+
}.freeze
|
7
|
+
|
8
|
+
attr_reader :client
|
9
|
+
|
10
|
+
def initialize(client)
|
11
|
+
@client = client
|
12
|
+
@prefix = nil
|
13
|
+
@classname = nil
|
14
|
+
end
|
15
|
+
|
16
|
+
def prefix
|
17
|
+
raise 'prefix not set' unless @prefix
|
18
|
+
|
19
|
+
@prefix
|
20
|
+
end
|
21
|
+
|
22
|
+
def classname
|
23
|
+
raise 'classname not set' unless @classname
|
24
|
+
|
25
|
+
@classname
|
26
|
+
end
|
27
|
+
|
28
|
+
def parser
|
29
|
+
@parser ||= Object::const_get("#{classname}Parser").new.parse(data)
|
30
|
+
end
|
31
|
+
|
32
|
+
def data
|
33
|
+
if cache_file
|
34
|
+
cache.read || cache.write(raw)
|
35
|
+
else
|
36
|
+
raw
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def cache
|
41
|
+
@cache ||= client.send("#{prefix}_cache").new(cache_file)
|
42
|
+
end
|
43
|
+
|
44
|
+
def cache_file
|
45
|
+
@cache_file ||= client.send "#{prefix}_file"
|
46
|
+
end
|
47
|
+
|
48
|
+
def raw
|
49
|
+
return response.body if response.success?
|
50
|
+
|
51
|
+
raise RequestError.new(request, response, classname)
|
52
|
+
end
|
53
|
+
|
54
|
+
def response
|
55
|
+
raise 'Must implement response method'
|
56
|
+
end
|
57
|
+
|
58
|
+
def request
|
59
|
+
return @request.to_h if @request.respond_to? :to_h
|
60
|
+
|
61
|
+
{}
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -1,35 +1,52 @@
|
|
1
1
|
module ResoTransport
|
2
2
|
class Client
|
3
|
-
attr_reader :connection, :uid, :vendor, :endpoint, :
|
4
|
-
|
3
|
+
attr_reader :connection, :uid, :vendor, :endpoint, :authentication, :md_file, :md_cache, :ds_file, :ds_cache,
|
4
|
+
:use_replication_endpoint
|
5
|
+
|
5
6
|
def initialize(options)
|
6
|
-
@
|
7
|
-
@
|
8
|
-
@
|
9
|
-
@
|
10
|
-
@
|
11
|
-
@
|
12
|
-
@
|
13
|
-
|
14
|
-
@
|
7
|
+
@use_replication_endpoint = options.fetch(:use_replication_endpoint, false)
|
8
|
+
@endpoint = options.fetch(:endpoint)
|
9
|
+
@md_file = options.fetch(:md_file, nil)
|
10
|
+
@ds_file = options.fetch(:ds_file, nil)
|
11
|
+
@authentication = ensure_valid_auth_strategy(options.fetch(:authentication))
|
12
|
+
@vendor = options.fetch(:vendor, {})
|
13
|
+
@faraday_options = options.fetch(:faraday_options, {})
|
14
|
+
@logger = options.fetch(:logger, nil)
|
15
|
+
@md_cache = options.fetch(:md_cache, ResoTransport::MetadataCache)
|
16
|
+
@ds_cache = options.fetch(:ds_cache, ResoTransport::MetadataCache)
|
17
|
+
@connection = establish_connection
|
18
|
+
end
|
19
|
+
|
20
|
+
def establish_connection
|
21
|
+
Faraday.new(@endpoint, @faraday_options) do |faraday|
|
15
22
|
faraday.request :url_encoded
|
16
23
|
faraday.response :logger, @logger || ResoTransport.configuration.logger
|
17
|
-
#yield faraday if block_given?
|
18
24
|
faraday.use Authentication::Middleware, @authentication
|
19
|
-
faraday.adapter Faraday.default_adapter #unless faraday.builder.send(:adapter_set?)
|
25
|
+
faraday.adapter Faraday.default_adapter # unless faraday.builder.send(:adapter_set?)
|
20
26
|
end
|
21
27
|
end
|
22
28
|
|
23
29
|
def resources
|
24
|
-
@resources ||= metadata.entity_sets.map {|es| {es.name =>
|
30
|
+
@resources ||= metadata.entity_sets.map { |es| { es.name => resource_for(es) } }.reduce(:merge!)
|
31
|
+
end
|
32
|
+
|
33
|
+
def resource_for(entity_set)
|
34
|
+
localizations = {}
|
35
|
+
localizations = datasystem.localizations_for(entity_set.entity_type) if metadata.datasystem?
|
36
|
+
|
37
|
+
Resource.new(self, entity_set, localizations)
|
25
38
|
end
|
26
39
|
|
27
40
|
def metadata
|
28
41
|
@metadata ||= Metadata.new(self)
|
29
42
|
end
|
30
43
|
|
44
|
+
def datasystem
|
45
|
+
@datasystem ||= Datasystem.new(self)
|
46
|
+
end
|
47
|
+
|
31
48
|
def to_s
|
32
|
-
%(#<ResoTransport::Client endpoint="#{endpoint}", md_file="#{md_file}">)
|
49
|
+
%(#<ResoTransport::Client endpoint="#{endpoint}", md_file="#{md_file}", ds_file="#{ds_file}">)
|
33
50
|
end
|
34
51
|
|
35
52
|
def inspect
|
@@ -41,7 +58,7 @@ module ResoTransport
|
|
41
58
|
def ensure_valid_auth_strategy(options)
|
42
59
|
case options
|
43
60
|
when Hash
|
44
|
-
if options.
|
61
|
+
if options.key?(:endpoint)
|
45
62
|
Authentication::FetchTokenAuth.new(options)
|
46
63
|
else
|
47
64
|
Authentication::StaticTokenAuth.new(options)
|
@@ -50,6 +67,5 @@ module ResoTransport
|
|
50
67
|
raise ArgumentError, "#{options.inspect} invalid: cannot determine strategy"
|
51
68
|
end
|
52
69
|
end
|
53
|
-
|
54
70
|
end
|
55
71
|
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require_relative 'base_metadata'
|
2
|
+
|
3
|
+
module ResoTransport
|
4
|
+
class Datasystem < BaseMetadata
|
5
|
+
def initialize(client)
|
6
|
+
super client
|
7
|
+
@prefix = 'ds'
|
8
|
+
@classname = self.class.name
|
9
|
+
end
|
10
|
+
|
11
|
+
def localizations_for(resource_name)
|
12
|
+
localizations = parser.resources.dig(resource_name, 'Localizations') || []
|
13
|
+
localizations.map { |l| [l['Name'], l] }.to_h
|
14
|
+
end
|
15
|
+
|
16
|
+
def response
|
17
|
+
@response ||= client.connection.get('DataSystem') do |req|
|
18
|
+
req.headers['Accept'] = 'application/json'
|
19
|
+
@request = req
|
20
|
+
end
|
21
|
+
rescue Faraday::ConnectionFailed
|
22
|
+
raise NoResponse.new(request, nil, 'DataSystem')
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module ResoTransport
|
2
|
+
class DatasystemParser
|
3
|
+
def parse(doc)
|
4
|
+
begin
|
5
|
+
data = doc.is_a?(File) ? doc.read : doc
|
6
|
+
@json = JSON.parse data
|
7
|
+
rescue JSON::ParserError => e
|
8
|
+
@json = {}
|
9
|
+
puts e.message
|
10
|
+
end
|
11
|
+
self
|
12
|
+
end
|
13
|
+
|
14
|
+
# value ->
|
15
|
+
# Resources ->
|
16
|
+
# Name ->
|
17
|
+
# ResourcePath ->
|
18
|
+
# Localizations ->
|
19
|
+
# Name ->
|
20
|
+
# ResourcePath ->
|
21
|
+
|
22
|
+
def resources
|
23
|
+
@resources ||= @json['value'].map { |v| v['Resources'] }.flatten.compact.map { |r| [r['Name'], r] }.to_h
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -1,11 +1,9 @@
|
|
1
1
|
module ResoTransport
|
2
2
|
EntitySet = Struct.new(:name, :schema, :entity_type) do
|
3
|
-
|
4
3
|
def self.from_stream(args)
|
5
|
-
schema, entity_type = ResoTransport.split_schema_and_class_name(args[
|
4
|
+
schema, entity_type = ResoTransport.split_schema_and_class_name(args['EntityType'])
|
6
5
|
|
7
|
-
new(args[
|
6
|
+
new(args['Name'], schema, entity_type)
|
8
7
|
end
|
9
|
-
|
10
8
|
end
|
11
9
|
end
|
@@ -1,30 +1,29 @@
|
|
1
1
|
module ResoTransport
|
2
2
|
EntityType = Struct.new(:name, :base_type, :primary_key, :schema) do
|
3
|
-
|
4
3
|
def self.from_stream(args)
|
5
|
-
new(args[
|
4
|
+
new(args['Name'], args['BaseType'])
|
6
5
|
end
|
7
6
|
|
8
7
|
def parse(record)
|
9
|
-
record.each_pair do |k,v|
|
8
|
+
record.each_pair do |k, v|
|
10
9
|
next if v.nil?
|
11
|
-
|
12
|
-
|
13
|
-
|
10
|
+
|
11
|
+
property = property_map[k] || navigation_property_map[k]
|
12
|
+
record[k] = property.parse(v) if property
|
14
13
|
end
|
15
14
|
end
|
16
15
|
|
17
16
|
def parse_value(record)
|
18
|
-
record.each_pair do |k,v|
|
17
|
+
record.each_pair do |k, v|
|
19
18
|
next if v.nil?
|
20
|
-
|
21
|
-
|
22
|
-
|
19
|
+
|
20
|
+
property = property_map[k] || navigation_property_map[k]
|
21
|
+
record[k] = property.parse(v) if property
|
23
22
|
end
|
24
23
|
end
|
25
24
|
|
26
25
|
def property_map
|
27
|
-
@property_map ||= properties.
|
26
|
+
@property_map ||= properties.each_with_object({}) { |p, hsh| hsh[p.name] = p; }
|
28
27
|
end
|
29
28
|
|
30
29
|
def properties
|
@@ -32,7 +31,7 @@ module ResoTransport
|
|
32
31
|
end
|
33
32
|
|
34
33
|
def navigation_property_map
|
35
|
-
@navigation_property_map ||= navigation_properties.
|
34
|
+
@navigation_property_map ||= navigation_properties.each_with_object({}) { |p, hsh| hsh[p.name] = p; }
|
36
35
|
end
|
37
36
|
|
38
37
|
def navigation_properties
|
@@ -42,6 +41,5 @@ module ResoTransport
|
|
42
41
|
def enumerations
|
43
42
|
@enumerations ||= []
|
44
43
|
end
|
45
|
-
|
46
44
|
end
|
47
45
|
end
|