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
data/Gemfile ADDED
@@ -0,0 +1,12 @@
1
+ source "http://rubygems.org"
2
+ # Add dependencies required to use your gem here.
3
+ # Example:
4
+ # gem "activesupport", ">= 2.3.5"
5
+
6
+ # Add dependencies to develop your gem here.
7
+ # Include everything needed to run rake, tests, features, etc.
8
+ group :development do
9
+ gem "bundler", "~> 1.0.0"
10
+ gem "jeweler", "~> 1.5.2"
11
+ gem "rcov", ">= 0"
12
+ end
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010 [name of plugin creator]
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README ADDED
@@ -0,0 +1,14 @@
1
+ OData Server
2
+ ===========
3
+
4
+ This is a rails plugin/engine to embue your application with OData features (so its data can be readily consumed by
5
+ an OData client).
6
+
7
+ It was forked Mark Borkum's work at from http://rubyforge.org/scm/?group_id=9605 with only a few small additions
8
+ to support postgresql and declarative authorization and released as a gem.
9
+
10
+
11
+
12
+ The software iterates over your models exposing each as an OData source automatically.
13
+
14
+ Beware - this code is not heavily tested, it does work in my limited tests usint Tableau public to consume data.
@@ -0,0 +1,53 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ begin
4
+ Bundler.setup(:default, :development)
5
+ rescue Bundler::BundlerError => e
6
+ $stderr.puts e.message
7
+ $stderr.puts "Run `bundle install` to install missing gems"
8
+ exit e.status_code
9
+ end
10
+ require 'rake'
11
+
12
+ require 'jeweler'
13
+ Jeweler::Tasks.new do |gem|
14
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
15
+ gem.name = "odata_server"
16
+ gem.homepage = "http://github.com/bwlang/odata_server"
17
+ gem.license = "MIT"
18
+ gem.summary = %Q{OData server for Rails}
19
+ gem.description = %Q{This is a gem to enable OData consumers to pull data from your Rails application.}
20
+ gem.email = "langhorst@neb.com"
21
+ gem.authors = ["Mark Borkum", "Brad Langhorst"]
22
+ # Include your dependencies below. Runtime dependencies are required when using your gem,
23
+ # and development dependencies are only needed for development (ie running rake tasks, tests, etc)
24
+ # gem.add_runtime_dependency 'jabber4r', '> 0.1'
25
+ # gem.add_development_dependency 'rspec', '> 1.2.3'
26
+ end
27
+ Jeweler::RubygemsDotOrgTasks.new
28
+
29
+ require 'rake/testtask'
30
+ Rake::TestTask.new(:test) do |test|
31
+ test.libs << 'lib' << 'test'
32
+ test.pattern = 'test/**/test_*.rb'
33
+ test.verbose = true
34
+ end
35
+
36
+ require 'rcov/rcovtask'
37
+ Rcov::RcovTask.new do |test|
38
+ test.libs << 'test'
39
+ test.pattern = 'test/**/test_*.rb'
40
+ test.verbose = true
41
+ end
42
+
43
+ task :default => :test
44
+
45
+ require 'rake/rdoctask'
46
+ Rake::RDocTask.new do |rdoc|
47
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
48
+
49
+ rdoc.rdoc_dir = 'rdoc'
50
+ rdoc.title = "odata_server #{version}"
51
+ rdoc.rdoc_files.include('README*')
52
+ rdoc.rdoc_files.include('lib/**/*.rb')
53
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.1
@@ -0,0 +1,145 @@
1
+ class ODataController < ApplicationController
2
+ cattr_reader :path_param
3
+ @@path_param = :__path__.freeze
4
+
5
+ cattr_reader :schema
6
+ @@schema = OData::ActiveRecordSchema::Base.new.freeze
7
+
8
+ cattr_reader :parser
9
+ @@parser = OData::AbstractQuery::Parser.new(@@schema).freeze
10
+
11
+ rescue_from OData::ODataException, :with => :handle_exception
12
+ rescue_from ActiveRecord::RecordNotFound, :with => :handle_exception
13
+
14
+ before_filter :extract_resource_path_and_query_string, :only => [:resource]
15
+ before_filter :parse_resource_path_and_query_string!, :only => [:resource]
16
+ before_filter :set_request_format!, :only => [:resource]
17
+
18
+ %w{service metadata resource}.each do |method_name|
19
+ define_method(:"redirect_to_#{method_name}") do
20
+ # redirect_to(send(:"o_data_#{method_name}_url"))
21
+ redirect_to(params.merge(:action => method_name.to_s))
22
+ end
23
+ end
24
+
25
+ def service
26
+ respond_to do |format|
27
+ format.xml # service.xml.builder
28
+ format.json { render :json => @@schema.to_json }
29
+ end
30
+ end
31
+
32
+ def metadata
33
+ respond_to do |format|
34
+ format.xml # metadata.xml.builder
35
+ end
36
+ end
37
+
38
+ def resource
39
+ @last_segment = @query.segments.last
40
+
41
+ @results = @query.execute!
42
+
43
+ case @last_segment.class.segment_name
44
+ when OData::AbstractQuery::Segments::CountSegment.segment_name
45
+ render :text => @results.to_i
46
+ when OData::AbstractQuery::Segments::LinksSegment.segment_name
47
+ request.format = :xml unless request.format == :json
48
+
49
+ respond_to do |format|
50
+ format.xml { render :inline => "xml.instruct!; @results.empty? ? xml.links('xmlns' => 'http://schemas.microsoft.com/ado/2007/08/dataservices') : xml.links('xmlns' => 'http://schemas.microsoft.com/ado/2007/08/dataservices') { @results.each { |r| xml.uri(o_data_resource_url(r[1])) } }", :type => :builder }
51
+ format.json { render :json => { "links" => @results.collect { |r| { "uri" => r } } }.to_json }
52
+ end
53
+ when OData::AbstractQuery::Segments::ValueSegment.segment_name
54
+ render :text => @results.to_s
55
+ when OData::AbstractQuery::Segments::PropertySegment.segment_name
56
+ request.format = :xml unless request.format == :json
57
+
58
+ respond_to do |format|
59
+ format.xml { render :inline => "xml.instruct!; value.blank? ? xml.tag!(key.to_sym, 'm:null' => true, 'xmlns' => 'http://schemas.microsoft.com/ado/2007/08/dataservices', 'xmlns:m' => 'http://schemas.microsoft.com/ado/2007/08/dataservices') : xml.tag!(key.to_sym, value, 'edm:Type' => type, 'xmlns' => 'http://schemas.microsoft.com/ado/2007/08/dataservices', 'xmlns:edm' => 'http://schemas.microsoft.com/ado/2007/05/edm')", :locals => { :key => @results.keys.first.name, :type => @results.keys.first.return_type, :value => @results.values.first }, :type => :builder }
60
+ format.json { render :json => { @results.keys.first.name => @results.values.first }.to_json }
61
+ end
62
+ when OData::AbstractQuery::Segments::NavigationPropertySegment.segment_name
63
+ @countable = @last_segment.countable?
64
+
65
+ @navigation_property = @last_segment.navigation_property
66
+ @polymorphic = @navigation_property.to_end.polymorphic?
67
+
68
+ if @polymorphic
69
+ @entity_type = nil
70
+ @entity_type_name = @navigation_property.to_end.name.singularize
71
+ else
72
+ @entity_type = @navigation_property.to_end.entity_type
73
+ @entity_type_name = @entity_type.name
74
+ end
75
+
76
+ @collection_name = @entity_type_name.pluralize
77
+
78
+ @expand_navigation_property_paths = {}
79
+ if expand_option = @query.options.find { |o| o.option_name == OData::AbstractQuery::Options::ExpandOption.option_name }
80
+ @expand_navigation_property_paths = expand_option.navigation_property_paths
81
+ end
82
+
83
+ respond_to do |format|
84
+ format.atom # resource.atom.builder
85
+ format.json # resource.json.erb
86
+ end
87
+ when OData::AbstractQuery::Segments::CollectionSegment.segment_name
88
+ @countable = @last_segment.countable?
89
+
90
+ @navigation_property = nil
91
+ @polymorphic = true
92
+
93
+ @entity_type = @last_segment.entity_type
94
+
95
+ @expand_navigation_property_paths = {}
96
+ if expand_option = @query.options.find { |o| o.option_name == OData::AbstractQuery::Options::ExpandOption.option_name }
97
+ @expand_navigation_property_paths = expand_option.navigation_property_paths
98
+ end
99
+
100
+ respond_to do |format|
101
+ format.atom # resource.atom.builder
102
+ format.json # resource.json.erb
103
+ end
104
+ else
105
+ # in theory, this branch is unreachable because the <tt>parse_resource_path_and_query_string!</tt>
106
+ # method will throw an exception if the <tt>OData::AbstractQuery::Parser</tt> fails to match any
107
+ # segment of the resource path.
108
+ raise OData::AbstractQuery::Errors::AbstractQueryException.new(@query)
109
+ end
110
+ end
111
+
112
+ private
113
+
114
+ def extract_resource_path_and_query_string
115
+ @resource_path = params[@@path_param].join('/')
116
+
117
+ @query_string = params.inject({}) { |acc, pair|
118
+ key, value = pair
119
+ acc[key.to_sym] = value unless [@@path_param, :controller, :action].include?(key.to_sym)
120
+ acc
121
+ }.collect { |key, value|
122
+ key.to_s + '=' + value.to_s
123
+ }.join('&')
124
+ end
125
+
126
+ def parse_resource_path_and_query_string!
127
+ @query = @@parser.parse!([@resource_path, @query_string].compact.join('?'))
128
+ end
129
+
130
+ def set_request_format!
131
+ if format_option = @query.options.find { |o| o.option_name == OData::AbstractQuery::Options::FormatOption.option_name }
132
+ if format_value = format_option.value
133
+ request.format = format_value.to_sym
134
+ end
135
+ end
136
+ end
137
+
138
+ def handle_exception(ex)
139
+ request.format = :xml
140
+
141
+ respond_to do |format|
142
+ format.xml { render :inline => "xml.instruct!; xml.error('xmlns' => 'http://schemas.microsoft.com/ado/2007/08/dataservices/metadata') { xml.code(code.to_s); xml.message(message); xml.uri(uri) }", :type => :builder, :locals => { :code => nil, :message => ex.message, :uri => request.url } }
143
+ end
144
+ end
145
+ end
@@ -0,0 +1,203 @@
1
+ module ODataHelper
2
+ @@o_data_atom_xmlns = {
3
+ "xmlns" => "http://www.w3.org/2005/Atom",
4
+ "xmlns:d" => "http://schemas.microsoft.com/ado/2007/08/dataservices",
5
+ "xmlns:m" => "http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"
6
+ }.freeze
7
+
8
+ def o_data_atom_feed(xml, query, results, options = {})
9
+ results_href, results_url = begin
10
+ if base_href = options.delete(:href)
11
+ [base_href.to_s, o_data_resource_url(base_href.to_s)]
12
+ else
13
+ [query.resource_path, o_data_resource_url(query.to_uri)]
14
+ end
15
+ end
16
+
17
+ results_title = options.delete(:title) || results_href
18
+
19
+ xml.tag!(:feed, { "xml:base" => o_data_service_url }.merge(options[:hide_xmlns] ? {} : @@o_data_atom_xmlns)) do
20
+ xml.tag!(:title, results_title)
21
+ xml.tag!(:id, results_url)
22
+ xml.tag!(:link, :rel => "self", :title => results_title, :href => results_href)
23
+
24
+ unless results.empty?
25
+ if last_result = results.last
26
+ if atom_updated_at = query.schema.atom_updated_at_for(last_result)
27
+ xml.tag!(:updated, atom_updated_at.iso8601)
28
+ end
29
+ end
30
+
31
+ results.each do |result|
32
+ o_data_atom_entry(xml, query, result, options.merge(:hide_xmlns => true, :href => results_href))
33
+ end
34
+ end
35
+
36
+ if inlinecount_option = query.options.find { |o| o.option_name == OData::AbstractQuery::Options::InlinecountOption.option_name }
37
+ if inlinecount_option.value == 'allpages'
38
+ xml.m(:count, results.length)
39
+ end
40
+ end
41
+ end
42
+ end
43
+
44
+ def o_data_atom_entry(xml, query, result, options = {})
45
+ entity_type = options[:entity_type] || query.schema.find_entity_type(:active_record => result.class)
46
+ raise OData::AbstractQuery::Errors::EntityTypeNotFound.new(query, result.class.name) if entity_type.blank?
47
+
48
+ result_href = entity_type.href_for(result)
49
+ result_url = o_data_resource_url(result_href)
50
+
51
+ result_title = entity_type.atom_title_for(result)
52
+ result_summary = entity_type.atom_summary_for(result)
53
+ result_updated_at = entity_type.atom_updated_at_for(result)
54
+
55
+ xml.tag!(:entry, {}.merge(options[:hide_xmlns] ? {} : @@o_data_atom_xmlns)) do
56
+ xml.tag!(:id, result_url) unless result_href.blank?
57
+ xml.tag!(:title, result_title, :type => "text") unless result_title.blank?
58
+ xml.tag!(:summary, result_summary, :type => "text") unless result_summary.blank?
59
+ xml.tag!(:updated, result_updated_at.iso8601) unless result_updated_at.blank?
60
+
61
+ xml.tag!(:author) do
62
+ xml.tag!(:name)
63
+ end
64
+
65
+ xml.tag!(:link, :rel => "edit", :title => result_title, :href => result_href) unless result_title.blank? || result_href.blank?
66
+
67
+ unless entity_type.navigation_properties.empty?
68
+ entity_type.navigation_properties.sort_by(&:name).each do |navigation_property|
69
+ navigation_property_href = result_href + '/' + navigation_property.name
70
+
71
+ navigation_property_attrs = { :rel => "http://schemas.microsoft.com/ado/2007/08/dataservices/related/" + navigation_property.name, :type => "application/atom+xml;type=#{navigation_property.to_end.multiple? ? 'feed' : 'entry'}", :title => navigation_property.name, :href => navigation_property_href }
72
+
73
+ if (options[:expand] || {}).keys.include?(navigation_property)
74
+ xml.tag!(:link, navigation_property_attrs) do
75
+ xml.m(:inline, :type => navigation_property_attrs[:type]) do
76
+ if navigation_property.to_end.multiple?
77
+ o_data_atom_feed(xml, query, navigation_property.find_all(result), options.merge(:entity_type => navigation_property.to_end.entity_type, :expand => options[:expand][navigation_property]))
78
+ else
79
+ o_data_atom_entry(xml, query, navigation_property.find_one(result), options.merge(:entity_type => navigation_property.to_end.entity_type, :expand => options[:expand][navigation_property]))
80
+ end
81
+ end
82
+ end
83
+ else
84
+ xml.tag!(:link, navigation_property_attrs)
85
+ end
86
+ end
87
+ end
88
+
89
+ xml.tag!(:category, :term => entity_type.qualified_name, :scheme => "http://schemas.microsoft.com/ado/2007/08/dataservices/scheme")
90
+
91
+ unless (properties = get_selected_properties_for(query, entity_type)).empty?
92
+ xml.tag!(:content, :type => "application/xml") do
93
+ xml.m(:properties) do
94
+ properties.each do |property|
95
+ property_attrs = { "m:type" => property.return_type }
96
+
97
+ unless (value = property.value_for(result)).blank?
98
+ xml.d(property.name.to_sym, value, property_attrs)
99
+ else
100
+ xml.d(property.name.to_sym, property_attrs.merge("m:null" => true))
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
108
+
109
+ def o_data_json_feed(query, results, options = {})
110
+ results_json = results.collect { |result| o_data_json_entry(query, result, options.merge(:d => false, :deferred => false)) }
111
+
112
+ if inlinecount_option = query.options.find { |o| o.option_name == OData::AbstractQuery::Options::InlinecountOption.option_name }
113
+ if inlinecount_option.value == 'allpages'
114
+ _json = {
115
+ "results" => results_json,
116
+ "__count" => results.length
117
+ }
118
+
119
+ return options[:d] ? { "d" => _json } : _json
120
+ end
121
+ end
122
+
123
+ options[:d] ? { "d" => results_json } : results_json
124
+ end
125
+
126
+ def o_data_json_entry(query, result, options = {})
127
+ entity_type = options[:entity_type] || query.schema.find_entity_type(:active_record => result.class)
128
+ raise OData::AbstractQuery::Errors::EntityTypeNotFound.new(query, result.class.name) if entity_type.blank?
129
+
130
+ resource_uri = o_data_resource_url(entity_type.href_for(result))
131
+ resource_type = entity_type.qualified_name
132
+
133
+ json = begin
134
+ if options[:deferred]
135
+ {
136
+ "__deferred" => {
137
+ "uri" => resource_uri
138
+ }
139
+ }
140
+ else
141
+ _json = {
142
+ "__metadata" => {
143
+ "uri" => resource_uri,
144
+ "type" => resource_type
145
+ }
146
+ }
147
+
148
+ get_selected_properties_for(query, entity_type).each do |property|
149
+ unless %w{__deferred __metadata}.include?(property.name.to_s)
150
+ _json[property.name.to_s] = property.value_for(result)
151
+ else
152
+ # TODO: raise JSONException (property with reserved name)
153
+ end
154
+ end
155
+
156
+ entity_type.navigation_properties.sort_by(&:name).each do |navigation_property|
157
+ unless %w{__deferred __metadata}.include?(navigation_property.name.to_s)
158
+ navigation_property_uri = resource_uri + '/' + navigation_property.name.to_s
159
+
160
+ _json[navigation_property.name.to_s] = begin
161
+ if (options[:expand] || {}).keys.include?(navigation_property)
162
+ if navigation_property.to_end.multiple?
163
+ o_data_json_feed(query, navigation_property.find_all(result), options.merge(:entity_type => navigation_property.to_end.entity_type, :expand => options[:expand][navigation_property], :d => false))
164
+ else
165
+ o_data_json_entry(query, navigation_property.find_one(result), options.merge(:entity_type => navigation_property.to_end.entity_type, :expand => options[:expand][navigation_property], :d => false))
166
+ end
167
+ else
168
+ {
169
+ "__deferred" => {
170
+ "uri" => navigation_property_uri
171
+ }
172
+ }
173
+ end
174
+ end
175
+ else
176
+ # TODO: raise JSONException (navigation property with reserved name)
177
+ end
178
+ end
179
+
180
+ _json
181
+ end
182
+ end
183
+
184
+ options[:d] ? { "d" => json } : json
185
+ end
186
+
187
+ protected
188
+
189
+ def get_selected_properties_for(query, entity_type)
190
+ if select_option = query.options.find { |o| o.option_name == OData::AbstractQuery::Options::SelectOption.option_name }
191
+ if select_option.entity_type == entity_type
192
+ # entity_type is the $select'ed collection/navigation property
193
+ return select_option.properties
194
+ else
195
+ # entity_type is an $expand'ed navigation property
196
+ return []
197
+ end
198
+ end
199
+
200
+ # $select option not supplied
201
+ entity_type.properties
202
+ end
203
+ end