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.
Files changed (60) hide show
  1. data/Gemfile +12 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README +14 -0
  4. data/Rakefile +53 -0
  5. data/VERSION +1 -0
  6. data/app/controllers/o_data_controller.rb +145 -0
  7. data/app/helpers/o_data_helper.rb +203 -0
  8. data/app/views/o_data/metadata.xml.builder +62 -0
  9. data/app/views/o_data/resource.atom.builder +8 -0
  10. data/app/views/o_data/resource.json.erb +1 -0
  11. data/app/views/o_data/service.xml.builder +11 -0
  12. data/config/routes.rb +11 -0
  13. data/init.rb +3 -0
  14. data/install.rb +1 -0
  15. data/lib/o_data/abstract_query.rb +27 -0
  16. data/lib/o_data/abstract_query/base.rb +133 -0
  17. data/lib/o_data/abstract_query/countable.rb +22 -0
  18. data/lib/o_data/abstract_query/errors.rb +117 -0
  19. data/lib/o_data/abstract_query/option.rb +58 -0
  20. data/lib/o_data/abstract_query/options/enumerated_option.rb +39 -0
  21. data/lib/o_data/abstract_query/options/expand_option.rb +81 -0
  22. data/lib/o_data/abstract_query/options/format_option.rb +19 -0
  23. data/lib/o_data/abstract_query/options/inlinecount_option.rb +20 -0
  24. data/lib/o_data/abstract_query/options/orderby_option.rb +79 -0
  25. data/lib/o_data/abstract_query/options/select_option.rb +74 -0
  26. data/lib/o_data/abstract_query/options/skip_option.rb +32 -0
  27. data/lib/o_data/abstract_query/options/top_option.rb +32 -0
  28. data/lib/o_data/abstract_query/parser.rb +77 -0
  29. data/lib/o_data/abstract_query/segment.rb +43 -0
  30. data/lib/o_data/abstract_query/segments/collection_segment.rb +24 -0
  31. data/lib/o_data/abstract_query/segments/count_segment.rb +38 -0
  32. data/lib/o_data/abstract_query/segments/entity_type_and_key_values_segment.rb +116 -0
  33. data/lib/o_data/abstract_query/segments/entity_type_segment.rb +31 -0
  34. data/lib/o_data/abstract_query/segments/links_segment.rb +49 -0
  35. data/lib/o_data/abstract_query/segments/navigation_property_segment.rb +82 -0
  36. data/lib/o_data/abstract_query/segments/property_segment.rb +50 -0
  37. data/lib/o_data/abstract_query/segments/value_segment.rb +40 -0
  38. data/lib/o_data/abstract_schema.rb +9 -0
  39. data/lib/o_data/abstract_schema/association.rb +29 -0
  40. data/lib/o_data/abstract_schema/base.rb +48 -0
  41. data/lib/o_data/abstract_schema/comparable.rb +42 -0
  42. data/lib/o_data/abstract_schema/end.rb +50 -0
  43. data/lib/o_data/abstract_schema/entity_type.rb +64 -0
  44. data/lib/o_data/abstract_schema/navigation_property.rb +37 -0
  45. data/lib/o_data/abstract_schema/property.rb +35 -0
  46. data/lib/o_data/abstract_schema/schema_object.rb +37 -0
  47. data/lib/o_data/abstract_schema/serializable.rb +79 -0
  48. data/lib/o_data/active_record_schema.rb +8 -0
  49. data/lib/o_data/active_record_schema/association.rb +130 -0
  50. data/lib/o_data/active_record_schema/base.rb +37 -0
  51. data/lib/o_data/active_record_schema/entity_type.rb +128 -0
  52. data/lib/o_data/active_record_schema/navigation_property.rb +45 -0
  53. data/lib/o_data/active_record_schema/property.rb +45 -0
  54. data/lib/o_data/active_record_schema/serializable.rb +36 -0
  55. data/lib/odata_server.rb +12 -0
  56. data/public/clientaccesspolicy.xml +13 -0
  57. data/tasks/o_data_server_tasks.rake +4 -0
  58. data/test/helper.rb +17 -0
  59. data/uninstall.rb +1 -0
  60. 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