lib/odata4/service.rb in odata4-0.7.0 vs lib/odata4/service.rb in odata4-0.8.0

- old
+ new

@@ -50,92 +50,89 @@ # @return [String] def metadata_url "#{service_url}/$metadata" end - # Returns a list of entities exposed by the service - def entity_types - @entity_types ||= metadata.xpath('//EntityType').collect {|entity| entity.attributes['Name'].value} + # Returns the service's metadata definition. + # @return [Nokogiri::XML] + def metadata + @metadata ||= lambda { read_metadata }.call end - # Returns a hash of EntitySet names keyed to their respective EntityType name - def entity_sets - @entity_sets ||= metadata.xpath('//EntityContainer/EntitySet').collect {|entity| + # Returns all of the service's schemas. + # @return Hash<String, OData4::Schema> + def schemas + @schemas ||= metadata.xpath('//Schema').map do |schema_xml| [ - entity.attributes['EntityType'].value.gsub("#{namespace}.", ''), - entity.attributes['Name'].value + schema_xml.attributes['Namespace'].value, + Schema.new(schema_xml, self) ] - }.to_h + end.to_h end - # Returns a list of ComplexTypes used by the service - # @return [Hash<String, OData4::ComplexType>] - def complex_types - @complex_types ||= metadata.xpath('//ComplexType').map do |entity| - [ - entity.attributes['Name'].value, - ::OData4::ComplexType.new(entity, self) - ] - end.to_h + # Returns the service's EntityContainer (singleton) + # @return OData4::EntityContainer + def entity_container + @entity_container ||= EntityContainer.new(self) end - # Returns a list of EnumTypes used by the service - # @return [Hash<String, OData4::EnumType>] - def enum_types - @enum_types ||= metadata.xpath('//EnumType').map do |entity| - [ - entity.attributes['Name'].value, - ::OData4::EnumType.new(entity, self) - ] - end.to_h + # Returns a hash of EntitySet names and their respective EntityType names + # @return Hash<String, String> + def entity_sets + entity_container.entity_sets end - # Returns a hash for finding an association through an entity type's defined - # NavigationProperty elements. - # @return [Hash<Hash<OData4::Association>>] - def navigation_properties - @navigation_properties ||= metadata.xpath('//EntityType').collect do |entity_type_def| - entity_type_name = entity_type_def.attributes['Name'].value - [ - entity_type_name, - entity_type_def.xpath('./NavigationProperty').collect do |nav_property_def| - [ - nav_property_def.attributes['Name'].value, - ::OData4::NavigationProperty.build(nav_property_def) - ] - end.to_h - ] - end.to_h + # Retrieves the EntitySet associated with a specific EntityType by name + # + # @param entity_set_name [to_s] the name of the EntitySet desired + # @return [OData4::EntitySet] an OData4::EntitySet to query + def [](entity_set_name) + entity_container[entity_set_name] end - # Returns the namespace defined on the service's schema + # Returns the default namespace, that is, the namespace of the schema + # that contains the service's EntityContainer. + # @return [String] def namespace - @namespace ||= metadata.xpath('//Schema').first.attributes['Namespace'].value + entity_container.namespace end + # Returns a list of `EntityType`s exposed by the service + # @return Array<String> + def entity_types + @entity_types ||= schemas.map do |namespace, schema| + schema.entity_types.map do |entity_type| + "#{namespace}.#{entity_type}" + end + end.flatten + end + + # Returns a list of `ComplexType`s used by the service. + # @return [Hash<String, OData4::ComplexType>] + def complex_types + @complex_types ||= schemas.map do |namespace, schema| + schema.complex_types.map do |name, complex_type| + [ "#{namespace}.#{name}", complex_type ] + end.to_h + end.reduce({}, :merge) + end + + # Returns a list of `EnumType`s used by the service + # @return [Hash<String, OData4::EnumType>] + def enum_types + @enum_types ||= schemas.map do |namespace, schema| + schema.enum_types.map do |name, enum_type| + [ "#{namespace}.#{name}", enum_type ] + end.to_h + end.reduce({}, :merge) + end + # Returns a more compact inspection of the service object def inspect "#<#{self.class.name}:#{self.object_id} name='#{name}' service_url='#{self.service_url}'>" end - # Retrieves the EntitySet associated with a specific EntityType by name - # - # @param entity_set_name [to_s] the name of the EntitySet desired - # @return [OData4::EntitySet] an OData4::EntitySet to query - def [](entity_set_name) - xpath_query = "//EntityContainer/EntitySet[@Name='#{entity_set_name}']" - entity_set_node = metadata.xpath(xpath_query).first - raise ArgumentError, "Unknown Entity Set: #{entity_set_name}" if entity_set_node.nil? - container_name = entity_set_node.parent.attributes['Name'].value - entity_type_name = entity_set_node.attributes['EntityType'].value.gsub(/#{namespace}\./, '') - OData4::EntitySet.new(name: entity_set_name, - namespace: namespace, - type: entity_type_name.to_s, - service_name: name, - container: container_name) - end - # Execute a request against the service # # @param url_chunk [to_s] string to append to service url # @param additional_options [Hash] options to pass to Typhoeus # @return [Typhoeus::Response] @@ -187,39 +184,38 @@ document.xpath('//entry') end # Get the property type for an entity from metadata. # - # @param entity_name [to_s] the name of the relevant entity + # @param entity_name [to_s] the fully qualified entity name # @param property_name [to_s] the property name needed # @return [String] the name of the property's type def get_property_type(entity_name, property_name) - metadata.xpath("//EntityType[@Name='#{entity_name}']/Property[@Name='#{property_name}']").first.attributes['Type'].value + namespace, _, entity_name = entity_name.rpartition('.') + raise ArgumentError, 'Namespace missing' if namespace.nil? || namespace.empty? + schemas[namespace].get_property_type(entity_name, property_name) end # Get the primary key for the supplied Entity. # - # @param entity_name [to_s] + # @param entity_name [to_s] The fully qualified entity name # @return [String] def primary_key_for(entity_name) - metadata.xpath("//EntityType[@Name='#{entity_name}']/Key/PropertyRef").first.attributes['Name'].value + namespace, _, entity_name = entity_name.rpartition('.') + raise ArgumentError, 'Namespace missing' if namespace.nil? || namespace.empty? + schemas[namespace].primary_key_for(entity_name) end # Get the list of properties and their various options for the supplied # Entity name. # @param entity_name [to_s] # @return [Hash] # @api private def properties_for_entity(entity_name) - type_definition = metadata.xpath("//EntityType[@Name='#{entity_name}']").first - raise ArgumentError, "Unknown EntityType: #{entity_name}" if type_definition.nil? - properties_to_return = {} - type_definition.xpath('./Property').each do |property_xml| - property_name, property = process_property_from_xml(property_xml) - properties_to_return[property_name] = property - end - properties_to_return + namespace, _, entity_name = entity_name.rpartition('.') + raise ArgumentError, 'Namespace missing' if namespace.nil? || namespace.empty? + schemas[namespace].properties_for_entity(entity_name) end # Returns the log level set via initial options, or the # default log level (`Logger::WARN`) if none was specified. # @see Logger @@ -260,11 +256,12 @@ def default_options { typhoeus: { headers: { 'OData4-Version' => '4.0' }, timeout: HTTP_TIMEOUT - } + }, + strict: true # strict property validation } end def content_type(format) if format == :auto @@ -274,14 +271,10 @@ else raise ArgumentError, "Unknown format '#{format}'" end end - def metadata - @metadata ||= lambda { read_metadata }.call - end - def read_metadata response = nil # From file, good for debugging if options[:metadata_file] data = File.read(options[:metadata_file]) @@ -312,33 +305,16 @@ def error_message(response) OData4::Query::Result.new(nil, response).error_message end - def process_property_from_xml(property_xml) - property_name = property_xml.attributes['Name'].value - value_type = property_xml.attributes['Type'].value - property_options = {} - - klass = ::OData4::PropertyRegistry[value_type] - - if klass.nil? - raise RuntimeError, "Unknown property type: #{value_type}" - else - property_options[:allows_nil] = false if property_xml.attributes['Nullable'] == 'false' - property = klass.new(property_name, nil, property_options) - end - - return [property_name, property] - end - def register_custom_types complex_types.each do |name, type| - ::OData4::PropertyRegistry.add(type.type, type.property_class) + ::OData4::PropertyRegistry.add(name, type.property_class) end enum_types.each do |name, type| - ::OData4::PropertyRegistry.add(type.type, type.property_class) + ::OData4::PropertyRegistry.add(name, type.property_class) end end end end