frodata 0.9.1
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/.autotest +2 -0
- data/.gitignore +24 -0
- data/.rspec +2 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +75 -0
- data/CHANGELOG.md +150 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +23 -0
- data/README.md +427 -0
- data/Rakefile +7 -0
- data/TODO.md +55 -0
- data/frodata.gemspec +34 -0
- data/lib/frodata.rb +36 -0
- data/lib/frodata/entity.rb +332 -0
- data/lib/frodata/entity_container.rb +75 -0
- data/lib/frodata/entity_set.rb +161 -0
- data/lib/frodata/errors.rb +68 -0
- data/lib/frodata/navigation_property.rb +29 -0
- data/lib/frodata/navigation_property/proxy.rb +80 -0
- data/lib/frodata/properties.rb +32 -0
- data/lib/frodata/properties/binary.rb +50 -0
- data/lib/frodata/properties/boolean.rb +37 -0
- data/lib/frodata/properties/collection.rb +50 -0
- data/lib/frodata/properties/complex.rb +114 -0
- data/lib/frodata/properties/date.rb +27 -0
- data/lib/frodata/properties/date_time.rb +83 -0
- data/lib/frodata/properties/date_time_offset.rb +17 -0
- data/lib/frodata/properties/decimal.rb +50 -0
- data/lib/frodata/properties/enum.rb +62 -0
- data/lib/frodata/properties/float.rb +67 -0
- data/lib/frodata/properties/geography.rb +13 -0
- data/lib/frodata/properties/geography/base.rb +162 -0
- data/lib/frodata/properties/geography/line_string.rb +33 -0
- data/lib/frodata/properties/geography/point.rb +31 -0
- data/lib/frodata/properties/geography/polygon.rb +38 -0
- data/lib/frodata/properties/guid.rb +17 -0
- data/lib/frodata/properties/integer.rb +107 -0
- data/lib/frodata/properties/number.rb +14 -0
- data/lib/frodata/properties/string.rb +72 -0
- data/lib/frodata/properties/time.rb +40 -0
- data/lib/frodata/properties/time_of_day.rb +27 -0
- data/lib/frodata/property.rb +139 -0
- data/lib/frodata/property_registry.rb +41 -0
- data/lib/frodata/query.rb +233 -0
- data/lib/frodata/query/criteria.rb +92 -0
- data/lib/frodata/query/criteria/comparison_operators.rb +49 -0
- data/lib/frodata/query/criteria/date_functions.rb +61 -0
- data/lib/frodata/query/criteria/geography_functions.rb +21 -0
- data/lib/frodata/query/criteria/lambda_operators.rb +27 -0
- data/lib/frodata/query/criteria/string_functions.rb +40 -0
- data/lib/frodata/query/in_batches.rb +58 -0
- data/lib/frodata/railtie.rb +19 -0
- data/lib/frodata/schema.rb +155 -0
- data/lib/frodata/schema/complex_type.rb +79 -0
- data/lib/frodata/schema/enum_type.rb +95 -0
- data/lib/frodata/service.rb +254 -0
- data/lib/frodata/service/request.rb +85 -0
- data/lib/frodata/service/response.rb +162 -0
- data/lib/frodata/service/response/atom.rb +40 -0
- data/lib/frodata/service/response/json.rb +41 -0
- data/lib/frodata/service/response/plain.rb +36 -0
- data/lib/frodata/service/response/xml.rb +40 -0
- data/lib/frodata/service_registry.rb +52 -0
- data/lib/frodata/version.rb +3 -0
- data/spec/fixtures/files/entity_to_xml.xml +17 -0
- data/spec/fixtures/files/error.xml +5 -0
- data/spec/fixtures/files/metadata.xml +150 -0
- data/spec/fixtures/files/product_0.json +10 -0
- data/spec/fixtures/files/product_0.xml +28 -0
- data/spec/fixtures/files/products.json +106 -0
- data/spec/fixtures/files/products.xml +308 -0
- data/spec/fixtures/files/supplier_0.json +26 -0
- data/spec/fixtures/files/supplier_0.xml +32 -0
- data/spec/fixtures/vcr_cassettes/entity_set_specs.yml +1635 -0
- data/spec/fixtures/vcr_cassettes/entity_set_specs/bad_entry.yml +183 -0
- data/spec/fixtures/vcr_cassettes/entity_set_specs/existing_entry.yml +256 -0
- data/spec/fixtures/vcr_cassettes/entity_set_specs/new_entry.yml +185 -0
- data/spec/fixtures/vcr_cassettes/entity_specs.yml +285 -0
- data/spec/fixtures/vcr_cassettes/navigation_property_proxy_specs.yml +346 -0
- data/spec/fixtures/vcr_cassettes/query/result_specs.yml +189 -0
- data/spec/fixtures/vcr_cassettes/query_specs.yml +1060 -0
- data/spec/fixtures/vcr_cassettes/schema/complex_type_specs.yml +127 -0
- data/spec/fixtures/vcr_cassettes/service/request_specs.yml +193 -0
- data/spec/fixtures/vcr_cassettes/service_registry_specs.yml +129 -0
- data/spec/fixtures/vcr_cassettes/service_specs.yml +127 -0
- data/spec/fixtures/vcr_cassettes/usage_example_specs.yml +1330 -0
- data/spec/frodata/entity/shared_examples.rb +82 -0
- data/spec/frodata/entity_container_spec.rb +38 -0
- data/spec/frodata/entity_set_spec.rb +168 -0
- data/spec/frodata/entity_spec.rb +151 -0
- data/spec/frodata/errors_spec.rb +48 -0
- data/spec/frodata/navigation_property/proxy_spec.rb +44 -0
- data/spec/frodata/navigation_property_spec.rb +55 -0
- data/spec/frodata/properties/binary_spec.rb +50 -0
- data/spec/frodata/properties/boolean_spec.rb +72 -0
- data/spec/frodata/properties/collection_spec.rb +44 -0
- data/spec/frodata/properties/date_spec.rb +23 -0
- data/spec/frodata/properties/date_time_offset_spec.rb +30 -0
- data/spec/frodata/properties/date_time_spec.rb +23 -0
- data/spec/frodata/properties/decimal_spec.rb +51 -0
- data/spec/frodata/properties/float_spec.rb +45 -0
- data/spec/frodata/properties/geography/line_string_spec.rb +33 -0
- data/spec/frodata/properties/geography/point_spec.rb +29 -0
- data/spec/frodata/properties/geography/polygon_spec.rb +55 -0
- data/spec/frodata/properties/geography/shared_examples.rb +72 -0
- data/spec/frodata/properties/guid_spec.rb +17 -0
- data/spec/frodata/properties/integer_spec.rb +58 -0
- data/spec/frodata/properties/string_spec.rb +46 -0
- data/spec/frodata/properties/time_of_day_spec.rb +23 -0
- data/spec/frodata/properties/time_spec.rb +15 -0
- data/spec/frodata/property_registry_spec.rb +16 -0
- data/spec/frodata/property_spec.rb +71 -0
- data/spec/frodata/query/criteria_spec.rb +229 -0
- data/spec/frodata/query_spec.rb +199 -0
- data/spec/frodata/schema/complex_type_spec.rb +96 -0
- data/spec/frodata/schema/enum_type_spec.rb +112 -0
- data/spec/frodata/schema_spec.rb +97 -0
- data/spec/frodata/service/request_spec.rb +49 -0
- data/spec/frodata/service/response_spec.rb +85 -0
- data/spec/frodata/service_registry_spec.rb +18 -0
- data/spec/frodata/service_spec.rb +191 -0
- data/spec/frodata/usage_example_spec.rb +188 -0
- data/spec/spec_helper.rb +32 -0
- data/spec/support/coverage.rb +2 -0
- data/spec/support/vcr.rb +9 -0
- metadata +401 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
module FrOData
|
|
2
|
+
class Schema
|
|
3
|
+
# ComplexTypes are used in FrOData to either encapsulate richer data types for
|
|
4
|
+
# use as Entity properties. ComplexTypes are composed of properties the same
|
|
5
|
+
# way that Entities are and, so, the interface for working with the various
|
|
6
|
+
# properties of a ComplexType mimics that of Entities.
|
|
7
|
+
class ComplexType
|
|
8
|
+
# Creates a new ComplexType based on the supplied options.
|
|
9
|
+
# @param type_xml [Nokogiri::XML::Element]
|
|
10
|
+
# @param service [FrOData::Service]
|
|
11
|
+
def initialize(type_definition, schema)
|
|
12
|
+
@type_definition = type_definition
|
|
13
|
+
@schema = schema
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# The name of the ComplexType
|
|
17
|
+
# @return [String]
|
|
18
|
+
def name
|
|
19
|
+
@name ||= type_definition.attributes['Name'].value
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Returns the namespaced type for the ComplexType.
|
|
23
|
+
# @return [String]
|
|
24
|
+
def type
|
|
25
|
+
"#{namespace}.#{name}"
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Returns the namespace this ComplexType belongs to.
|
|
29
|
+
# @return [String]
|
|
30
|
+
def namespace
|
|
31
|
+
@namespace ||= service.namespace
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Returns this ComplexType's properties.
|
|
35
|
+
# @return [Hash<String, FrOData::Property>]
|
|
36
|
+
def properties
|
|
37
|
+
@properties ||= collect_properties
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Returns a list of this ComplexType's property names.
|
|
41
|
+
# @return [Array<String>]
|
|
42
|
+
def property_names
|
|
43
|
+
@property_names ||= properties.keys
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Returns the property class that implements this `ComplexType`.
|
|
47
|
+
# @return [Class < FrOData::Properties::Complex]
|
|
48
|
+
def property_class
|
|
49
|
+
@property_class ||= lambda { |type, complex_type|
|
|
50
|
+
klass = Class.new ::FrOData::Properties::Complex
|
|
51
|
+
klass.send(:define_method, :type) { type }
|
|
52
|
+
klass.send(:define_method, :complex_type) { complex_type }
|
|
53
|
+
klass
|
|
54
|
+
}.call(type, self)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
private
|
|
58
|
+
|
|
59
|
+
def schema
|
|
60
|
+
@schema
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def service
|
|
64
|
+
@schema.service
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def type_definition
|
|
68
|
+
@type_definition
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def collect_properties
|
|
72
|
+
Hash[type_definition.xpath('./Property').map do |property_xml|
|
|
73
|
+
property_name, property = schema.send(:process_property_from_xml, property_xml)
|
|
74
|
+
[property_name, property]
|
|
75
|
+
end]
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
module FrOData
|
|
2
|
+
class Schema
|
|
3
|
+
# Enumeration types are nominal types that represent a series of related values.
|
|
4
|
+
# Enumeration types expose these related values as members of the enumeration.
|
|
5
|
+
class EnumType
|
|
6
|
+
# Creates a new EnumType based on the supplied options.
|
|
7
|
+
# @param type_xml [Nokogiri::XML::Element]
|
|
8
|
+
# @param service [FrOData::Service]
|
|
9
|
+
# @return [self]
|
|
10
|
+
def initialize(type_definition, schema)
|
|
11
|
+
@type_definition = type_definition
|
|
12
|
+
@schema = schema
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# The name of the EnumType
|
|
16
|
+
# @return [String]
|
|
17
|
+
def name
|
|
18
|
+
options['Name']
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Returns the namespaced type for the EnumType.
|
|
22
|
+
# @return [String]
|
|
23
|
+
def type
|
|
24
|
+
"#{namespace}.#{name}"
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Whether this EnumType supports setting multiple values.
|
|
28
|
+
# @return [Boolean]
|
|
29
|
+
def is_flags?
|
|
30
|
+
options['IsFlags'] == 'true'
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# The underlying type of this EnumType.
|
|
34
|
+
# @return [String]
|
|
35
|
+
def underlying_type
|
|
36
|
+
options['UnderlyingType'] || 'Edm.Int32'
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Returns the namespace this EnumType belongs to.
|
|
40
|
+
# @return [String]
|
|
41
|
+
def namespace
|
|
42
|
+
@namespace ||= service.namespace
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Returns the members of this EnumType and their values.
|
|
46
|
+
# @return [Hash]
|
|
47
|
+
def members
|
|
48
|
+
@members ||= collect_members
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Returns the property class that implements this `EnumType`.
|
|
52
|
+
# @return [Class < FrOData::Properties::Enum]
|
|
53
|
+
def property_class
|
|
54
|
+
@property_class ||= lambda { |type, members, is_flags|
|
|
55
|
+
klass = Class.new ::FrOData::Properties::Enum
|
|
56
|
+
klass.send(:define_method, :type) { type }
|
|
57
|
+
klass.send(:define_method, :members) { members }
|
|
58
|
+
klass.send(:define_method, :is_flags?) { is_flags }
|
|
59
|
+
klass
|
|
60
|
+
}.call(type, members, is_flags?)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Returns the value of the requested member.
|
|
64
|
+
# @param member_name [to_s]
|
|
65
|
+
# @return [*]
|
|
66
|
+
def [](member_name)
|
|
67
|
+
members.invert[member_name.to_s]
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
private
|
|
71
|
+
|
|
72
|
+
def service
|
|
73
|
+
@schema.service
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def type_definition
|
|
77
|
+
@type_definition
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def options
|
|
81
|
+
@options = type_definition.attributes.map do |name, attr|
|
|
82
|
+
[name, attr.value]
|
|
83
|
+
end.to_h
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def collect_members
|
|
87
|
+
Hash[type_definition.xpath('./Member').map.with_index do |member_xml, index|
|
|
88
|
+
member_name = member_xml.attributes['Name'].value
|
|
89
|
+
member_value = member_xml.attributes['Value'].andand.value.andand.to_i
|
|
90
|
+
[member_value || index, member_name]
|
|
91
|
+
end]
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
require 'frodata/service/request'
|
|
2
|
+
require 'frodata/service/response'
|
|
3
|
+
|
|
4
|
+
module FrOData
|
|
5
|
+
# Encapsulates the basic details and functionality needed to interact with an
|
|
6
|
+
# FrOData service.
|
|
7
|
+
class Service
|
|
8
|
+
# The Faraday connection object used by the service to make requests
|
|
9
|
+
attr_reader :connection
|
|
10
|
+
# The FrOData Service's URL
|
|
11
|
+
attr_reader :service_url
|
|
12
|
+
# Service options
|
|
13
|
+
attr_reader :options
|
|
14
|
+
|
|
15
|
+
DEFAULT_TIMEOUT = 20
|
|
16
|
+
|
|
17
|
+
METADATA_TIMEOUTS = [20, 60]
|
|
18
|
+
|
|
19
|
+
MIME_TYPES = {
|
|
20
|
+
atom: 'application/atom+xml',
|
|
21
|
+
json: 'application/json',
|
|
22
|
+
xml: 'application/xml',
|
|
23
|
+
plain: 'text/plain'
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
# Opens the service based on the requested URL and adds the service to
|
|
27
|
+
# {FrOData::Registry}
|
|
28
|
+
#
|
|
29
|
+
# @param service_url [String|Faraday::Connection]
|
|
30
|
+
# The URL to the desired FrOData service, or a Faraday connection object
|
|
31
|
+
# @param options [Hash] options to pass to the service
|
|
32
|
+
# @return [FrOData::Service] an instance of the service
|
|
33
|
+
def initialize(service_url, options = {}, &block)
|
|
34
|
+
if service_url.is_a? Faraday::Connection
|
|
35
|
+
@connection = service_url
|
|
36
|
+
@service_url = connection.url_prefix
|
|
37
|
+
else
|
|
38
|
+
@service_url = service_url
|
|
39
|
+
@connection = Faraday.new(service_url, options[:connection], &block)
|
|
40
|
+
end
|
|
41
|
+
@options = default_options.merge(options)
|
|
42
|
+
FrOData::ServiceRegistry.add(self)
|
|
43
|
+
register_custom_types
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Opens the service based on the requested URL and adds the service to
|
|
47
|
+
# {FrOData::Registry}
|
|
48
|
+
# @deprecated Use {Service.new} instead.
|
|
49
|
+
#
|
|
50
|
+
# @param service_url [String] the URL to the desired FrOData service
|
|
51
|
+
# @param options [Hash] options to pass to the service
|
|
52
|
+
# @return [FrOData::Service] an instance of the service
|
|
53
|
+
def self.open(service_url, options = {}, &block)
|
|
54
|
+
Service.new(service_url, options, &block)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Returns user supplied name for service, or its URL
|
|
58
|
+
# @return [String]
|
|
59
|
+
def name
|
|
60
|
+
@name ||= options[:name] || service_url
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Returns the service's metadata URL.
|
|
64
|
+
# @return [String]
|
|
65
|
+
def metadata_url
|
|
66
|
+
"#{service_url}/$metadata"
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Returns the service's metadata definition.
|
|
70
|
+
# @return [Nokogiri::XML]
|
|
71
|
+
def metadata
|
|
72
|
+
@metadata ||= lambda { read_metadata }.call
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Returns all of the service's schemas.
|
|
76
|
+
# @return Hash<String, FrOData::Schema>
|
|
77
|
+
def schemas
|
|
78
|
+
@schemas ||= metadata.xpath('//Schema').map do |schema_xml|
|
|
79
|
+
[
|
|
80
|
+
schema_xml.attributes['Namespace'].value,
|
|
81
|
+
Schema.new(schema_xml, self)
|
|
82
|
+
]
|
|
83
|
+
end.to_h
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Returns the service's EntityContainer (singleton)
|
|
87
|
+
# @return FrOData::EntityContainer
|
|
88
|
+
def entity_container
|
|
89
|
+
@entity_container ||= EntityContainer.new(self)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Returns a hash of EntitySet names and their respective EntityType names
|
|
93
|
+
# @return Hash<String, String>
|
|
94
|
+
def entity_sets
|
|
95
|
+
entity_container.entity_sets
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Retrieves the EntitySet associated with a specific EntityType by name
|
|
99
|
+
#
|
|
100
|
+
# @param entity_set_name [to_s] the name of the EntitySet desired
|
|
101
|
+
# @return [FrOData::EntitySet] an FrOData::EntitySet to query
|
|
102
|
+
def [](entity_set_name)
|
|
103
|
+
entity_container[entity_set_name]
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Returns the default namespace, that is, the namespace of the schema
|
|
107
|
+
# that contains the service's EntityContainer.
|
|
108
|
+
# @return [String]
|
|
109
|
+
def namespace
|
|
110
|
+
entity_container.namespace
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Returns a list of `EntityType`s exposed by the service
|
|
114
|
+
# @return Array<String>
|
|
115
|
+
def entity_types
|
|
116
|
+
@entity_types ||= schemas.map do |namespace, schema|
|
|
117
|
+
schema.entity_types.map do |entity_type|
|
|
118
|
+
"#{namespace}.#{entity_type}"
|
|
119
|
+
end
|
|
120
|
+
end.flatten
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Returns a list of `ComplexType`s used by the service.
|
|
124
|
+
# @return [Hash<String, FrOData::Schema::ComplexType>]
|
|
125
|
+
def complex_types
|
|
126
|
+
@complex_types ||= schemas.map do |namespace, schema|
|
|
127
|
+
schema.complex_types.map do |name, complex_type|
|
|
128
|
+
[ "#{namespace}.#{name}", complex_type ]
|
|
129
|
+
end.to_h
|
|
130
|
+
end.reduce({}, :merge)
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# Returns a list of `EnumType`s used by the service
|
|
134
|
+
# @return [Hash<String, FrOData::Schema::EnumType>]
|
|
135
|
+
def enum_types
|
|
136
|
+
@enum_types ||= schemas.map do |namespace, schema|
|
|
137
|
+
schema.enum_types.map do |name, enum_type|
|
|
138
|
+
[ "#{namespace}.#{name}", enum_type ]
|
|
139
|
+
end.to_h
|
|
140
|
+
end.reduce({}, :merge)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# Returns a more compact inspection of the service object
|
|
144
|
+
def inspect
|
|
145
|
+
"#<#{self.class.name}:#{self.object_id} name='#{name}' service_url='#{self.service_url}'>"
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# Execute a request against the service
|
|
149
|
+
#
|
|
150
|
+
# @param url_chunk [to_s] string to append to service URL
|
|
151
|
+
# @param options [Hash] additional request options
|
|
152
|
+
# @return [FrOData::Service::Response]
|
|
153
|
+
def execute(url_chunk, options = {})
|
|
154
|
+
Request.new(self, url_chunk, options).execute
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# Get the property type for an entity from metadata.
|
|
158
|
+
#
|
|
159
|
+
# @param entity_name [to_s] the fully qualified entity name
|
|
160
|
+
# @param property_name [to_s] the property name needed
|
|
161
|
+
# @return [String] the name of the property's type
|
|
162
|
+
def get_property_type(entity_name, property_name)
|
|
163
|
+
namespace, _, entity_name = entity_name.rpartition('.')
|
|
164
|
+
raise ArgumentError, 'Namespace missing' if namespace.nil? || namespace.empty?
|
|
165
|
+
schemas[namespace].get_property_type(entity_name, property_name)
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# Get the primary key for the supplied Entity.
|
|
169
|
+
#
|
|
170
|
+
# @param entity_name [to_s] The fully qualified entity name
|
|
171
|
+
# @return [String]
|
|
172
|
+
def primary_key_for(entity_name)
|
|
173
|
+
namespace, _, entity_name = entity_name.rpartition('.')
|
|
174
|
+
raise ArgumentError, 'Namespace missing' if namespace.nil? || namespace.empty?
|
|
175
|
+
schemas[namespace].primary_key_for(entity_name)
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# Get the list of properties and their various options for the supplied
|
|
179
|
+
# Entity name.
|
|
180
|
+
# @param entity_name [to_s]
|
|
181
|
+
# @return [Hash]
|
|
182
|
+
# @api private
|
|
183
|
+
def properties_for_entity(entity_name)
|
|
184
|
+
namespace, _, entity_name = entity_name.rpartition('.')
|
|
185
|
+
raise ArgumentError, 'Namespace missing' if namespace.nil? || namespace.empty?
|
|
186
|
+
schemas[namespace].properties_for_entity(entity_name)
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
# Returns the logger instance used by the service.
|
|
190
|
+
# When Ruby on Rails has been detected, the service will
|
|
191
|
+
# use `Rails.logger`. The log level will NOT be changed.
|
|
192
|
+
#
|
|
193
|
+
# When no Rails has been detected, a default logger will
|
|
194
|
+
# be used that logs to STDOUT with the log level supplied
|
|
195
|
+
# via options, or the default log level if none was given.
|
|
196
|
+
# @return [Logger]
|
|
197
|
+
def logger
|
|
198
|
+
@logger ||= options[:logger] || if defined?(Rails)
|
|
199
|
+
Rails.logger
|
|
200
|
+
else
|
|
201
|
+
default_logger
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
# Allows the logger to be set to a custom `Logger` instance.
|
|
206
|
+
# @param custom_logger [Logger]
|
|
207
|
+
def logger=(custom_logger)
|
|
208
|
+
@logger = custom_logger
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
private
|
|
212
|
+
|
|
213
|
+
def default_options
|
|
214
|
+
{
|
|
215
|
+
request: {
|
|
216
|
+
timeout: DEFAULT_TIMEOUT
|
|
217
|
+
},
|
|
218
|
+
strict: true # strict property validation
|
|
219
|
+
}
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
def default_logger
|
|
223
|
+
Logger.new(STDOUT).tap do |logger|
|
|
224
|
+
logger.level = options[:log_level] || Logger::WARN
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
def read_metadata
|
|
229
|
+
# From file, good for debugging
|
|
230
|
+
if options[:metadata_file]
|
|
231
|
+
data = File.read(options[:metadata_file])
|
|
232
|
+
::Nokogiri::XML(data).remove_namespaces!
|
|
233
|
+
else # From a URL
|
|
234
|
+
response = nil
|
|
235
|
+
METADATA_TIMEOUTS.each do |timeout|
|
|
236
|
+
response = execute(metadata_url, timeout: timeout)
|
|
237
|
+
break unless response.timed_out?
|
|
238
|
+
end
|
|
239
|
+
raise "Metadata Timeout" if response.timed_out?
|
|
240
|
+
::Nokogiri::XML(response.body).remove_namespaces!
|
|
241
|
+
end
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
def register_custom_types
|
|
245
|
+
complex_types.each do |name, type|
|
|
246
|
+
::FrOData::PropertyRegistry.add(name, type.property_class)
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
enum_types.each do |name, type|
|
|
250
|
+
::FrOData::PropertyRegistry.add(name, type.property_class)
|
|
251
|
+
end
|
|
252
|
+
end
|
|
253
|
+
end
|
|
254
|
+
end
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
module FrOData
|
|
2
|
+
class Service
|
|
3
|
+
# Encapsulates a single request to an OData service.
|
|
4
|
+
class Request
|
|
5
|
+
# The OData service against which the request is performed
|
|
6
|
+
attr_reader :service
|
|
7
|
+
# The FrOData::Query that generated this request (optional)
|
|
8
|
+
attr_reader :query
|
|
9
|
+
# The HTTP method for this request
|
|
10
|
+
attr_accessor :method
|
|
11
|
+
# The request format (`:atom`, `:json`, or `:auto`)
|
|
12
|
+
attr_accessor :format
|
|
13
|
+
|
|
14
|
+
# Create a new request
|
|
15
|
+
# @param service [FrOData::Service] Where the request will be sent
|
|
16
|
+
# @param url_chunk [String] Request path, relative to the service URL, including query params
|
|
17
|
+
# @param options [Hash] Additional request options
|
|
18
|
+
def initialize(service, url_chunk, options = {})
|
|
19
|
+
@service = service
|
|
20
|
+
@url_chunk = url_chunk
|
|
21
|
+
@method = options.delete(:method) || :get
|
|
22
|
+
@format = options.delete(:format) || :auto
|
|
23
|
+
@query = options.delete(:query)
|
|
24
|
+
@options = options
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Return the full request URL (including service base)
|
|
28
|
+
# @return [String]
|
|
29
|
+
def url
|
|
30
|
+
::URI.join("#{service.service_url}/", ::URI.escape(url_chunk)).to_s
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# The content type for this request. Depends on format.
|
|
34
|
+
# @return [String]
|
|
35
|
+
def content_type
|
|
36
|
+
if format == :auto
|
|
37
|
+
MIME_TYPES.values.join(',')
|
|
38
|
+
elsif MIME_TYPES.has_key? format
|
|
39
|
+
MIME_TYPES[format]
|
|
40
|
+
else
|
|
41
|
+
raise ArgumentError, "Unknown format '#{format}'"
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Execute the request
|
|
46
|
+
#
|
|
47
|
+
# @param additional_options [Hash] Request options to pass to Faraday
|
|
48
|
+
# @return [FrOData::Service::Response]
|
|
49
|
+
def execute(additional_options = {})
|
|
50
|
+
request_options = service.options[:request].merge(additional_options)
|
|
51
|
+
|
|
52
|
+
logger.info "Requesting #{method.to_s.upcase} #{url}..."
|
|
53
|
+
Response.new(service, query) do
|
|
54
|
+
connection.run_request(method, url_chunk, nil, headers) do |conn|
|
|
55
|
+
conn.options.merge! request_options
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
private
|
|
61
|
+
|
|
62
|
+
attr_reader :url_chunk
|
|
63
|
+
|
|
64
|
+
def connection
|
|
65
|
+
service.connection
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def default_headers
|
|
69
|
+
{
|
|
70
|
+
'Accept' => content_type,
|
|
71
|
+
'Content-Type' => content_type,
|
|
72
|
+
'OData-Version' => '4.0'
|
|
73
|
+
}
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def headers
|
|
77
|
+
default_headers.merge(@options[:headers] || {})
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def logger
|
|
81
|
+
service.logger
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|