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.
@@ -0,0 +1,69 @@
1
+ module ResoTransport
2
+ class ResourceError < StandardError
3
+ attr_reader :resource
4
+
5
+ def initialize(resource)
6
+ @resource = resource
7
+ super message
8
+ end
9
+
10
+ def resource_name
11
+ return resource.name if resource.respond_to?(:name)
12
+
13
+ resource || 'unknown'
14
+ end
15
+
16
+ def message
17
+ "Request error for #{resource_name}"
18
+ end
19
+ end
20
+
21
+ class EncodeError < ResourceError
22
+ def initialize(resource, property)
23
+ @property = property
24
+ super resource
25
+ end
26
+
27
+ def message
28
+ "Property #{@property} not found for #{resource_name}"
29
+ end
30
+ end
31
+
32
+ class LocalizationRequired < ResourceError
33
+ def message
34
+ "Localization required for #{resource_name}"
35
+ end
36
+ end
37
+
38
+ class RequestError < ResourceError
39
+ attr_reader :request, :response
40
+
41
+ def initialize(request, response, resource = nil)
42
+ @response = response.respond_to?(:to_hash) ? response.to_hash : response
43
+ @request = request
44
+ super resource
45
+ end
46
+
47
+ def message
48
+ "Received #{response[:status]} for #{resource_name}"
49
+ end
50
+ end
51
+
52
+ class NoResponse < RequestError
53
+ def message
54
+ "No response for #{resource_name}"
55
+ end
56
+ end
57
+
58
+ class ResponseError < RequestError
59
+ def message
60
+ "Request succeeded for #{resource_name} with response errors"
61
+ end
62
+ end
63
+
64
+ class AccessDenied < RequestError
65
+ def message
66
+ "Access denied: #{response.reason_phrase}"
67
+ end
68
+ end
69
+ end
@@ -1,10 +1,12 @@
1
- module ResoTransport
2
- Metadata = Struct.new(:client) do
1
+ require_relative 'base_metadata'
3
2
 
4
- MIME_TYPES = {
5
- xml: "application/xml",
6
- json: "application/json"
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,34 +16,17 @@ module ResoTransport
14
16
  parser.schemas
15
17
  end
16
18
 
17
- def parser
18
- @parser ||= MetadataParser.new.parse(get_data)
19
+ def datasystem?
20
+ parser.datasystem?
19
21
  end
20
22
 
21
- def md_cache
22
- @md_cache ||= client.md_cache.new(client.md_file)
23
- end
24
-
25
- def get_data
26
- if client.md_file
27
- md_cache.read || md_cache.write(raw)
28
- else
29
- raw
30
- end
31
- end
32
-
33
- def raw
34
- resp = client.connection.get("$metadata") do |req|
23
+ def response
24
+ @response ||= client.connection.get('$metadata') do |req|
35
25
  req.headers['Accept'] = MIME_TYPES[client.vendor.fetch(:metadata_format, :xml).to_sym]
26
+ @request = req
36
27
  end
37
-
38
- if resp.success?
39
- resp.body
40
- else
41
- puts resp.body
42
- raise "Error getting metadata!"
43
- end
28
+ rescue Faraday::ConnectionFailed
29
+ raise NoResponse.new(request, nil, '$metadata')
44
30
  end
45
-
46
31
  end
47
32
  end
@@ -7,15 +7,14 @@ module ResoTransport
7
7
  end
8
8
 
9
9
  def read
10
- if File.exist?(name) && File.size(name) > 0
11
- File.new(name)
12
- end
10
+ return nil if !File.exist?(name) || File.size(name).zero?
11
+
12
+ File.new(name)
13
13
  end
14
14
 
15
15
  def write(raw)
16
- File.open(name, "w") {|f| f.write(raw.force_encoding("UTF-8")) } unless raw.length == 0
16
+ File.open(name, 'w') { |f| f.write(raw.force_encoding('UTF-8')) } if raw.length.positive?
17
17
  File.new(name)
18
18
  end
19
-
20
19
  end
21
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
- return self
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 "Schema"
59
+ when 'Schema'
58
60
  @schemas << ResoTransport::Schema.from_stream(args)
59
- when "EntitySet"
61
+ when 'EntitySet'
60
62
  @entity_sets << ResoTransport::EntitySet.from_stream(args)
61
- when "EntityType"
63
+ when 'EntityType'
62
64
  @current_entity_type = ResoTransport::EntityType.from_stream(args)
63
- when "ComplexType"
65
+ when 'ComplexType'
64
66
  @current_complex_type = ResoTransport::EntityType.from_stream(args)
65
- when "PropertyRef"
67
+ when 'PropertyRef'
66
68
  @current_entity_type.primary_key = args['Name']
67
- when "Property"
68
- @current_entity_type.properties << ResoTransport::Property.from_stream(args.merge(schema: @schemas.last)) if @current_entity_type
69
- @current_complex_type.properties << ResoTransport::Property.from_stream(args.merge(schema: @schemas.last)) if @current_complex_type
70
- when "NavigationProperty"
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 "EnumType"
78
+ when 'EnumType'
73
79
  @current_enum_type = ResoTransport::Enum.from_stream(args.merge(schema: @schemas.last))
74
- when "Member"
80
+ when 'Member'
75
81
  @current_member = ResoTransport::Member.from_stream(args)
76
- when "Annotation"
82
+ when 'Annotation'
77
83
  if @current_enum_type && @current_member
78
84
  @current_member.annotation = args['String']
79
- else
80
- if @current_entity_type || @current_complex_type
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 "EntityType"
96
+ when 'EntityType'
93
97
  @current_entity_type.schema = @schemas.last.namespace
94
98
  @schemas.last.entity_types << @current_entity_type
95
- when "ComplexType"
99
+ when 'ComplexType'
96
100
  @current_complex_type.schema = @schemas.last.namespace
97
101
  @schemas.last.complex_types << @current_complex_type
98
- when "EnumType"
102
+ when 'EnumType'
99
103
  @enumerations << @current_enum_type
100
104
  @current_enum_type = nil
101
- when "Member"
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
@@ -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
- return self
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
- return self
14
+ self
16
15
  end
17
16
 
18
- [:eq, :ne, :gt, :ge, :lt, :le].each do |op|
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,67 +25,70 @@ module ResoTransport
26
25
 
27
26
  def limit(size)
28
27
  options[:top] = size
29
- return self
28
+ self
30
29
  end
31
30
 
32
31
  def offset(size)
33
32
  options[:skip] = size
34
- return self
33
+ self
35
34
  end
36
35
 
37
- def order(field, dir=nil)
38
- options[:orderby] = [field, dir].join(" ").strip
39
- return self
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
- return self
43
+ self
45
44
  end
46
45
 
47
46
  def select(*fields)
48
- os = options.fetch(:select, "").split(",")
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
- return self
50
+ self
52
51
  end
53
52
 
54
53
  def expand(*names)
55
- ex = options.fetch(:expand, "").split(",")
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
- return self
57
+ self
59
58
  end
60
59
 
61
60
  def count
62
- p compile_params
63
61
  limit(1).include_count
64
- resp = resource.get(compile_params)
65
- parsed_body = JSON.parse(resp.body)
66
- parsed_body.fetch("@odata.count", 0)
62
+ parsed = handle_response response
63
+ parsed.fetch('@odata.count', 0)
67
64
  end
68
65
 
69
66
  def results
70
- resp = execute
67
+ parsed = handle_response response
71
68
 
72
- if resp[:success]
73
- resp[:results]
74
- else
75
- puts resp[:meta]
76
- raise "Request Failed"
77
- end
69
+ @next_link = parsed.fetch('@odata.nextLink')
70
+ results = Array(parsed.delete('value'))
71
+ resource.parse(results)
78
72
  end
79
73
 
80
- def execute
81
- resp = resource.get(compile_params)
82
- parsed_body = JSON.parse(resp.body)
83
- results = Array(parsed_body.delete("value"))
74
+ # Can only be accessed after results call
75
+ def next_link
76
+ @next_link
77
+ end
84
78
 
85
- {
86
- success: resp.success? && !parsed_body.has_key?("error"),
87
- meta: parsed_body,
88
- results: resource.parse(results)
89
- }
79
+ def response
80
+ resource.get(compile_params)
81
+ rescue Faraday::ConnectionFailed
82
+ raise NoResponse.new(resource.request, nil, resource)
83
+ end
84
+
85
+ def handle_response(response)
86
+ raise RequestError.new(resource.request, response, resource) unless response.success?
87
+
88
+ parsed = JSON.parse(response.body)
89
+ raise ResponseError.new(resource.request, response, resource) if parsed.key?('error')
90
+
91
+ parsed
90
92
  end
91
93
 
92
94
  def new_query_context(context)
@@ -110,7 +112,7 @@ module ResoTransport
110
112
  end
111
113
 
112
114
  def sub_queries
113
- @sub_queries ||= Hash.new {|h,k| h[k] = { context: 'and', criteria: [] } }
115
+ @sub_queries ||= Hash.new { |h, k| h[k] = { context: 'and', criteria: [] } }
114
116
  end
115
117
 
116
118
  def compile_filters
@@ -120,36 +122,32 @@ module ResoTransport
120
122
 
121
123
  filter_chunks = []
122
124
 
123
- if global && global[:criteria]&.any?
124
- filter_chunks << global[:criteria].join(" #{global[:context]} ")
125
- end
125
+ filter_chunks << global[:criteria].join(" #{global[:context]} ") if global && global[:criteria]&.any?
126
126
 
127
127
  filter_chunks << filter_groups.map do |g|
128
128
  "(#{g[:criteria].join(" #{g[:context]} ")})"
129
- end.join(" and ")
129
+ end.join(' and ')
130
130
 
131
- filter_chunks.reject {|c| c == ""}.join(" and ")
131
+ filter_chunks.reject { |c| c == '' }.join(' and ')
132
132
  end
133
133
 
134
134
  def compile_params
135
135
  params = {}
136
136
 
137
- options.each_pair do |k,v|
137
+ options.each_pair do |k, v|
138
138
  params["$#{k}"] = v
139
139
  end
140
140
 
141
- if !sub_queries.empty?
142
- params["$filter"] = compile_filters
143
- end
141
+ params['$filter'] = compile_filters unless sub_queries.empty?
144
142
 
145
143
  params
146
144
  end
147
145
 
148
- def encode_value(key, v)
146
+ def encode_value(key, val)
149
147
  field = resource.property(key.to_s)
150
- raise "Couldn't find property #{key} for #{resource.name}" if field.nil?
151
- field.encode(v)
152
- end
148
+ raise EncodeError.new(resource, key) if field.nil?
153
149
 
150
+ field.encode(val)
151
+ end
154
152
  end
155
153
  end
@@ -1,6 +1,5 @@
1
1
  module ResoTransport
2
- Resource = Struct.new(:client, :entity_set) do
3
-
2
+ Resource = Struct.new(:client, :entity_set, :localizations, :local) do
4
3
  def query
5
4
  Query.new(self)
6
5
  end
@@ -10,7 +9,7 @@ module ResoTransport
10
9
  end
11
10
 
12
11
  def property(name)
13
- properties.detect {|p| p.name == name }
12
+ properties.detect { |p| p.name == name }
14
13
  end
15
14
 
16
15
  def properties
@@ -20,13 +19,13 @@ module ResoTransport
20
19
  def expandable
21
20
  entity_type.navigation_properties
22
21
  end
23
-
22
+
24
23
  def entity_type
25
- @entity_type ||= schema.entity_types.detect {|et| et.name == entity_set.entity_type }
24
+ @entity_type ||= schema.entity_types.detect { |et| et.name == entity_set.entity_type }
26
25
  end
27
26
 
28
27
  def schema
29
- @schema ||= md.schemas.detect {|s| s.namespace == entity_set.schema }
28
+ @schema ||= md.schemas.detect { |s| s.namespace == entity_set.schema }
30
29
  end
31
30
 
32
31
  def md
@@ -34,15 +33,31 @@ module ResoTransport
34
33
  end
35
34
 
36
35
  def parse(results)
37
- results.map {|r| entity_type.parse(r) }
36
+ results.map { |r| entity_type.parse(r) }
38
37
  end
39
38
 
40
39
  def get(params)
41
- client.connection.get(name, params) do |req|
40
+ client.connection.get(url, params) do |req|
42
41
  req.headers['Accept'] = 'application/json'
42
+ @request = req
43
43
  end
44
44
  end
45
45
 
46
+ def url
47
+ return local['ResourcePath'].gsub(%r{^/}, '') if local
48
+
49
+ raise LocalizationRequired, self if localizations.any? && local.nil?
50
+
51
+ return "#{name}/replication" if client.use_replication_endpoint
52
+
53
+ name
54
+ end
55
+
56
+ def localization(name)
57
+ self.local = localizations[name] if localizations.key?(name)
58
+ self
59
+ end
60
+
46
61
  def to_s
47
62
  %(#<ResoTransport::Resource entity_set="#{name}", schema="#{schema&.namespace}">)
48
63
  end
@@ -51,5 +66,10 @@ module ResoTransport
51
66
  to_s
52
67
  end
53
68
 
69
+ def request
70
+ return @request.to_h if @request.respond_to? :to_h
71
+
72
+ {}
73
+ end
54
74
  end
55
75
  end
@@ -1,3 +1,3 @@
1
1
  module ResoTransport
2
- VERSION = "1.5.4"
2
+ VERSION = '1.5.9'.freeze
3
3
  end
@@ -5,41 +5,28 @@ require 'faraday'
5
5
  require 'json'
6
6
  require 'time'
7
7
 
8
- require "reso_transport/version"
9
- require "reso_transport/configuration"
10
- require "reso_transport/authentication"
11
- require "reso_transport/client"
12
- require "reso_transport/resource"
13
- require "reso_transport/metadata"
14
- require "reso_transport/metadata_cache"
15
- require "reso_transport/metadata_parser"
16
- require "reso_transport/schema"
17
- require "reso_transport/entity_set"
18
- require "reso_transport/entity_type"
19
- require "reso_transport/enum"
20
- require "reso_transport/property"
21
- require "reso_transport/query"
22
-
23
-
24
-
25
- # module Faraday
26
- # module Utils
27
-
28
- # def escape(str)
29
- # str.to_s.gsub(ESCAPE_RE) do |match|
30
- # '%' + match.unpack('H2' * match.bytesize).join('%').upcase
31
- # end.gsub(" ","%20")
32
-
33
- # end
34
- # end
35
- # end
36
-
37
- Faraday::Utils.default_space_encoding = "%20"
8
+ require 'reso_transport/version'
9
+ require 'reso_transport/configuration'
10
+ require 'reso_transport/authentication'
11
+ require 'reso_transport/client'
12
+ require 'reso_transport/resource'
13
+ require 'reso_transport/metadata'
14
+ require 'reso_transport/metadata_cache'
15
+ require 'reso_transport/metadata_parser'
16
+ require 'reso_transport/datasystem'
17
+ require 'reso_transport/datasystem_parser'
18
+ require 'reso_transport/schema'
19
+ require 'reso_transport/entity_set'
20
+ require 'reso_transport/entity_type'
21
+ require 'reso_transport/enum'
22
+ require 'reso_transport/property'
23
+ require 'reso_transport/query'
24
+ require 'reso_transport/errors'
25
+
26
+ Faraday::Utils.default_space_encoding = '%20'
38
27
 
39
28
  module ResoTransport
40
- class Error < StandardError; end
41
- class AccessDenied < StandardError; end
42
- ODATA_TIME_FORMAT = "%Y-%m-%dT%H:%M:%S%Z"
29
+ ODATA_TIME_FORMAT = '%Y-%m-%dT%H:%M:%SZ'.freeze
43
30
 
44
31
  class << self
45
32
  attr_writer :configuration
@@ -53,8 +40,7 @@ module ResoTransport
53
40
  yield(configuration)
54
41
  end
55
42
 
56
- def self.split_schema_and_class_name(s)
57
- s.to_s.partition(/(\w+)$/).first(2).map {|s| s.sub(/\.$/, '') }
43
+ def self.split_schema_and_class_name(text)
44
+ text.to_s.partition(/(\w+)$/).first(2).map { |s| s.sub(/\.$/, '') }
58
45
  end
59
-
60
46
  end
@@ -1,35 +1,35 @@
1
-
2
- lib = File.expand_path("../lib", __FILE__)
1
+ lib = File.expand_path('lib', __dir__)
3
2
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
- require "reso_transport/version"
3
+ require 'reso_transport/version'
5
4
 
6
5
  Gem::Specification.new do |spec|
7
- spec.name = "reso_transport"
6
+ spec.name = 'reso_transport'
8
7
  spec.version = ResoTransport::VERSION
9
- spec.authors = ["Jon Druse"]
10
- spec.email = ["jon@wrstudios.com"]
8
+ spec.authors = ['Jon Druse']
9
+ spec.email = ['jon@wrstudios.com']
11
10
 
12
- spec.summary = "A utility for consuming RESO Web API connections"
13
- spec.description = "Supports Trestle, Spark, Bridge Interactive, MLS Grid"
14
- spec.homepage = "http://github.com/wrstudios/reso_transport"
15
- spec.license = "MIT"
11
+ spec.summary = 'A utility for consuming RESO Web API connections'
12
+ spec.description = 'Supports Trestle, Spark, Bridge Interactive, MLS Grid'
13
+ spec.homepage = 'http://github.com/wrstudios/reso_transport'
14
+ spec.license = 'MIT'
16
15
 
16
+ spec.required_ruby_version = '>= 2.6'
17
17
 
18
18
  # Specify which files should be added to the gem when it is released.
19
19
  # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
20
- spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
20
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
21
21
  `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
22
22
  end
23
- spec.bindir = "exe"
23
+ spec.bindir = 'exe'
24
24
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
25
- spec.require_paths = ["lib"]
25
+ spec.require_paths = ['lib']
26
26
 
27
- spec.add_dependency "faraday", "~> 1.0.1"
27
+ spec.add_dependency 'faraday', '~> 1.0.1'
28
28
 
29
- spec.add_development_dependency "bundler", "~> 2"
30
- spec.add_development_dependency "rake", "~> 13"
31
- spec.add_development_dependency "minitest", "~> 5.0"
32
- spec.add_development_dependency "minitest-rg", "~> 5.0"
33
- spec.add_development_dependency "vcr", "~> 6.0"
34
- spec.add_development_dependency "byebug"
29
+ spec.add_development_dependency 'bundler', '~> 2'
30
+ spec.add_development_dependency 'byebug', '~> 11'
31
+ spec.add_development_dependency 'minitest', '~> 5.0'
32
+ spec.add_development_dependency 'minitest-rg', '~> 5.0'
33
+ spec.add_development_dependency 'rake', '~> 13'
34
+ spec.add_development_dependency 'vcr', '~> 6.0'
35
35
  end