frodo 0.10.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/.autotest +2 -0
- data/.circleci/config.yml +54 -0
- data/.gitignore +24 -0
- data/.gitlab-ci.yml +9 -0
- data/.rspec +2 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +75 -0
- data/CHANGELOG.md +163 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +23 -0
- data/README.md +479 -0
- data/Rakefile +7 -0
- data/TODO.md +55 -0
- data/frodo.gemspec +39 -0
- data/images/frodo.jpg +0 -0
- data/lib/frodo/abstract_client.rb +11 -0
- data/lib/frodo/client.rb +6 -0
- data/lib/frodo/concerns/api.rb +292 -0
- data/lib/frodo/concerns/authentication.rb +32 -0
- data/lib/frodo/concerns/base.rb +84 -0
- data/lib/frodo/concerns/caching.rb +26 -0
- data/lib/frodo/concerns/connection.rb +79 -0
- data/lib/frodo/concerns/verbs.rb +68 -0
- data/lib/frodo/config.rb +143 -0
- data/lib/frodo/entity.rb +335 -0
- data/lib/frodo/entity_container.rb +75 -0
- data/lib/frodo/entity_set.rb +131 -0
- data/lib/frodo/errors.rb +70 -0
- data/lib/frodo/middleware/authentication/token.rb +13 -0
- data/lib/frodo/middleware/authentication.rb +87 -0
- data/lib/frodo/middleware/authorization.rb +18 -0
- data/lib/frodo/middleware/caching.rb +30 -0
- data/lib/frodo/middleware/custom_headers.rb +14 -0
- data/lib/frodo/middleware/gzip.rb +33 -0
- data/lib/frodo/middleware/instance_url.rb +20 -0
- data/lib/frodo/middleware/logger.rb +42 -0
- data/lib/frodo/middleware/multipart.rb +64 -0
- data/lib/frodo/middleware/odata_headers.rb +13 -0
- data/lib/frodo/middleware/raise_error.rb +47 -0
- data/lib/frodo/middleware.rb +33 -0
- data/lib/frodo/navigation_property/proxy.rb +80 -0
- data/lib/frodo/navigation_property.rb +29 -0
- data/lib/frodo/properties/binary.rb +50 -0
- data/lib/frodo/properties/boolean.rb +37 -0
- data/lib/frodo/properties/collection.rb +50 -0
- data/lib/frodo/properties/complex.rb +114 -0
- data/lib/frodo/properties/date.rb +27 -0
- data/lib/frodo/properties/date_time.rb +83 -0
- data/lib/frodo/properties/date_time_offset.rb +17 -0
- data/lib/frodo/properties/decimal.rb +54 -0
- data/lib/frodo/properties/enum.rb +62 -0
- data/lib/frodo/properties/float.rb +67 -0
- data/lib/frodo/properties/geography/base.rb +162 -0
- data/lib/frodo/properties/geography/line_string.rb +33 -0
- data/lib/frodo/properties/geography/point.rb +31 -0
- data/lib/frodo/properties/geography/polygon.rb +38 -0
- data/lib/frodo/properties/geography.rb +13 -0
- data/lib/frodo/properties/guid.rb +17 -0
- data/lib/frodo/properties/integer.rb +107 -0
- data/lib/frodo/properties/number.rb +14 -0
- data/lib/frodo/properties/string.rb +72 -0
- data/lib/frodo/properties/time.rb +40 -0
- data/lib/frodo/properties/time_of_day.rb +27 -0
- data/lib/frodo/properties.rb +32 -0
- data/lib/frodo/property.rb +139 -0
- data/lib/frodo/property_registry.rb +41 -0
- data/lib/frodo/query/criteria/comparison_operators.rb +49 -0
- data/lib/frodo/query/criteria/date_functions.rb +61 -0
- data/lib/frodo/query/criteria/geography_functions.rb +21 -0
- data/lib/frodo/query/criteria/lambda_operators.rb +27 -0
- data/lib/frodo/query/criteria/string_functions.rb +40 -0
- data/lib/frodo/query/criteria.rb +92 -0
- data/lib/frodo/query/in_batches.rb +58 -0
- data/lib/frodo/query.rb +221 -0
- data/lib/frodo/railtie.rb +19 -0
- data/lib/frodo/schema/complex_type.rb +79 -0
- data/lib/frodo/schema/enum_type.rb +95 -0
- data/lib/frodo/schema.rb +164 -0
- data/lib/frodo/service.rb +199 -0
- data/lib/frodo/service_registry.rb +52 -0
- data/lib/frodo/version.rb +3 -0
- data/lib/frodo.rb +67 -0
- data/spec/fixtures/auth_success_response.json +11 -0
- data/spec/fixtures/error.json +11 -0
- data/spec/fixtures/files/entity_to_xml.xml +18 -0
- data/spec/fixtures/files/error.xml +5 -0
- data/spec/fixtures/files/metadata.xml +150 -0
- data/spec/fixtures/files/metadata_with_error.xml +157 -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/leads.json +923 -0
- data/spec/fixtures/refresh_error_response.json +8 -0
- data/spec/frodo/abstract_client_spec.rb +13 -0
- data/spec/frodo/client_spec.rb +57 -0
- data/spec/frodo/concerns/authentication_spec.rb +79 -0
- data/spec/frodo/concerns/base_spec.rb +68 -0
- data/spec/frodo/concerns/caching_spec.rb +40 -0
- data/spec/frodo/concerns/connection_spec.rb +65 -0
- data/spec/frodo/config_spec.rb +127 -0
- data/spec/frodo/entity/shared_examples.rb +83 -0
- data/spec/frodo/entity_container_spec.rb +38 -0
- data/spec/frodo/entity_set_spec.rb +169 -0
- data/spec/frodo/entity_spec.rb +153 -0
- data/spec/frodo/errors_spec.rb +48 -0
- data/spec/frodo/middleware/authentication/token_spec.rb +87 -0
- data/spec/frodo/middleware/authentication_spec.rb +83 -0
- data/spec/frodo/middleware/authorization_spec.rb +17 -0
- data/spec/frodo/middleware/custom_headers_spec.rb +21 -0
- data/spec/frodo/middleware/gzip_spec.rb +68 -0
- data/spec/frodo/middleware/instance_url_spec.rb +27 -0
- data/spec/frodo/middleware/logger_spec.rb +21 -0
- data/spec/frodo/middleware/odata_headers_spec.rb +15 -0
- data/spec/frodo/middleware/raise_error_spec.rb +66 -0
- data/spec/frodo/navigation_property/proxy_spec.rb +46 -0
- data/spec/frodo/navigation_property_spec.rb +55 -0
- data/spec/frodo/properties/binary_spec.rb +50 -0
- data/spec/frodo/properties/boolean_spec.rb +72 -0
- data/spec/frodo/properties/collection_spec.rb +44 -0
- data/spec/frodo/properties/date_spec.rb +23 -0
- data/spec/frodo/properties/date_time_offset_spec.rb +30 -0
- data/spec/frodo/properties/date_time_spec.rb +23 -0
- data/spec/frodo/properties/decimal_spec.rb +50 -0
- data/spec/frodo/properties/float_spec.rb +45 -0
- data/spec/frodo/properties/geography/line_string_spec.rb +33 -0
- data/spec/frodo/properties/geography/point_spec.rb +29 -0
- data/spec/frodo/properties/geography/polygon_spec.rb +55 -0
- data/spec/frodo/properties/geography/shared_examples.rb +72 -0
- data/spec/frodo/properties/guid_spec.rb +17 -0
- data/spec/frodo/properties/integer_spec.rb +58 -0
- data/spec/frodo/properties/string_spec.rb +46 -0
- data/spec/frodo/properties/time_of_day_spec.rb +23 -0
- data/spec/frodo/properties/time_spec.rb +15 -0
- data/spec/frodo/property_registry_spec.rb +16 -0
- data/spec/frodo/property_spec.rb +71 -0
- data/spec/frodo/query/criteria_spec.rb +229 -0
- data/spec/frodo/query_spec.rb +156 -0
- data/spec/frodo/schema/complex_type_spec.rb +97 -0
- data/spec/frodo/schema/enum_type_spec.rb +112 -0
- data/spec/frodo/schema_spec.rb +113 -0
- data/spec/frodo/service_registry_spec.rb +19 -0
- data/spec/frodo/service_spec.rb +153 -0
- data/spec/frodo/usage_example_spec.rb +161 -0
- data/spec/spec_helper.rb +35 -0
- data/spec/support/coverage.rb +2 -0
- data/spec/support/fixture_helpers.rb +14 -0
- data/spec/support/middleware.rb +19 -0
- metadata +479 -0
data/lib/frodo/schema.rb
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
require 'frodo/schema/complex_type'
|
|
2
|
+
require 'frodo/schema/enum_type'
|
|
3
|
+
|
|
4
|
+
module Frodo
|
|
5
|
+
class Schema
|
|
6
|
+
# The schema's parent service
|
|
7
|
+
attr_reader :service
|
|
8
|
+
# The schema's metadata (i.e its XML definition)
|
|
9
|
+
attr_reader :metadata
|
|
10
|
+
|
|
11
|
+
# Creates a new schema.
|
|
12
|
+
#
|
|
13
|
+
# @param schema_definition [Nokogiri::XML] The schema's XML definition
|
|
14
|
+
# @param service [Frodo::Service] The schema's parent service
|
|
15
|
+
def initialize(schema_definition, service)
|
|
16
|
+
@metadata = schema_definition
|
|
17
|
+
@service = service
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Returns the schema's `Namespace` attribute (mandatory).
|
|
21
|
+
# @return [String]
|
|
22
|
+
def namespace
|
|
23
|
+
@namespace ||= metadata.attributes['Namespace'].value
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Returns a list of actions defined by the schema.
|
|
27
|
+
# @return [Array<String>]
|
|
28
|
+
def actions
|
|
29
|
+
@actions ||= metadata.xpath('//Action').map do |action|
|
|
30
|
+
action.attributes['Name'].value
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Returns a list of entities defined by the schema.
|
|
35
|
+
# @return [Array<String>]
|
|
36
|
+
def entity_types
|
|
37
|
+
@entity_types ||= metadata.xpath('//EntityType').map do |entity|
|
|
38
|
+
entity.attributes['Name'].value
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Returns a list of `ComplexType`s defined by the schema.
|
|
43
|
+
# @return [Hash<String, Frodo::Schema::ComplexType>]
|
|
44
|
+
def complex_types
|
|
45
|
+
@complex_types ||= metadata.xpath('//ComplexType').map do |entity|
|
|
46
|
+
[
|
|
47
|
+
entity.attributes['Name'].value,
|
|
48
|
+
ComplexType.new(entity, self)
|
|
49
|
+
]
|
|
50
|
+
end.to_h
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Returns a list of EnumTypes defined by the schema.
|
|
54
|
+
# @return [Hash<String, Frodo::Schema::EnumType>]
|
|
55
|
+
def enum_types
|
|
56
|
+
@enum_types ||= metadata.xpath('//EnumType').map do |entity|
|
|
57
|
+
[
|
|
58
|
+
entity.attributes['Name'].value,
|
|
59
|
+
EnumType.new(entity, self)
|
|
60
|
+
]
|
|
61
|
+
end.to_h
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Returns a list of functions defined by the schema.
|
|
65
|
+
# @return [Array<String>]
|
|
66
|
+
def functions
|
|
67
|
+
@functions ||= metadata.xpath('//Function').map do |function|
|
|
68
|
+
function.attributes['Name'].value
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Returns a list of type definitions defined by the schema.
|
|
73
|
+
# @return [Array<String>]
|
|
74
|
+
def type_definitions
|
|
75
|
+
@typedefs ||= metadata.xpath('//TypeDefinition').map do |typedef|
|
|
76
|
+
typedef.attributes['Name'].value
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Returns a hash for finding an association through an entity type's defined
|
|
81
|
+
# NavigationProperty elements.
|
|
82
|
+
# @return [Hash<Hash<Frodo::NavigationProperty>>]
|
|
83
|
+
def navigation_properties
|
|
84
|
+
@navigation_properties ||= metadata.xpath('//EntityType').map do |entity_type_def|
|
|
85
|
+
[
|
|
86
|
+
entity_type_def.attributes['Name'].value,
|
|
87
|
+
entity_type_def.xpath('./NavigationProperty').map do |nav_property_def|
|
|
88
|
+
[
|
|
89
|
+
nav_property_def.attributes['Name'].value,
|
|
90
|
+
::Frodo::NavigationProperty.build(nav_property_def)
|
|
91
|
+
]
|
|
92
|
+
end.to_h
|
|
93
|
+
]
|
|
94
|
+
end.to_h
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Get the property type for an entity from metadata.
|
|
98
|
+
#
|
|
99
|
+
# @param entity_name [to_s] the name of the relevant entity
|
|
100
|
+
# @param property_name [to_s] the property name needed
|
|
101
|
+
# @return [String] the name of the property's type
|
|
102
|
+
def get_property_type(entity_name, property_name)
|
|
103
|
+
metadata.xpath("//EntityType[@Name='#{entity_name}']/Property[@Name='#{property_name}']").first.attributes['Type'].value
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Get the primary key for the supplied Entity.
|
|
107
|
+
#
|
|
108
|
+
# @param entity_name [to_s]
|
|
109
|
+
# @return [String]
|
|
110
|
+
def primary_key_for(entity_name)
|
|
111
|
+
metadata.xpath("//EntityType[@Name='#{entity_name}']/Key/PropertyRef").first.attributes['Name'].value
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Get the list of properties and their various options for the supplied
|
|
115
|
+
# Entity name.
|
|
116
|
+
# @param entity_name [to_s]
|
|
117
|
+
# @return [Hash]
|
|
118
|
+
# @api private
|
|
119
|
+
def properties_for_entity(entity_name)
|
|
120
|
+
type_definition = metadata.xpath("//EntityType[@Name='#{entity_name}']").first
|
|
121
|
+
|
|
122
|
+
raise ArgumentError, "Unknown EntityType: #{entity_name}" if type_definition.nil?
|
|
123
|
+
properties_to_return = {}
|
|
124
|
+
|
|
125
|
+
parent_properties = if base_type = type_definition.attributes['BaseType']
|
|
126
|
+
parent_type = base_type.value.split('.').last
|
|
127
|
+
properties_for_entity(parent_type)
|
|
128
|
+
else
|
|
129
|
+
{}
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
type_definition.xpath('./Property').each do |property_xml|
|
|
133
|
+
property_name, property = process_property_from_xml(property_xml)
|
|
134
|
+
properties_to_return[property_name] = property
|
|
135
|
+
end
|
|
136
|
+
parent_properties.merge!(properties_to_return)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
private
|
|
140
|
+
|
|
141
|
+
def process_property_from_xml(property_xml)
|
|
142
|
+
property_name = property_xml.attributes['Name'].value
|
|
143
|
+
property_type = property_xml.attributes['Type'].value
|
|
144
|
+
property_options = { service: service }
|
|
145
|
+
|
|
146
|
+
property_type, value_type = property_type.split(/\(|\)/)
|
|
147
|
+
if property_type == 'Collection'
|
|
148
|
+
klass = ::Frodo::Properties::Collection
|
|
149
|
+
property_options.merge(value_type: value_type)
|
|
150
|
+
else
|
|
151
|
+
klass = ::Frodo::PropertyRegistry[property_type]
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
if klass.nil?
|
|
155
|
+
raise RuntimeError, "Unknown property type: #{property_type}"
|
|
156
|
+
else
|
|
157
|
+
property_options[:allows_nil] = false if property_xml.attributes['Nullable'] == 'false'
|
|
158
|
+
property = klass.new(property_name, nil, property_options)
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
return [property_name, property]
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
|
|
2
|
+
module Frodo
|
|
3
|
+
# Encapsulates the basic details and functionality needed to interact with an
|
|
4
|
+
# Frodo service.
|
|
5
|
+
class Service
|
|
6
|
+
# The Frodo Service's URL
|
|
7
|
+
attr_reader :service_url
|
|
8
|
+
# Service options
|
|
9
|
+
attr_reader :options
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
# Opens the service based on the requested URL and adds the service to
|
|
13
|
+
# {Frodo::Registry}
|
|
14
|
+
#
|
|
15
|
+
# @param service_url [String]
|
|
16
|
+
# The URL to the Frodo service
|
|
17
|
+
# @param options [Hash] options to pass to the service
|
|
18
|
+
# @return [Frodo::Service] an instance of the service
|
|
19
|
+
def initialize(service_url, options = {}, &block)
|
|
20
|
+
@options = default_options.merge(options)
|
|
21
|
+
@service_url = service_url
|
|
22
|
+
|
|
23
|
+
Frodo::ServiceRegistry.add(self)
|
|
24
|
+
register_custom_types
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Returns user supplied name for service, or its URL
|
|
28
|
+
# @return [String]
|
|
29
|
+
def name
|
|
30
|
+
@name ||= options[:name] || service_url
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Returns the service's metadata URL.
|
|
34
|
+
# @return [String]
|
|
35
|
+
def metadata_url
|
|
36
|
+
"#{service_url}/$metadata"
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Returns the service's metadata definition.
|
|
40
|
+
# @return [Nokogiri::XML]
|
|
41
|
+
def metadata
|
|
42
|
+
@metadata ||= lambda { read_metadata }.call
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Returns all of the service's schemas.
|
|
46
|
+
# @return Hash<String, Frodo::Schema>
|
|
47
|
+
def schemas
|
|
48
|
+
@schemas ||= metadata.xpath('//Schema').map do |schema_xml|
|
|
49
|
+
[
|
|
50
|
+
schema_xml.attributes['Namespace'].value,
|
|
51
|
+
Schema.new(schema_xml, self)
|
|
52
|
+
]
|
|
53
|
+
end.to_h
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Returns the service's EntityContainer (singleton)
|
|
57
|
+
# @return Frodo::EntityContainer
|
|
58
|
+
def entity_container
|
|
59
|
+
@entity_container ||= EntityContainer.new(self)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Returns a hash of EntitySet names and their respective EntityType names
|
|
63
|
+
# @return Hash<String, String>
|
|
64
|
+
def entity_sets
|
|
65
|
+
entity_container.entity_sets
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Retrieves the EntitySet associated with a specific EntityType by name
|
|
69
|
+
#
|
|
70
|
+
# @param entity_set_name [to_s] the name of the EntitySet desired
|
|
71
|
+
# @return [Frodo::EntitySet] an Frodo::EntitySet to query
|
|
72
|
+
def [](entity_set_name)
|
|
73
|
+
entity_container[entity_set_name]
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Returns the default namespace, that is, the namespace of the schema
|
|
77
|
+
# that contains the service's EntityContainer.
|
|
78
|
+
# @return [String]
|
|
79
|
+
def namespace
|
|
80
|
+
entity_container.namespace
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Returns a list of `EntityType`s exposed by the service
|
|
84
|
+
# @return Array<String>
|
|
85
|
+
def entity_types
|
|
86
|
+
@entity_types ||= schemas.map do |namespace, schema|
|
|
87
|
+
schema.entity_types.map do |entity_type|
|
|
88
|
+
"#{namespace}.#{entity_type}"
|
|
89
|
+
end
|
|
90
|
+
end.flatten
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Returns a list of `ComplexType`s used by the service.
|
|
94
|
+
# @return [Hash<String, Frodo::Schema::ComplexType>]
|
|
95
|
+
def complex_types
|
|
96
|
+
@complex_types ||= schemas.map do |namespace, schema|
|
|
97
|
+
schema.complex_types.map do |name, complex_type|
|
|
98
|
+
[ "#{namespace}.#{name}", complex_type ]
|
|
99
|
+
end.to_h
|
|
100
|
+
end.reduce({}, :merge)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Returns a list of `EnumType`s used by the service
|
|
104
|
+
# @return [Hash<String, Frodo::Schema::EnumType>]
|
|
105
|
+
def enum_types
|
|
106
|
+
@enum_types ||= schemas.map do |namespace, schema|
|
|
107
|
+
schema.enum_types.map do |name, enum_type|
|
|
108
|
+
[ "#{namespace}.#{name}", enum_type ]
|
|
109
|
+
end.to_h
|
|
110
|
+
end.reduce({}, :merge)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Returns a more compact inspection of the service object
|
|
114
|
+
def inspect
|
|
115
|
+
"#<#{self.class.name}:#{self.object_id} name='#{name}' service_url='#{self.service_url}'>"
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# Get the property type for an entity from metadata.
|
|
119
|
+
#
|
|
120
|
+
# @param entity_name [to_s] the fully qualified entity name
|
|
121
|
+
# @param property_name [to_s] the property name needed
|
|
122
|
+
# @return [String] the name of the property's type
|
|
123
|
+
def get_property_type(entity_name, property_name)
|
|
124
|
+
namespace, _, entity_name = entity_name.rpartition('.')
|
|
125
|
+
raise ArgumentError, 'Namespace missing' if namespace.nil? || namespace.empty?
|
|
126
|
+
schemas[namespace].get_property_type(entity_name, property_name)
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# Get the primary key for the supplied Entity.
|
|
130
|
+
#
|
|
131
|
+
# @param entity_name [to_s] The fully qualified entity name
|
|
132
|
+
# @return [String]
|
|
133
|
+
def primary_key_for(entity_name)
|
|
134
|
+
namespace, _, entity_name = entity_name.rpartition('.')
|
|
135
|
+
raise ArgumentError, 'Namespace missing' if namespace.nil? || namespace.empty?
|
|
136
|
+
schemas[namespace].primary_key_for(entity_name)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# Get the list of properties and their various options for the supplied
|
|
140
|
+
# Entity name.
|
|
141
|
+
# @param entity_name [to_s]
|
|
142
|
+
# @return [Hash]
|
|
143
|
+
# @api private
|
|
144
|
+
def properties_for_entity(entity_name)
|
|
145
|
+
namespace, _, entity_name = entity_name.rpartition('.')
|
|
146
|
+
raise ArgumentError, 'Namespace missing' if namespace.nil? || namespace.empty?
|
|
147
|
+
schemas[namespace].properties_for_entity(entity_name)
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# Returns the logger instance used by the service.
|
|
151
|
+
# When Ruby on Rails has been detected, the service will
|
|
152
|
+
# use `Rails.logger`. The log level will NOT be changed.
|
|
153
|
+
#
|
|
154
|
+
# When no Rails has been detected, a default logger will
|
|
155
|
+
# be used that logs to STDOUT with the log level supplied
|
|
156
|
+
# via options, or the default log level if none was given.
|
|
157
|
+
# @return [Logger]
|
|
158
|
+
def logger
|
|
159
|
+
@logger ||= options[:logger] || if defined?(Rails)
|
|
160
|
+
Rails.logger
|
|
161
|
+
else
|
|
162
|
+
default_logger
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
private
|
|
167
|
+
|
|
168
|
+
def default_options
|
|
169
|
+
{
|
|
170
|
+
strict: true # strict property validation
|
|
171
|
+
}
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def default_logger
|
|
175
|
+
Frodo.configuration.logger if Frodo.log?
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def read_metadata
|
|
179
|
+
# From file, good for debugging
|
|
180
|
+
if options[:metadata_file]
|
|
181
|
+
data = File.read(options[:metadata_file])
|
|
182
|
+
::Nokogiri::XML(data).remove_namespaces!
|
|
183
|
+
elsif options[:metadata_document]
|
|
184
|
+
data = options[:metadata_document]
|
|
185
|
+
::Nokogiri::XML(data).remove_namespaces!
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def register_custom_types
|
|
190
|
+
complex_types.each do |name, type|
|
|
191
|
+
::Frodo::PropertyRegistry.add(name, type.property_class)
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
enum_types.each do |name, type|
|
|
195
|
+
::Frodo::PropertyRegistry.add(name, type.property_class)
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
require 'singleton'
|
|
2
|
+
|
|
3
|
+
module Frodo
|
|
4
|
+
# Provides a registry for keeping track of multiple Frodo::Service instances
|
|
5
|
+
class ServiceRegistry
|
|
6
|
+
include Singleton
|
|
7
|
+
|
|
8
|
+
# Add a service to the Registry
|
|
9
|
+
#
|
|
10
|
+
# @param service [Frodo::Service] service to add to the registry
|
|
11
|
+
def add(service)
|
|
12
|
+
initialize_instance_variables
|
|
13
|
+
@services << service if service.is_a?(Frodo::Service) && !@services.include?(service)
|
|
14
|
+
@services_by_name[service.name] = @services.find_index(service)
|
|
15
|
+
@services_by_url[service.service_url] = @services.find_index(service)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Lookup a service by URL or name
|
|
19
|
+
#
|
|
20
|
+
# @param lookup_key [String] the URL or name to lookup
|
|
21
|
+
# @return [Frodo::Service, nil] the Frodo::Service or nil
|
|
22
|
+
def [](lookup_key)
|
|
23
|
+
initialize_instance_variables
|
|
24
|
+
index = @services_by_name[lookup_key] || @services_by_url[lookup_key]
|
|
25
|
+
index.nil? ? nil : @services[index]
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# (see #add)
|
|
29
|
+
def self.add(service)
|
|
30
|
+
Frodo::ServiceRegistry.instance.add(service)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# (see #[])
|
|
34
|
+
def self.[](lookup_key)
|
|
35
|
+
Frodo::ServiceRegistry.instance[lookup_key]
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
def initialize_instance_variables
|
|
41
|
+
@services ||= []
|
|
42
|
+
@services_by_name ||= {}
|
|
43
|
+
@services_by_url ||= {}
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def flush
|
|
47
|
+
@services = []
|
|
48
|
+
@services_by_name = {}
|
|
49
|
+
@services_by_url = {}
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
data/lib/frodo.rb
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
require 'uri'
|
|
2
|
+
require 'date'
|
|
3
|
+
require 'time'
|
|
4
|
+
require 'bigdecimal'
|
|
5
|
+
require 'nokogiri'
|
|
6
|
+
require 'faraday'
|
|
7
|
+
require 'logger'
|
|
8
|
+
require 'andand'
|
|
9
|
+
require 'json'
|
|
10
|
+
require 'faraday_middleware'
|
|
11
|
+
|
|
12
|
+
require 'frodo/concerns/api'
|
|
13
|
+
require 'frodo/concerns/authentication'
|
|
14
|
+
require 'frodo/concerns/base'
|
|
15
|
+
require 'frodo/concerns/caching'
|
|
16
|
+
require 'frodo/concerns/connection'
|
|
17
|
+
require 'frodo/concerns/verbs'
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
require 'frodo/middleware'
|
|
21
|
+
require 'frodo/middleware/authentication'
|
|
22
|
+
require 'frodo/middleware/authentication/token'
|
|
23
|
+
require 'frodo/middleware/authorization'
|
|
24
|
+
require 'frodo/middleware/caching'
|
|
25
|
+
require 'frodo/middleware/custom_headers'
|
|
26
|
+
require 'frodo/middleware/gzip'
|
|
27
|
+
require 'frodo/middleware/instance_url'
|
|
28
|
+
require 'frodo/middleware/logger'
|
|
29
|
+
require 'frodo/middleware/odata_headers'
|
|
30
|
+
require 'frodo/middleware/raise_error'
|
|
31
|
+
require 'frodo/middleware/multipart'
|
|
32
|
+
require 'frodo/abstract_client'
|
|
33
|
+
require 'frodo/client'
|
|
34
|
+
require 'frodo/config'
|
|
35
|
+
|
|
36
|
+
require 'frodo/version'
|
|
37
|
+
require 'frodo/errors'
|
|
38
|
+
require 'frodo/property_registry'
|
|
39
|
+
require 'frodo/property'
|
|
40
|
+
require 'frodo/properties'
|
|
41
|
+
require 'frodo/navigation_property'
|
|
42
|
+
require 'frodo/entity'
|
|
43
|
+
require 'frodo/entity_container'
|
|
44
|
+
require 'frodo/entity_set'
|
|
45
|
+
require 'frodo/query'
|
|
46
|
+
require 'frodo/schema'
|
|
47
|
+
require 'frodo/service'
|
|
48
|
+
require 'frodo/service_registry'
|
|
49
|
+
|
|
50
|
+
require 'frodo/railtie' if defined?(::Rails)
|
|
51
|
+
|
|
52
|
+
# The Frodo gem provides a convenient way to interact with OData V4 services from
|
|
53
|
+
# Ruby. Please look to the {file:README.md README} for how to get started using
|
|
54
|
+
# the Frodo gem.
|
|
55
|
+
module Frodo
|
|
56
|
+
Error = Class.new(StandardError)
|
|
57
|
+
ServerError = Class.new(Error)
|
|
58
|
+
AuthenticationError = Class.new(Error)
|
|
59
|
+
UnauthorizedError = Class.new(Error)
|
|
60
|
+
APIVersionError = Class.new(Error)
|
|
61
|
+
|
|
62
|
+
class << self
|
|
63
|
+
def new(*args, &block)
|
|
64
|
+
Frodo::Client.new(*args, &block)
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"token_type": "Bearer",
|
|
3
|
+
"scope": "user_impersonation",
|
|
4
|
+
"expires_in": "3599",
|
|
5
|
+
"ext_expires_in": "3599",
|
|
6
|
+
"expires_on": "1552430212",
|
|
7
|
+
"not_before": "1552426312",
|
|
8
|
+
"resource": "https://emmanueltest.crm.dynamics.com",
|
|
9
|
+
"access_token": "gfEzf4azkWZMTjlay7usiSWhc0eOLNkKMw",
|
|
10
|
+
"refresh_token": "QswqIkdHSdbyvbDFuLwHNAoU1QgAA"
|
|
11
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"error": {
|
|
3
|
+
"code": "0x8006088a",
|
|
4
|
+
"message": "Resource not found for the segment 'leadsss'.",
|
|
5
|
+
"innererror": {
|
|
6
|
+
"message": "Resource not found for the segment 'leadsss'.",
|
|
7
|
+
"type": "Microsoft.OData.UriParser.ODataUnrecognizedPathException",
|
|
8
|
+
"stacktrace": " at Microsoft.OData.UriParser.ODataPathParser.CreateDynamicPathSegment(ODataPathSegment previous, String identifier, String parenthesisExpression)rn at Microsoft.OData.UriParser.ODataPathParser.CreateFirstSegment(String segmentText)rn at Microsoft.OData.UriParser.ODataPathParser.ParsePath(ICollection`1 segments)rn at Microsoft.OData.UriParser.ODataPathFactory.BindPath(ICollection`1 segments, ODataUriParserConfiguration configuration)rn at Microsoft.OData.UriParser.ODataUriParser.Initialize()rn at System.Web.OData.Routing.DefaultODataPathHandler.Parse(String serviceRoot, String odataPath, IServiceProvider requestContainer, Boolean template)rn at System.Web.OData.Routing.DefaultODataPathHandler.Parse(String serviceRoot, String odataPath, IServiceProvider requestContainer)rn at Microsoft.Crm.Extensibility.ODataV4.Routing.CrmODataPathHandler.Parse(String serviceRoot, String odataPath, IServiceProvider requestContainer)"
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<?xml version="1.0"?>
|
|
2
|
+
<entry xmlns="http://www.w3.org/2005/Atom" xmlns:data="http://docs.oasis-open.org/odata/ns/data" xmlns:metadata="http://docs.oasis-open.org/odata/ns/metadata" xmlns:georss="http://www.georss.org/georss" xmlns:gml="http://www.opengis.net/gml" xml:base="http://services.odata.org/V4/OData/OData.svc">
|
|
3
|
+
<category term="ODataDemo.Product" scheme="http://docs.oasis-open.org/odata/ns/scheme"/>
|
|
4
|
+
<author>
|
|
5
|
+
<name/>
|
|
6
|
+
</author>
|
|
7
|
+
<content type="application/xml">
|
|
8
|
+
<metadata:properties>
|
|
9
|
+
<data:Name metadata:type="Edm.String">Bread</data:Name>
|
|
10
|
+
<data:Description metadata:type="Edm.String">Whole grain bread</data:Description>
|
|
11
|
+
<data:ReleaseDate metadata:type="Edm.DateTimeOffset">1992-01-01T00:00:00Z</data:ReleaseDate>
|
|
12
|
+
<data:DiscontinuedDate metadata:type="Edm.DateTimeOffset" metadata:null="true"/>
|
|
13
|
+
<data:Rating metadata:type="Edm.Int16">4</data:Rating>
|
|
14
|
+
<data:Price metadata:type="Edm.Double">2.5</data:Price>
|
|
15
|
+
<data:ProductStatus metadata:type="ODataDemo.ProductStatus" metadata:null="true"/>
|
|
16
|
+
</metadata:properties>
|
|
17
|
+
</content>
|
|
18
|
+
</entry>
|