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.
@@ -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