reso_transport 1.0.0
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 +7 -0
- data/.gitignore +15 -0
- data/.travis.yml +7 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +33 -0
- data/LICENSE.txt +21 -0
- data/README.md +193 -0
- data/Rakefile +10 -0
- data/bin/console +30 -0
- data/bin/setup +8 -0
- data/lib/reso_transport/authentication/access.rb +23 -0
- data/lib/reso_transport/authentication/auth_strategy.rb +26 -0
- data/lib/reso_transport/authentication/fetch_token_auth.rb +49 -0
- data/lib/reso_transport/authentication/middleware.rb +46 -0
- data/lib/reso_transport/authentication/static_token_auth.rb +24 -0
- data/lib/reso_transport/authentication.rb +5 -0
- data/lib/reso_transport/client.rb +53 -0
- data/lib/reso_transport/configuration.rb +6 -0
- data/lib/reso_transport/entity_set.rb +11 -0
- data/lib/reso_transport/entity_type.rb +47 -0
- data/lib/reso_transport/enum.rb +37 -0
- data/lib/reso_transport/metadata.rb +48 -0
- data/lib/reso_transport/metadata_parser.rb +104 -0
- data/lib/reso_transport/property.rb +101 -0
- data/lib/reso_transport/query.rb +147 -0
- data/lib/reso_transport/resource.rb +55 -0
- data/lib/reso_transport/schema.rb +21 -0
- data/lib/reso_transport/version.rb +3 -0
- data/lib/reso_transport.rb +57 -0
- data/reso_transport.gemspec +35 -0
- data/sample_secrets.yml +26 -0
- metadata +172 -0
@@ -0,0 +1,47 @@
|
|
1
|
+
module ResoTransport
|
2
|
+
EntityType = Struct.new(:name, :base_type, :primary_key, :schema) do
|
3
|
+
|
4
|
+
def self.from_stream(args)
|
5
|
+
new(args["Name"], args["BaseType"])
|
6
|
+
end
|
7
|
+
|
8
|
+
def parse(record)
|
9
|
+
record.each_pair do |k,v|
|
10
|
+
next if v.nil?
|
11
|
+
if property = (property_map[k] || navigation_property_map[k])
|
12
|
+
record[k] = property.parse(v)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def parse_value(record)
|
18
|
+
record.each_pair do |k,v|
|
19
|
+
next if v.nil?
|
20
|
+
if property = (property_map[k] || navigation_property_map[k])
|
21
|
+
record[k] = property.parse(v)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def property_map
|
27
|
+
@property_map ||= properties.inject({}) {|hsh, p| hsh[p.name] = p; hsh }
|
28
|
+
end
|
29
|
+
|
30
|
+
def properties
|
31
|
+
@properties ||= []
|
32
|
+
end
|
33
|
+
|
34
|
+
def navigation_property_map
|
35
|
+
@navigation_property_map ||= navigation_properties.inject({}) {|hsh, p| hsh[p.name] = p; hsh }
|
36
|
+
end
|
37
|
+
|
38
|
+
def navigation_properties
|
39
|
+
@navigation_properties ||= []
|
40
|
+
end
|
41
|
+
|
42
|
+
def enumerations
|
43
|
+
@enumerations ||= []
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module ResoTransport
|
2
|
+
Member = Struct.new(:name, :value, :annotation) do
|
3
|
+
def self.from_stream(args)
|
4
|
+
new(args["Name"], args["Value"])
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
Enum = Struct.new(:name, :type, :is_flags) do
|
9
|
+
def self.from_stream(args)
|
10
|
+
new("#{args[:schema].namespace}.#{args["Name"]}", args["UnderlyingType"], args["IsFlags"])
|
11
|
+
end
|
12
|
+
|
13
|
+
def members
|
14
|
+
@members ||= []
|
15
|
+
end
|
16
|
+
|
17
|
+
def parse_value(value)
|
18
|
+
mapping.fetch(value, value)
|
19
|
+
end
|
20
|
+
|
21
|
+
def encode_value(value)
|
22
|
+
"'#{mapping.invert.fetch(value, value)}'"
|
23
|
+
end
|
24
|
+
|
25
|
+
def mapping
|
26
|
+
@mapping ||= generate_member_map || {}
|
27
|
+
end
|
28
|
+
|
29
|
+
def generate_member_map
|
30
|
+
members.map {|mem|
|
31
|
+
{ mem.name => mem.annotation || mem.name }
|
32
|
+
}.reduce(:merge!)
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module ResoTransport
|
2
|
+
Metadata = Struct.new(:client) do
|
3
|
+
|
4
|
+
MIME_TYPES = {
|
5
|
+
xml: "application/xml",
|
6
|
+
json: "application/json"
|
7
|
+
}
|
8
|
+
|
9
|
+
def entity_sets
|
10
|
+
parser.entity_sets
|
11
|
+
end
|
12
|
+
|
13
|
+
def schemas
|
14
|
+
parser.schemas
|
15
|
+
end
|
16
|
+
|
17
|
+
def parser
|
18
|
+
@parser ||= MetadataParser.new.parse(get_data)
|
19
|
+
end
|
20
|
+
|
21
|
+
def get_data
|
22
|
+
if client.md_file
|
23
|
+
if File.exist?(client.md_file) && File.size(client.md_file) > 0
|
24
|
+
File.new(client.md_file)
|
25
|
+
else
|
26
|
+
File.open(client.md_file, "w") {|f| f.write(raw) }
|
27
|
+
File.new(client.md_file)
|
28
|
+
end
|
29
|
+
else
|
30
|
+
raw
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def raw
|
35
|
+
resp = client.connection.get("$metadata") do |req|
|
36
|
+
req.headers['Accept'] = MIME_TYPES[client.vendor.fetch(:metadata_format, :xml).to_sym]
|
37
|
+
end
|
38
|
+
|
39
|
+
if resp.success?
|
40
|
+
resp.body
|
41
|
+
else
|
42
|
+
puts resp.body
|
43
|
+
raise "Error getting metadata!"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
module ResoTransport
|
2
|
+
class MetadataParser
|
3
|
+
include REXML::StreamListener
|
4
|
+
|
5
|
+
attr_reader :schemas, :entity_sets, :enumerations
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@schemas = []
|
9
|
+
@entity_sets = []
|
10
|
+
@entity_types = []
|
11
|
+
@enumerations = []
|
12
|
+
|
13
|
+
@current_entity_type = nil
|
14
|
+
@current_complex_type = nil
|
15
|
+
@current_enum_type = nil
|
16
|
+
@current_member = nil
|
17
|
+
end
|
18
|
+
|
19
|
+
def parse(doc)
|
20
|
+
REXML::Document.parse_stream(doc, self)
|
21
|
+
finalize
|
22
|
+
return self
|
23
|
+
end
|
24
|
+
|
25
|
+
def finalize
|
26
|
+
schemas.each do |s|
|
27
|
+
s.entity_types.each do |et|
|
28
|
+
et.properties.each do |p|
|
29
|
+
p.finalize_type(self)
|
30
|
+
end
|
31
|
+
|
32
|
+
et.navigation_properties.each do |p|
|
33
|
+
p.finalize_type(self)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
s.complex_types.each do |et|
|
38
|
+
et.properties.each do |p|
|
39
|
+
p.finalize_type(self)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# Schema ->
|
46
|
+
# EnumType ->
|
47
|
+
# Members ->
|
48
|
+
# Annotation
|
49
|
+
# EntityType ->
|
50
|
+
# Key
|
51
|
+
# Properties ->
|
52
|
+
# enumerations
|
53
|
+
#
|
54
|
+
|
55
|
+
def tag_start(name, args)
|
56
|
+
case name
|
57
|
+
when "Schema"
|
58
|
+
@schemas << ResoTransport::Schema.from_stream(args)
|
59
|
+
when "EntitySet"
|
60
|
+
@entity_sets << ResoTransport::EntitySet.from_stream(args)
|
61
|
+
when "EntityType"
|
62
|
+
@current_entity_type = ResoTransport::EntityType.from_stream(args)
|
63
|
+
when "ComplexType"
|
64
|
+
@current_complex_type = ResoTransport::EntityType.from_stream(args)
|
65
|
+
when "PropertyRef"
|
66
|
+
@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"
|
71
|
+
@current_entity_type.navigation_properties << ResoTransport::Property.from_stream(args)
|
72
|
+
when "EnumType"
|
73
|
+
@current_enum_type = ResoTransport::Enum.from_stream(args.merge(schema: @schemas.last))
|
74
|
+
when "Member"
|
75
|
+
@current_member = ResoTransport::Member.from_stream(args)
|
76
|
+
when "Annotation"
|
77
|
+
if @current_enum_type && @current_member
|
78
|
+
@current_member.annotation = args['String']
|
79
|
+
end
|
80
|
+
end
|
81
|
+
rescue => e
|
82
|
+
puts e.inspect
|
83
|
+
puts "Error processing Tag: #{[name, args].inspect}"
|
84
|
+
end
|
85
|
+
|
86
|
+
def tag_end(name)
|
87
|
+
case name
|
88
|
+
when "EntityType"
|
89
|
+
@current_entity_type.schema = @schemas.last.namespace
|
90
|
+
@schemas.last.entity_types << @current_entity_type
|
91
|
+
when "ComplexType"
|
92
|
+
@current_complex_type.schema = @schemas.last.namespace
|
93
|
+
@schemas.last.complex_types << @current_complex_type
|
94
|
+
when "EnumType"
|
95
|
+
@enumerations << @current_enum_type
|
96
|
+
@current_enum_type = nil
|
97
|
+
when "Member"
|
98
|
+
@current_enum_type.members << @current_member
|
99
|
+
@current_member = nil
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
module ResoTransport
|
2
|
+
Property = Struct.new(:name, :data_type, :attrs, :multi, :enum, :complex_type, :entity_type) do
|
3
|
+
|
4
|
+
def self.from_stream(args)
|
5
|
+
new(args["Name"], args["Type"], args)
|
6
|
+
end
|
7
|
+
|
8
|
+
def parse(value)
|
9
|
+
case value
|
10
|
+
when Array
|
11
|
+
value.map {|v| parser_object.parse_value(v) }
|
12
|
+
else
|
13
|
+
if multi
|
14
|
+
value.split(',').map(&:strip).map {|v| parser_object.parse_value(v) }
|
15
|
+
else
|
16
|
+
parser_object.parse_value(value)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def parse_value(value)
|
22
|
+
case data_type
|
23
|
+
when "Edm.DateTimeOffset"
|
24
|
+
DateTime.parse(value)
|
25
|
+
when "Edm.Date"
|
26
|
+
Date.parse(value)
|
27
|
+
else
|
28
|
+
value
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def encode(value)
|
33
|
+
case value
|
34
|
+
when Array
|
35
|
+
value.map {|v| parser_object.encode_value(v) }
|
36
|
+
else
|
37
|
+
parser_object.encode_value(value)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def encode_value(value)
|
42
|
+
case data_type
|
43
|
+
when "Edm.String"
|
44
|
+
"'#{value}'"
|
45
|
+
when "Edm.DateTimeOffset"
|
46
|
+
if value.respond_to?(:to_datetime)
|
47
|
+
value.to_datetime.strftime(ODATA_TIME_FORMAT)
|
48
|
+
else
|
49
|
+
DateTime.parse(value).strftime(ODATA_TIME_FORMAT)
|
50
|
+
end
|
51
|
+
else
|
52
|
+
value
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def parser_object
|
57
|
+
enum || complex_type || entity_type || self
|
58
|
+
end
|
59
|
+
|
60
|
+
def finalize_type(parser)
|
61
|
+
type_name, is_collection = case self.data_type
|
62
|
+
when /^Collection\((.*)\)$/
|
63
|
+
[$1, true]
|
64
|
+
when /^Edm\.(.*)$/
|
65
|
+
[$1, false]
|
66
|
+
else
|
67
|
+
[self.data_type, false]
|
68
|
+
end
|
69
|
+
|
70
|
+
if enum = parser.enumerations.detect {|e| e.name == type_name }
|
71
|
+
self.multi = is_collection || enum.is_flags
|
72
|
+
self.enum = enum
|
73
|
+
end
|
74
|
+
|
75
|
+
schema_name, collection_name = ResoTransport.split_schema_and_class_name(type_name)
|
76
|
+
if schema = parser.schemas.detect {|e| e.namespace == schema_name }
|
77
|
+
if complex_type = schema.complex_types.detect {|c| c.name == collection_name }
|
78
|
+
self.multi = is_collection
|
79
|
+
self.complex_type = complex_type
|
80
|
+
end
|
81
|
+
|
82
|
+
if entity_type = schema.entity_types.detect {|et| et.name == collection_name }
|
83
|
+
self.multi = is_collection
|
84
|
+
self.entity_type = entity_type
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
|
90
|
+
def method_missing(name, *args, &block)
|
91
|
+
self.attrs[name] || self.attrs[camelize(name)] || super
|
92
|
+
end
|
93
|
+
|
94
|
+
private
|
95
|
+
|
96
|
+
def camelize(name)
|
97
|
+
name.to_s.split("_").map(&:capitalize).join
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,147 @@
|
|
1
|
+
module ResoTransport
|
2
|
+
Query = Struct.new(:resource) do
|
3
|
+
|
4
|
+
def all(&block)
|
5
|
+
new_query_context('and')
|
6
|
+
instance_eval(&block)
|
7
|
+
clear_query_context
|
8
|
+
return self
|
9
|
+
end
|
10
|
+
|
11
|
+
def any(&block)
|
12
|
+
new_query_context('or')
|
13
|
+
instance_eval(&block)
|
14
|
+
clear_query_context
|
15
|
+
return self
|
16
|
+
end
|
17
|
+
|
18
|
+
[:eq, :ne, :gt, :ge, :lt, :le].each do |op|
|
19
|
+
define_method(op) do |conditions|
|
20
|
+
conditions.each_pair do |k,v|
|
21
|
+
current_query_context << "#{k} #{op} #{encode_value(k, v)}"
|
22
|
+
end
|
23
|
+
return self
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def limit(size)
|
28
|
+
options[:top] = size
|
29
|
+
return self
|
30
|
+
end
|
31
|
+
|
32
|
+
def offset(size)
|
33
|
+
options[:skip] = size
|
34
|
+
return self
|
35
|
+
end
|
36
|
+
|
37
|
+
def order(field, dir=nil)
|
38
|
+
options[:orderby] = [field, dir].join(" ").strip
|
39
|
+
return self
|
40
|
+
end
|
41
|
+
|
42
|
+
def include_count
|
43
|
+
options[:count] = true
|
44
|
+
return self
|
45
|
+
end
|
46
|
+
|
47
|
+
def select(*fields)
|
48
|
+
os = options.fetch(:select, "").split(",")
|
49
|
+
options[:select] = (os + Array(fields)).uniq.join(",")
|
50
|
+
|
51
|
+
return self
|
52
|
+
end
|
53
|
+
|
54
|
+
def expand(*names)
|
55
|
+
ex = options.fetch(:expand, "").split(",")
|
56
|
+
options[:expand] = (ex + Array(names)).uniq.join(",")
|
57
|
+
|
58
|
+
return self
|
59
|
+
end
|
60
|
+
|
61
|
+
def results
|
62
|
+
resp = execute
|
63
|
+
|
64
|
+
if resp[:success]
|
65
|
+
resp[:results]
|
66
|
+
else
|
67
|
+
puts resp[:meta]
|
68
|
+
raise "Request Failed"
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def execute
|
73
|
+
resp = resource.get(compile_params)
|
74
|
+
parsed_body = JSON.parse(resp.body)
|
75
|
+
results = Array(parsed_body.delete("value"))
|
76
|
+
|
77
|
+
{
|
78
|
+
success: resp.success? && !parsed_body.has_key?("error"),
|
79
|
+
meta: parsed_body,
|
80
|
+
results: resource.parse(results)
|
81
|
+
}
|
82
|
+
end
|
83
|
+
|
84
|
+
def new_query_context(context)
|
85
|
+
@last_query_context ||= 0
|
86
|
+
@current_query_context = @last_query_context + 1
|
87
|
+
sub_queries[@current_query_context][:context] = context
|
88
|
+
end
|
89
|
+
|
90
|
+
def clear_query_context
|
91
|
+
@last_query_context = @current_query_context
|
92
|
+
@current_query_context = nil
|
93
|
+
end
|
94
|
+
|
95
|
+
def current_query_context
|
96
|
+
@current_query_context ||= nil
|
97
|
+
sub_queries[@current_query_context || :global][:criteria]
|
98
|
+
end
|
99
|
+
|
100
|
+
def options
|
101
|
+
@options ||= {}
|
102
|
+
end
|
103
|
+
|
104
|
+
def sub_queries
|
105
|
+
@sub_queries ||= Hash.new {|h,k| h[k] = { context: 'and', criteria: [] } }
|
106
|
+
end
|
107
|
+
|
108
|
+
def compile_filters
|
109
|
+
groups = sub_queries.dup
|
110
|
+
global = groups.delete(:global)
|
111
|
+
filter_groups = groups.values
|
112
|
+
|
113
|
+
filter_string = ""
|
114
|
+
|
115
|
+
if global && global[:criteria]&.any?
|
116
|
+
filter_string << global[:criteria].join(" #{global[:context]} ")
|
117
|
+
end
|
118
|
+
|
119
|
+
filter_string << filter_groups.map do |g|
|
120
|
+
"(#{g[:criteria].join(" #{g[:context]} ")})"
|
121
|
+
end.join(" and ")
|
122
|
+
|
123
|
+
filter_string
|
124
|
+
end
|
125
|
+
|
126
|
+
def compile_params
|
127
|
+
params = {}
|
128
|
+
|
129
|
+
options.each_pair do |k,v|
|
130
|
+
params["$#{k}"] = v
|
131
|
+
end
|
132
|
+
|
133
|
+
if !sub_queries.empty?
|
134
|
+
params["$filter"] = compile_filters
|
135
|
+
end
|
136
|
+
|
137
|
+
params
|
138
|
+
end
|
139
|
+
|
140
|
+
def encode_value(key, v)
|
141
|
+
field = resource.property(key.to_s)
|
142
|
+
raise "Couldn't find property #{key} for #{resource.name}" if field.nil?
|
143
|
+
field.encode(v)
|
144
|
+
end
|
145
|
+
|
146
|
+
end
|
147
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module ResoTransport
|
2
|
+
Resource = Struct.new(:client, :entity_set) do
|
3
|
+
|
4
|
+
def query
|
5
|
+
Query.new(self)
|
6
|
+
end
|
7
|
+
|
8
|
+
def name
|
9
|
+
entity_set.name
|
10
|
+
end
|
11
|
+
|
12
|
+
def property(name)
|
13
|
+
properties.detect {|p| p.name == name }
|
14
|
+
end
|
15
|
+
|
16
|
+
def properties
|
17
|
+
entity_type.properties
|
18
|
+
end
|
19
|
+
|
20
|
+
def expandable
|
21
|
+
entity_type.navigation_properties
|
22
|
+
end
|
23
|
+
|
24
|
+
def entity_type
|
25
|
+
@entity_type ||= schema.entity_types.detect {|et| et.name == entity_set.entity_type }
|
26
|
+
end
|
27
|
+
|
28
|
+
def schema
|
29
|
+
@schema ||= md.schemas.detect {|s| s.namespace == entity_set.schema }
|
30
|
+
end
|
31
|
+
|
32
|
+
def md
|
33
|
+
client.metadata
|
34
|
+
end
|
35
|
+
|
36
|
+
def parse(results)
|
37
|
+
results.map {|r| entity_type.parse(r) }
|
38
|
+
end
|
39
|
+
|
40
|
+
def get(params)
|
41
|
+
client.connection.get(name, params) do |req|
|
42
|
+
req.headers['Accept'] = 'application/json'
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def to_s
|
47
|
+
%(#<ResoTransport::Resource entity_set="#{name}", schema="#{schema&.namespace}">)
|
48
|
+
end
|
49
|
+
|
50
|
+
def inspect
|
51
|
+
to_s
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module ResoTransport
|
2
|
+
Schema = Struct.new(:namespace) do
|
3
|
+
|
4
|
+
def self.from_stream(args)
|
5
|
+
new(args["Namespace"])
|
6
|
+
end
|
7
|
+
|
8
|
+
def entity_types
|
9
|
+
@entity_types ||= []
|
10
|
+
end
|
11
|
+
|
12
|
+
def complex_types
|
13
|
+
@complex_types ||= []
|
14
|
+
end
|
15
|
+
|
16
|
+
def enumerations
|
17
|
+
@enumerations ||= []
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'rexml/document'
|
2
|
+
require 'rexml/streamlistener'
|
3
|
+
require 'logger'
|
4
|
+
require 'faraday'
|
5
|
+
require 'json'
|
6
|
+
require 'time'
|
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_parser"
|
15
|
+
require "reso_transport/schema"
|
16
|
+
require "reso_transport/entity_set"
|
17
|
+
require "reso_transport/entity_type"
|
18
|
+
require "reso_transport/enum"
|
19
|
+
require "reso_transport/property"
|
20
|
+
require "reso_transport/query"
|
21
|
+
|
22
|
+
|
23
|
+
|
24
|
+
module Faraday
|
25
|
+
module Utils
|
26
|
+
|
27
|
+
def escape(str)
|
28
|
+
str.to_s.gsub(ESCAPE_RE) do |match|
|
29
|
+
'%' + match.unpack('H2' * match.bytesize).join('%').upcase
|
30
|
+
end.gsub(" ","%20")
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
module ResoTransport
|
37
|
+
class Error < StandardError; end
|
38
|
+
class AccessDenied < StandardError; end
|
39
|
+
ODATA_TIME_FORMAT = "%Y-%m-%dT%H:%M:%S%Z"
|
40
|
+
|
41
|
+
class << self
|
42
|
+
attr_writer :configuration
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.configuration
|
46
|
+
@configuration ||= Configuration.new
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.configure
|
50
|
+
yield(configuration)
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.split_schema_and_class_name(s)
|
54
|
+
s.partition(/(\w+)$/).first(2).map {|s| s.sub(/\.$/, '') }
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
|
2
|
+
lib = File.expand_path("../lib", __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require "reso_transport/version"
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "reso_transport"
|
8
|
+
spec.version = ResoTransport::VERSION
|
9
|
+
spec.authors = ["Jon Druse"]
|
10
|
+
spec.email = ["jon@wrstudios.com"]
|
11
|
+
|
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"
|
16
|
+
|
17
|
+
|
18
|
+
# Specify which files should be added to the gem when it is released.
|
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
|
21
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
22
|
+
end
|
23
|
+
spec.bindir = "exe"
|
24
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
25
|
+
spec.require_paths = ["lib"]
|
26
|
+
|
27
|
+
spec.add_dependency "faraday", "~> 0.17.0"
|
28
|
+
|
29
|
+
spec.add_development_dependency "bundler", "~> 1.17"
|
30
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
31
|
+
spec.add_development_dependency "minitest", "~> 5.0"
|
32
|
+
spec.add_development_dependency "minitest-rg", "~> 5.0"
|
33
|
+
spec.add_development_dependency "vcr", "~> 5.0"
|
34
|
+
spec.add_development_dependency "byebug"
|
35
|
+
end
|
data/sample_secrets.yml
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
:bridge:
|
2
|
+
:md_file: PATH_TO_METADATA_FILE
|
3
|
+
:vendor:
|
4
|
+
:name: bridge_interactive
|
5
|
+
:endpoint: ENDPOINT_URL_HERE
|
6
|
+
:authentication:
|
7
|
+
:access_token: ACCESS_TOKEN_HERE
|
8
|
+
|
9
|
+
:trestle:
|
10
|
+
:md_file: PATH_TO_METADATA_FILE
|
11
|
+
:vendor:
|
12
|
+
:name: trestle
|
13
|
+
:endpoint: ENDPOINT_URL_HERE
|
14
|
+
:authentication:
|
15
|
+
:endpoint: AUTH_ENDPOINT_HERE
|
16
|
+
:client_id: CLIENT_ID_HERE
|
17
|
+
:client_secret: CLIENT_SECRET_HERE
|
18
|
+
|
19
|
+
:spark:
|
20
|
+
:md_file: PATH_TO_METADATA_FILE
|
21
|
+
:vendor:
|
22
|
+
:name: fbs
|
23
|
+
:endpoint: ENDPOINT_URL_HERE
|
24
|
+
:authentication:
|
25
|
+
:access_token: ACCESS_TOKEN_HERE
|
26
|
+
|