odata_server 0.0.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.
- data/Gemfile +12 -0
- data/MIT-LICENSE +20 -0
- data/README +14 -0
- data/Rakefile +53 -0
- data/VERSION +1 -0
- data/app/controllers/o_data_controller.rb +145 -0
- data/app/helpers/o_data_helper.rb +203 -0
- data/app/views/o_data/metadata.xml.builder +62 -0
- data/app/views/o_data/resource.atom.builder +8 -0
- data/app/views/o_data/resource.json.erb +1 -0
- data/app/views/o_data/service.xml.builder +11 -0
- data/config/routes.rb +11 -0
- data/init.rb +3 -0
- data/install.rb +1 -0
- data/lib/o_data/abstract_query.rb +27 -0
- data/lib/o_data/abstract_query/base.rb +133 -0
- data/lib/o_data/abstract_query/countable.rb +22 -0
- data/lib/o_data/abstract_query/errors.rb +117 -0
- data/lib/o_data/abstract_query/option.rb +58 -0
- data/lib/o_data/abstract_query/options/enumerated_option.rb +39 -0
- data/lib/o_data/abstract_query/options/expand_option.rb +81 -0
- data/lib/o_data/abstract_query/options/format_option.rb +19 -0
- data/lib/o_data/abstract_query/options/inlinecount_option.rb +20 -0
- data/lib/o_data/abstract_query/options/orderby_option.rb +79 -0
- data/lib/o_data/abstract_query/options/select_option.rb +74 -0
- data/lib/o_data/abstract_query/options/skip_option.rb +32 -0
- data/lib/o_data/abstract_query/options/top_option.rb +32 -0
- data/lib/o_data/abstract_query/parser.rb +77 -0
- data/lib/o_data/abstract_query/segment.rb +43 -0
- data/lib/o_data/abstract_query/segments/collection_segment.rb +24 -0
- data/lib/o_data/abstract_query/segments/count_segment.rb +38 -0
- data/lib/o_data/abstract_query/segments/entity_type_and_key_values_segment.rb +116 -0
- data/lib/o_data/abstract_query/segments/entity_type_segment.rb +31 -0
- data/lib/o_data/abstract_query/segments/links_segment.rb +49 -0
- data/lib/o_data/abstract_query/segments/navigation_property_segment.rb +82 -0
- data/lib/o_data/abstract_query/segments/property_segment.rb +50 -0
- data/lib/o_data/abstract_query/segments/value_segment.rb +40 -0
- data/lib/o_data/abstract_schema.rb +9 -0
- data/lib/o_data/abstract_schema/association.rb +29 -0
- data/lib/o_data/abstract_schema/base.rb +48 -0
- data/lib/o_data/abstract_schema/comparable.rb +42 -0
- data/lib/o_data/abstract_schema/end.rb +50 -0
- data/lib/o_data/abstract_schema/entity_type.rb +64 -0
- data/lib/o_data/abstract_schema/navigation_property.rb +37 -0
- data/lib/o_data/abstract_schema/property.rb +35 -0
- data/lib/o_data/abstract_schema/schema_object.rb +37 -0
- data/lib/o_data/abstract_schema/serializable.rb +79 -0
- data/lib/o_data/active_record_schema.rb +8 -0
- data/lib/o_data/active_record_schema/association.rb +130 -0
- data/lib/o_data/active_record_schema/base.rb +37 -0
- data/lib/o_data/active_record_schema/entity_type.rb +128 -0
- data/lib/o_data/active_record_schema/navigation_property.rb +45 -0
- data/lib/o_data/active_record_schema/property.rb +45 -0
- data/lib/o_data/active_record_schema/serializable.rb +36 -0
- data/lib/odata_server.rb +12 -0
- data/public/clientaccesspolicy.xml +13 -0
- data/tasks/o_data_server_tasks.rake +4 -0
- data/test/helper.rb +17 -0
- data/uninstall.rb +1 -0
- metadata +171 -0
@@ -0,0 +1,39 @@
|
|
1
|
+
module OData
|
2
|
+
module AbstractQuery
|
3
|
+
module Options
|
4
|
+
class EnumeratedOption < OData::AbstractQuery::Option
|
5
|
+
def self.valid_values
|
6
|
+
%w{}
|
7
|
+
end
|
8
|
+
|
9
|
+
def valid_values
|
10
|
+
self.class.valid_values
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize(query, key, value = nil)
|
14
|
+
super(query, key, value)
|
15
|
+
end
|
16
|
+
|
17
|
+
# def self.applies_to?(query)
|
18
|
+
# false
|
19
|
+
# end
|
20
|
+
|
21
|
+
def self.parse!(query, key, value = nil)
|
22
|
+
return nil unless key == self.option_name
|
23
|
+
return nil if valid_values.empty?
|
24
|
+
|
25
|
+
if value.blank?
|
26
|
+
query.Option(self, key, valid_values.first)
|
27
|
+
else
|
28
|
+
query.Option(self, key, value)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def valid?
|
33
|
+
return false if self.value.blank? || self.valid_values.empty?
|
34
|
+
self.valid_values.collect(&:to_s).include?(self.value.to_s)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
module OData
|
2
|
+
module AbstractQuery
|
3
|
+
module Options
|
4
|
+
class ExpandOption < OData::AbstractQuery::Option
|
5
|
+
def self.option_name
|
6
|
+
'$expand'
|
7
|
+
end
|
8
|
+
|
9
|
+
attr_reader :navigation_property_paths, :navigation_property_paths_str
|
10
|
+
|
11
|
+
# TODO: remove navigation_property_paths_str
|
12
|
+
def initialize(query, navigation_property_paths = {}, navigation_property_paths_str = nil)
|
13
|
+
@navigation_property_paths = navigation_property_paths
|
14
|
+
@navigation_property_paths_str = navigation_property_paths_str
|
15
|
+
|
16
|
+
super(query, self.class.option_name)
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.applies_to?(query)
|
20
|
+
return false if query.segments.empty?
|
21
|
+
(query.segments.last.is_a?(OData::AbstractQuery::Segments::CollectionSegment) || query.segments.last.is_a?(OData::AbstractQuery::Segments::NavigationPropertySegment))
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.parse!(query, key, value = nil)
|
25
|
+
return nil unless key == self.option_name
|
26
|
+
|
27
|
+
if query.segments.last.respond_to?(:navigation_property)
|
28
|
+
navigation_property = query.segments.last.navigation_property
|
29
|
+
|
30
|
+
raise OData::AbstractQuery::Errors::InvalidOptionValue.new(query, self.option_name) if navigation_property.to_end.polymorphic?
|
31
|
+
end
|
32
|
+
|
33
|
+
if query.segments.last.respond_to?(:entity_type)
|
34
|
+
entity_type = query.segments.last.entity_type
|
35
|
+
|
36
|
+
navigation_property_paths = value.to_s.split(/\s*,\s*/).inject({}) { |acc, path|
|
37
|
+
segments = path.split('/')
|
38
|
+
reflect_on_navigation_property_path(query, acc, entity_type, segments.shift, segments)
|
39
|
+
acc
|
40
|
+
}
|
41
|
+
|
42
|
+
query.Option(self, navigation_property_paths, value.to_s)
|
43
|
+
else
|
44
|
+
raise OData::AbstractQuery::Errors::InvalidOptionContext.new(query, self.option_name) unless value.blank?
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def valid?
|
49
|
+
# TODO: replace with validation
|
50
|
+
true
|
51
|
+
end
|
52
|
+
|
53
|
+
def value
|
54
|
+
"'" + @navigation_property_paths_str.gsub(/\s+/, '') + "'"
|
55
|
+
end
|
56
|
+
|
57
|
+
protected
|
58
|
+
|
59
|
+
def self.reflect_on_navigation_property_path(query, acc, entity_type, head, rest)
|
60
|
+
if head.blank?
|
61
|
+
acc
|
62
|
+
elsif entity_type.blank?
|
63
|
+
raise OData::AbstractQuery::Errors::NavigationPropertyNotFound.new(nil, head)
|
64
|
+
elsif navigation_property = entity_type.navigation_properties.find { |np| np.name == head }
|
65
|
+
acc[navigation_property] ||= {}
|
66
|
+
|
67
|
+
if navigation_property.to_end.polymorphic?
|
68
|
+
raise OData::AbstractQuery::Errors::InvalidOptionValue.new(query, head) unless rest.empty?
|
69
|
+
else
|
70
|
+
reflect_on_navigation_property_path(query, acc[navigation_property], navigation_property.to_end.entity_type, rest.shift, rest)
|
71
|
+
end
|
72
|
+
|
73
|
+
acc
|
74
|
+
else
|
75
|
+
raise OData::AbstractQuery::Errors::NavigationPropertyNotFound.new(nil, head)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module OData
|
2
|
+
module AbstractQuery
|
3
|
+
module Options
|
4
|
+
class FormatOption < EnumeratedOption
|
5
|
+
def self.option_name
|
6
|
+
'$format'
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.valid_values
|
10
|
+
%w{atom json}
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.applies_to?(query)
|
14
|
+
true
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module OData
|
2
|
+
module AbstractQuery
|
3
|
+
module Options
|
4
|
+
class InlinecountOption < EnumeratedOption
|
5
|
+
def self.option_name
|
6
|
+
'$inlinecount'
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.valid_values
|
10
|
+
%w{none allpages}
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.applies_to?(query)
|
14
|
+
return false if query.segments.empty?
|
15
|
+
query.segments.last.is_a?(OData::AbstractQuery::Segments::CollectionSegment) || query.segments.last.is_a?(OData::AbstractQuery::Segments::NavigationPropertySegment)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
module OData
|
2
|
+
module AbstractQuery
|
3
|
+
module Options
|
4
|
+
class OrderbyOption < OData::AbstractQuery::Option
|
5
|
+
def self.option_name
|
6
|
+
'$orderby'
|
7
|
+
end
|
8
|
+
|
9
|
+
attr_reader :pairs
|
10
|
+
|
11
|
+
def initialize(query, pairs = [])
|
12
|
+
@pairs = pairs
|
13
|
+
|
14
|
+
super(query, self.class.option_name)
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.applies_to?(query)
|
18
|
+
return false if query.segments.empty?
|
19
|
+
(query.segments.last.is_a?(OData::AbstractQuery::Segments::CollectionSegment) || query.segments.last.is_a?(OData::AbstractQuery::Segments::NavigationPropertySegment))
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.parse!(query, key, value = nil)
|
23
|
+
return nil unless key == self.option_name
|
24
|
+
|
25
|
+
if query.segments.last.respond_to?(:navigation_property)
|
26
|
+
navigation_property = query.segments.last.navigation_property
|
27
|
+
|
28
|
+
raise OData::AbstractQuery::Errors::InvalidOptionValue.new(query, self.option_name) if navigation_property.to_end.polymorphic?
|
29
|
+
end
|
30
|
+
|
31
|
+
raise OData::AbstractQuery::Errors::InvalidOptionContext.new(query, self) unless query.segments.last.respond_to?(:countable?) && query.segments.last.countable?
|
32
|
+
|
33
|
+
if query.segments.last.respond_to?(:entity_type)
|
34
|
+
entity_type = query.segments.last.entity_type
|
35
|
+
|
36
|
+
pairs = value.to_s.split(/\s*,\s*/).collect { |path|
|
37
|
+
if md = path.match(/^([A-Za-z_]+(?:\/[A-Za-z_]+)*)(?:\s+(asc|desc))?$/)
|
38
|
+
property_name = md[1]
|
39
|
+
order = md[2].blank? ? :asc : md[2].to_sym
|
40
|
+
|
41
|
+
property = entity_type.properties.find { |p| p.name == property_name }
|
42
|
+
raise OData::AbstractQuery::Errors::PropertyNotFound.new(query, property_name) if property.blank?
|
43
|
+
|
44
|
+
[property, order]
|
45
|
+
else
|
46
|
+
raise OData::AbstractQuery::Errors::PropertyNotFound.new(query, path)
|
47
|
+
end
|
48
|
+
}
|
49
|
+
|
50
|
+
query.Option(self, pairs.empty? ? [[entity_type.key_property, :asc]] : pairs)
|
51
|
+
else
|
52
|
+
raise OData::AbstractQuery::Errors::InvalidOptionContext.new(query, self.option_name) unless value.blank?
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def entity_type
|
57
|
+
return nil if self.query.segments.empty?
|
58
|
+
return nil unless self.query.segments.last.respond_to?(:entity_type)
|
59
|
+
@entity_type ||= self.query.segments.last.entity_type
|
60
|
+
end
|
61
|
+
|
62
|
+
def valid?
|
63
|
+
entity_type = self.entity_type
|
64
|
+
return false if entity_type.blank?
|
65
|
+
|
66
|
+
@pairs.is_a?(Array) && @pairs.all? { |pair|
|
67
|
+
property, value = pair
|
68
|
+
|
69
|
+
property.is_a?(OData::AbstractSchema::Property) && !!entity_type.properties.find { |p| p.name == property.name }
|
70
|
+
}
|
71
|
+
end
|
72
|
+
|
73
|
+
def value
|
74
|
+
"'" + @pairs.collect { |p| "#{p.first.name} #{p.last}" }.join(',') + "'"
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
module OData
|
2
|
+
module AbstractQuery
|
3
|
+
module Options
|
4
|
+
class SelectOption < OData::AbstractQuery::Option
|
5
|
+
def self.option_name
|
6
|
+
'$select'
|
7
|
+
end
|
8
|
+
|
9
|
+
attr_reader :properties
|
10
|
+
|
11
|
+
def initialize(query, properties = [])
|
12
|
+
@properties = properties
|
13
|
+
|
14
|
+
super(query, self.class.option_name)
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.applies_to?(query)
|
18
|
+
return false if query.segments.empty?
|
19
|
+
(query.segments.last.is_a?(OData::AbstractQuery::Segments::CollectionSegment) || query.segments.last.is_a?(OData::AbstractQuery::Segments::NavigationPropertySegment))
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.parse!(query, key, value = nil)
|
23
|
+
return nil unless key == self.option_name
|
24
|
+
|
25
|
+
if query.segments.last.respond_to?(:navigation_property)
|
26
|
+
navigation_property = query.segments.last.navigation_property
|
27
|
+
|
28
|
+
raise OData::AbstractQuery::Errors::InvalidOptionValue.new(query, self.option_name) if navigation_property.to_end.polymorphic?
|
29
|
+
end
|
30
|
+
|
31
|
+
if query.segments.last.respond_to?(:entity_type)
|
32
|
+
entity_type = query.segments.last.entity_type
|
33
|
+
|
34
|
+
properties = value.to_s.strip == "*" ? entity_type.properties : value.to_s.split(/\s*,\s*/).collect { |path|
|
35
|
+
if md = path.match(/^([A-Za-z_]+)$/)
|
36
|
+
property_name = md[1]
|
37
|
+
|
38
|
+
property = entity_type.properties.find { |p| p.name == property_name }
|
39
|
+
raise OData::AbstractQuery::Errors::PropertyNotFound.new(query, property_name) if property.blank?
|
40
|
+
|
41
|
+
property
|
42
|
+
else
|
43
|
+
raise OData::AbstractQuery::Errors::PropertyNotFound.new(query, path)
|
44
|
+
end
|
45
|
+
}
|
46
|
+
|
47
|
+
query.Option(self, properties)
|
48
|
+
else
|
49
|
+
raise OData::AbstractQuery::Errors::InvalidOptionContext.new(query, self.option_name) unless value.blank?
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def entity_type
|
54
|
+
return nil if self.query.segments.empty?
|
55
|
+
return nil unless self.query.segments.last.respond_to?(:entity_type)
|
56
|
+
@entity_type ||= self.query.segments.last.entity_type
|
57
|
+
end
|
58
|
+
|
59
|
+
def valid?
|
60
|
+
entity_type = self.entity_type
|
61
|
+
return false if entity_type.blank?
|
62
|
+
|
63
|
+
@properties.is_a?(Array) && @properties.all? { |property|
|
64
|
+
property.is_a?(OData::AbstractSchema::Property) && !!entity_type.properties.find { |p| p == property }
|
65
|
+
}
|
66
|
+
end
|
67
|
+
|
68
|
+
def value
|
69
|
+
"'" + @properties.collect(&:name).join(',') + "'"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module OData
|
2
|
+
module AbstractQuery
|
3
|
+
module Options
|
4
|
+
class SkipOption < OData::AbstractQuery::Option
|
5
|
+
def self.option_name
|
6
|
+
'$skip'
|
7
|
+
end
|
8
|
+
|
9
|
+
def initialize(query, key, value = nil)
|
10
|
+
super(query, key, value)
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.applies_to?(query)
|
14
|
+
return false if query.segments.empty?
|
15
|
+
return false unless query.segments.last.respond_to?(:countable?)
|
16
|
+
query.segments.last.countable?
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.parse!(query, key, value = nil)
|
20
|
+
return nil unless key == self.option_name
|
21
|
+
|
22
|
+
query.Option(self, key, value.to_i)
|
23
|
+
end
|
24
|
+
|
25
|
+
def valid?
|
26
|
+
return false if self.value.blank?
|
27
|
+
self.value.to_i >= 0
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module OData
|
2
|
+
module AbstractQuery
|
3
|
+
module Options
|
4
|
+
class TopOption < OData::AbstractQuery::Option
|
5
|
+
def self.option_name
|
6
|
+
'$top'
|
7
|
+
end
|
8
|
+
|
9
|
+
def initialize(query, key, value = nil)
|
10
|
+
super(query, key, value)
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.applies_to?(query)
|
14
|
+
return false if query.segments.empty?
|
15
|
+
return false unless query.segments.last.respond_to?(:countable?)
|
16
|
+
query.segments.last.countable?
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.parse!(query, key, value = nil)
|
20
|
+
return nil unless key == self.option_name
|
21
|
+
|
22
|
+
query.Option(self, key, value.to_i)
|
23
|
+
end
|
24
|
+
|
25
|
+
def valid?
|
26
|
+
return false if self.value.blank?
|
27
|
+
self.value.to_i >= 1
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
module OData
|
2
|
+
module AbstractQuery
|
3
|
+
class Parser
|
4
|
+
cattr_reader :reserved_option_names
|
5
|
+
@@reserved_option_names = %w{orderby expand select top skip filter format inlinecount}.freeze
|
6
|
+
|
7
|
+
attr_reader :schema
|
8
|
+
|
9
|
+
def initialize(schema)
|
10
|
+
@schema = schema
|
11
|
+
end
|
12
|
+
|
13
|
+
def parse!(uri)
|
14
|
+
return nil if uri.blank?
|
15
|
+
|
16
|
+
query = @schema.Query
|
17
|
+
|
18
|
+
resource_path, query_string = uri.split('?', 2)
|
19
|
+
|
20
|
+
unless resource_path.blank?
|
21
|
+
resource_path_components = resource_path.split('/')
|
22
|
+
resource_path_components.each_index do |i|
|
23
|
+
resource_path_component = resource_path_components[i]
|
24
|
+
segment = _parse_segment!(query, resource_path_component)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
unless query_string.blank?
|
29
|
+
query_string_components = query_string.split('&')
|
30
|
+
query_string_components.each_index do |i|
|
31
|
+
query_string_component = query_string_components[i]
|
32
|
+
option = _parse_option!(query, query_string_component)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
query
|
37
|
+
end
|
38
|
+
|
39
|
+
protected
|
40
|
+
|
41
|
+
def _parse_segment!(query, resource_path_component)
|
42
|
+
Object.subclasses_of(Segment).each do |segment_class|
|
43
|
+
if segment_class.can_follow?(query.segments.last)
|
44
|
+
if segment = segment_class.parse!(query, resource_path_component)
|
45
|
+
return segment
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
raise Errors::ParseQuerySegmentException.new(query, resource_path_component)
|
51
|
+
end
|
52
|
+
|
53
|
+
def _parse_option!(query, query_string_component)
|
54
|
+
key, value = query_string_component.split('=', 2)
|
55
|
+
|
56
|
+
if md = key.match(/^\$(.*?)$/)
|
57
|
+
raise Errors::InvalidReservedOptionName.new(query, key, value) unless @@reserved_option_names.include?(md[1])
|
58
|
+
end
|
59
|
+
|
60
|
+
if md = value.match(/^'\s*([^']+)\s*'$/)
|
61
|
+
value = md[1]
|
62
|
+
end
|
63
|
+
|
64
|
+
Object.subclasses_of(Option).each do |option_class|
|
65
|
+
if option_class.applies_to?(query)
|
66
|
+
if option = option_class.parse!(query, key, value)
|
67
|
+
return option
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# basic (or "custom") option
|
73
|
+
query.Option(BasicOption, key, value)
|
74
|
+
end
|
75
|
+
end # Parser
|
76
|
+
end # AbstractQuery
|
77
|
+
end # OData
|