reso_transport 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|