reso_transport 1.5.1 → 1.5.7
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 +83 -7
- data/bin/console +10 -10
- data/bin/rake +29 -0
- data/lib/reso_transport.rb +22 -33
- data/lib/reso_transport/authentication/fetch_token_auth.rb +30 -11
- data/lib/reso_transport/base_metadata.rb +61 -0
- data/lib/reso_transport/client.rb +34 -19
- data/lib/reso_transport/datasystem.rb +22 -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/metadata.rb +12 -32
- data/lib/reso_transport/metadata_cache.rb +20 -0
- data/lib/reso_transport/metadata_parser.rb +31 -22
- data/lib/reso_transport/query.rb +43 -41
- data/lib/reso_transport/resource.rb +22 -9
- data/lib/reso_transport/version.rb +1 -1
- data/reso_transport.gemspec +20 -20
- metadata +21 -14
@@ -1,36 +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
|
-
@
|
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|
|
14
22
|
faraday.request :url_encoded
|
15
|
-
|
16
|
-
# faraday.response :logger, @logger if @logger
|
17
|
-
faraday.response :logger
|
18
|
-
#yield faraday if block_given?
|
23
|
+
faraday.response :logger, @logger || ResoTransport.configuration.logger
|
19
24
|
faraday.use Authentication::Middleware, @authentication
|
20
|
-
faraday.adapter Faraday.default_adapter #unless faraday.builder.send(:adapter_set?)
|
25
|
+
faraday.adapter Faraday.default_adapter # unless faraday.builder.send(:adapter_set?)
|
21
26
|
end
|
22
27
|
end
|
23
28
|
|
24
29
|
def resources
|
25
|
-
@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)
|
26
38
|
end
|
27
39
|
|
28
40
|
def metadata
|
29
41
|
@metadata ||= Metadata.new(self)
|
30
42
|
end
|
31
43
|
|
44
|
+
def datasystem
|
45
|
+
@datasystem ||= Datasystem.new(self)
|
46
|
+
end
|
47
|
+
|
32
48
|
def to_s
|
33
|
-
%(#<ResoTransport::Client endpoint="#{endpoint}", md_file="#{md_file}">)
|
49
|
+
%(#<ResoTransport::Client endpoint="#{endpoint}", md_file="#{md_file}", ds_file="#{ds_file}">)
|
34
50
|
end
|
35
51
|
|
36
52
|
def inspect
|
@@ -42,7 +58,7 @@ module ResoTransport
|
|
42
58
|
def ensure_valid_auth_strategy(options)
|
43
59
|
case options
|
44
60
|
when Hash
|
45
|
-
if options.
|
61
|
+
if options.key?(:endpoint)
|
46
62
|
Authentication::FetchTokenAuth.new(options)
|
47
63
|
else
|
48
64
|
Authentication::StaticTokenAuth.new(options)
|
@@ -51,6 +67,5 @@ module ResoTransport
|
|
51
67
|
raise ArgumentError, "#{options.inspect} invalid: cannot determine strategy"
|
52
68
|
end
|
53
69
|
end
|
54
|
-
|
55
70
|
end
|
56
71
|
end
|
@@ -0,0 +1,22 @@
|
|
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
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
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
|
@@ -1,10 +1,12 @@
|
|
1
|
-
|
2
|
-
Metadata = Struct.new(:client) do
|
1
|
+
require_relative 'base_metadata'
|
3
2
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
3
|
+
module ResoTransport
|
4
|
+
class Metadata < BaseMetadata
|
5
|
+
def initialize(client)
|
6
|
+
super client
|
7
|
+
@prefix = 'md'
|
8
|
+
@classname = self.class.name
|
9
|
+
end
|
8
10
|
|
9
11
|
def entity_sets
|
10
12
|
parser.entity_sets
|
@@ -14,36 +16,14 @@ module ResoTransport
|
|
14
16
|
parser.schemas
|
15
17
|
end
|
16
18
|
|
17
|
-
def
|
18
|
-
|
19
|
+
def datasystem?
|
20
|
+
parser.datasystem?
|
19
21
|
end
|
20
22
|
|
21
|
-
def
|
22
|
-
|
23
|
-
if File.exist?(client.md_file) && File.size(client.md_file) > 0
|
24
|
-
File.new(client.md_file)
|
25
|
-
else
|
26
|
-
raw_md = raw
|
27
|
-
File.open(client.md_file, "w") {|f| f.write(raw.force_encoding("UTF-8")) } unless raw_md.length == 0
|
28
|
-
File.new(client.md_file)
|
29
|
-
end
|
30
|
-
else
|
31
|
-
raw
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
|
-
def raw
|
36
|
-
resp = client.connection.get("$metadata") do |req|
|
23
|
+
def response
|
24
|
+
@response ||= client.connection.get('$metadata') do |req|
|
37
25
|
req.headers['Accept'] = MIME_TYPES[client.vendor.fetch(:metadata_format, :xml).to_sym]
|
38
26
|
end
|
39
|
-
|
40
|
-
if resp.success?
|
41
|
-
resp.body
|
42
|
-
else
|
43
|
-
puts resp.body
|
44
|
-
raise "Error getting metadata!"
|
45
|
-
end
|
46
27
|
end
|
47
|
-
|
48
28
|
end
|
49
29
|
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module ResoTransport
|
2
|
+
class MetadataCache
|
3
|
+
attr_reader :name
|
4
|
+
|
5
|
+
def initialize(name)
|
6
|
+
@name = name
|
7
|
+
end
|
8
|
+
|
9
|
+
def read
|
10
|
+
return nil if !File.exist?(name) || File.size(name).zero?
|
11
|
+
|
12
|
+
File.new(name)
|
13
|
+
end
|
14
|
+
|
15
|
+
def write(raw)
|
16
|
+
File.open(name, 'w') { |f| f.write(raw.force_encoding('UTF-8')) } if raw.length.positive?
|
17
|
+
File.new(name)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -14,12 +14,14 @@ module ResoTransport
|
|
14
14
|
@current_complex_type = nil
|
15
15
|
@current_enum_type = nil
|
16
16
|
@current_member = nil
|
17
|
+
|
18
|
+
@datasystem = nil
|
17
19
|
end
|
18
20
|
|
19
21
|
def parse(doc)
|
20
22
|
REXML::Document.parse_stream(doc, self)
|
21
23
|
finalize
|
22
|
-
|
24
|
+
self
|
23
25
|
end
|
24
26
|
|
25
27
|
def finalize
|
@@ -54,55 +56,62 @@ module ResoTransport
|
|
54
56
|
|
55
57
|
def tag_start(name, args)
|
56
58
|
case name
|
57
|
-
when
|
59
|
+
when 'Schema'
|
58
60
|
@schemas << ResoTransport::Schema.from_stream(args)
|
59
|
-
when
|
61
|
+
when 'EntitySet'
|
60
62
|
@entity_sets << ResoTransport::EntitySet.from_stream(args)
|
61
|
-
when
|
63
|
+
when 'EntityType'
|
62
64
|
@current_entity_type = ResoTransport::EntityType.from_stream(args)
|
63
|
-
when
|
65
|
+
when 'ComplexType'
|
64
66
|
@current_complex_type = ResoTransport::EntityType.from_stream(args)
|
65
|
-
when
|
67
|
+
when 'PropertyRef'
|
66
68
|
@current_entity_type.primary_key = args['Name']
|
67
|
-
when
|
68
|
-
|
69
|
-
|
70
|
-
|
69
|
+
when 'Property'
|
70
|
+
if @current_entity_type
|
71
|
+
@current_entity_type.properties << ResoTransport::Property.from_stream(args.merge(schema: @schemas.last))
|
72
|
+
end
|
73
|
+
if @current_complex_type
|
74
|
+
@current_complex_type.properties << ResoTransport::Property.from_stream(args.merge(schema: @schemas.last))
|
75
|
+
end
|
76
|
+
when 'NavigationProperty'
|
71
77
|
@current_entity_type.navigation_properties << ResoTransport::Property.from_stream(args)
|
72
|
-
when
|
78
|
+
when 'EnumType'
|
73
79
|
@current_enum_type = ResoTransport::Enum.from_stream(args.merge(schema: @schemas.last))
|
74
|
-
when
|
80
|
+
when 'Member'
|
75
81
|
@current_member = ResoTransport::Member.from_stream(args)
|
76
|
-
when
|
82
|
+
when 'Annotation'
|
77
83
|
if @current_enum_type && @current_member
|
78
84
|
@current_member.annotation = args['String']
|
79
|
-
|
80
|
-
|
81
|
-
#raise args.inspect
|
82
|
-
end
|
85
|
+
elsif @current_entity_type || @current_complex_type
|
86
|
+
# raise args.inspect
|
83
87
|
end
|
84
88
|
end
|
85
|
-
rescue => e
|
89
|
+
rescue StandardError => e
|
86
90
|
puts e.inspect
|
87
91
|
puts "Error processing Tag: #{[name, args].inspect}"
|
88
92
|
end
|
89
93
|
|
90
94
|
def tag_end(name)
|
91
95
|
case name
|
92
|
-
when
|
96
|
+
when 'EntityType'
|
93
97
|
@current_entity_type.schema = @schemas.last.namespace
|
94
98
|
@schemas.last.entity_types << @current_entity_type
|
95
|
-
when
|
99
|
+
when 'ComplexType'
|
96
100
|
@current_complex_type.schema = @schemas.last.namespace
|
97
101
|
@schemas.last.complex_types << @current_complex_type
|
98
|
-
when
|
102
|
+
when 'EnumType'
|
99
103
|
@enumerations << @current_enum_type
|
100
104
|
@current_enum_type = nil
|
101
|
-
when
|
105
|
+
when 'Member'
|
102
106
|
@current_enum_type.members << @current_member
|
103
107
|
@current_member = nil
|
104
108
|
end
|
105
109
|
end
|
106
110
|
|
111
|
+
def datasystem?
|
112
|
+
return @datasystem unless @datasystem.nil?
|
113
|
+
|
114
|
+
@datasystem = @schemas.any? { |s| s.entity_types.any? { |t| t.name == 'DataSystem' } }
|
115
|
+
end
|
107
116
|
end
|
108
117
|
end
|
data/lib/reso_transport/query.rb
CHANGED
@@ -1,23 +1,22 @@
|
|
1
1
|
module ResoTransport
|
2
2
|
Query = Struct.new(:resource) do
|
3
|
-
|
4
|
-
def all(*contexts, &block)
|
3
|
+
def all(*_contexts, &block)
|
5
4
|
new_query_context('and')
|
6
5
|
instance_eval(&block)
|
7
6
|
clear_query_context
|
8
|
-
|
7
|
+
self
|
9
8
|
end
|
10
9
|
|
11
10
|
def any(&block)
|
12
11
|
new_query_context('or')
|
13
12
|
instance_eval(&block)
|
14
13
|
clear_query_context
|
15
|
-
|
14
|
+
self
|
16
15
|
end
|
17
16
|
|
18
|
-
[
|
17
|
+
%i[eq ne gt ge lt le].each do |op|
|
19
18
|
define_method(op) do |conditions|
|
20
|
-
conditions.each_pair do |k,v|
|
19
|
+
conditions.each_pair do |k, v|
|
21
20
|
current_query_context << "#{k} #{op} #{encode_value(k, v)}"
|
22
21
|
end
|
23
22
|
return self
|
@@ -26,36 +25,36 @@ module ResoTransport
|
|
26
25
|
|
27
26
|
def limit(size)
|
28
27
|
options[:top] = size
|
29
|
-
|
28
|
+
self
|
30
29
|
end
|
31
30
|
|
32
31
|
def offset(size)
|
33
32
|
options[:skip] = size
|
34
|
-
|
33
|
+
self
|
35
34
|
end
|
36
35
|
|
37
|
-
def order(field, dir=nil)
|
38
|
-
options[:orderby] = [field, dir].join(
|
39
|
-
|
36
|
+
def order(field, dir = nil)
|
37
|
+
options[:orderby] = [field, dir].join(' ').strip
|
38
|
+
self
|
40
39
|
end
|
41
40
|
|
42
41
|
def include_count
|
43
42
|
options[:count] = true
|
44
|
-
|
43
|
+
self
|
45
44
|
end
|
46
45
|
|
47
46
|
def select(*fields)
|
48
|
-
os = options.fetch(:select,
|
49
|
-
options[:select] = (os + Array(fields)).uniq.join(
|
47
|
+
os = options.fetch(:select, '').split(',')
|
48
|
+
options[:select] = (os + Array(fields)).uniq.join(',')
|
50
49
|
|
51
|
-
|
50
|
+
self
|
52
51
|
end
|
53
52
|
|
54
53
|
def expand(*names)
|
55
|
-
ex = options.fetch(:expand,
|
56
|
-
options[:expand] = (ex + Array(names)).uniq.join(
|
54
|
+
ex = options.fetch(:expand, '').split(',')
|
55
|
+
options[:expand] = (ex + Array(names)).uniq.join(',')
|
57
56
|
|
58
|
-
|
57
|
+
self
|
59
58
|
end
|
60
59
|
|
61
60
|
def count
|
@@ -63,7 +62,7 @@ module ResoTransport
|
|
63
62
|
limit(1).include_count
|
64
63
|
resp = resource.get(compile_params)
|
65
64
|
parsed_body = JSON.parse(resp.body)
|
66
|
-
parsed_body.fetch(
|
65
|
+
parsed_body.fetch('@odata.count', 0)
|
67
66
|
end
|
68
67
|
|
69
68
|
def results
|
@@ -73,20 +72,27 @@ module ResoTransport
|
|
73
72
|
resp[:results]
|
74
73
|
else
|
75
74
|
puts resp[:meta]
|
76
|
-
raise
|
75
|
+
raise 'Request Failed'
|
77
76
|
end
|
78
77
|
end
|
79
78
|
|
80
79
|
def execute
|
81
80
|
resp = resource.get(compile_params)
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
81
|
+
if resp.success?
|
82
|
+
parsed_body = JSON.parse(resp.body)
|
83
|
+
results = Array(parsed_body.delete('value'))
|
84
|
+
|
85
|
+
{
|
86
|
+
success: resp.success? && !parsed_body.key?('error'),
|
87
|
+
meta: parsed_body,
|
88
|
+
results: resource.parse(results)
|
89
|
+
}
|
90
|
+
else
|
91
|
+
{
|
92
|
+
success: false,
|
93
|
+
meta: resp.body
|
94
|
+
}
|
95
|
+
end
|
90
96
|
end
|
91
97
|
|
92
98
|
def new_query_context(context)
|
@@ -110,7 +116,7 @@ module ResoTransport
|
|
110
116
|
end
|
111
117
|
|
112
118
|
def sub_queries
|
113
|
-
@sub_queries ||= Hash.new {|h,k| h[k] = { context: 'and', criteria: [] } }
|
119
|
+
@sub_queries ||= Hash.new { |h, k| h[k] = { context: 'and', criteria: [] } }
|
114
120
|
end
|
115
121
|
|
116
122
|
def compile_filters
|
@@ -120,36 +126,32 @@ module ResoTransport
|
|
120
126
|
|
121
127
|
filter_chunks = []
|
122
128
|
|
123
|
-
if global && global[:criteria]&.any?
|
124
|
-
filter_chunks << global[:criteria].join(" #{global[:context]} ")
|
125
|
-
end
|
129
|
+
filter_chunks << global[:criteria].join(" #{global[:context]} ") if global && global[:criteria]&.any?
|
126
130
|
|
127
131
|
filter_chunks << filter_groups.map do |g|
|
128
132
|
"(#{g[:criteria].join(" #{g[:context]} ")})"
|
129
|
-
end.join(
|
133
|
+
end.join(' and ')
|
130
134
|
|
131
|
-
filter_chunks.reject {|c| c ==
|
135
|
+
filter_chunks.reject { |c| c == '' }.join(' and ')
|
132
136
|
end
|
133
137
|
|
134
138
|
def compile_params
|
135
139
|
params = {}
|
136
140
|
|
137
|
-
options.each_pair do |k,v|
|
141
|
+
options.each_pair do |k, v|
|
138
142
|
params["$#{k}"] = v
|
139
143
|
end
|
140
144
|
|
141
|
-
|
142
|
-
params["$filter"] = compile_filters
|
143
|
-
end
|
145
|
+
params['$filter'] = compile_filters unless sub_queries.empty?
|
144
146
|
|
145
147
|
params
|
146
148
|
end
|
147
149
|
|
148
|
-
def encode_value(key,
|
150
|
+
def encode_value(key, val)
|
149
151
|
field = resource.property(key.to_s)
|
150
152
|
raise "Couldn't find property #{key} for #{resource.name}" if field.nil?
|
151
|
-
field.encode(v)
|
152
|
-
end
|
153
153
|
|
154
|
+
field.encode(val)
|
155
|
+
end
|
154
156
|
end
|
155
157
|
end
|