reso_transport 1.5.4 → 1.5.9
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![Gitpod ready-to-code](https://img.shields.io/badge/Gitpod-ready--to--code-blue?logo=gitpod)](https://gitpod.io/#https://github.com/wrstudios/reso_transport)
|
2
|
+
[![Gem Version](https://badge.fury.io/rb/reso_transport.svg)](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
|