reso_transport 1.0.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/.gitignore +15 -0
- data/.travis.yml +7 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +33 -0
- data/LICENSE.txt +21 -0
- data/README.md +193 -0
- data/Rakefile +10 -0
- data/bin/console +30 -0
- data/bin/setup +8 -0
- data/lib/reso_transport/authentication/access.rb +23 -0
- data/lib/reso_transport/authentication/auth_strategy.rb +26 -0
- data/lib/reso_transport/authentication/fetch_token_auth.rb +49 -0
- data/lib/reso_transport/authentication/middleware.rb +46 -0
- data/lib/reso_transport/authentication/static_token_auth.rb +24 -0
- data/lib/reso_transport/authentication.rb +5 -0
- data/lib/reso_transport/client.rb +53 -0
- data/lib/reso_transport/configuration.rb +6 -0
- data/lib/reso_transport/entity_set.rb +11 -0
- data/lib/reso_transport/entity_type.rb +47 -0
- data/lib/reso_transport/enum.rb +37 -0
- data/lib/reso_transport/metadata.rb +48 -0
- data/lib/reso_transport/metadata_parser.rb +104 -0
- data/lib/reso_transport/property.rb +101 -0
- data/lib/reso_transport/query.rb +147 -0
- data/lib/reso_transport/resource.rb +55 -0
- data/lib/reso_transport/schema.rb +21 -0
- data/lib/reso_transport/version.rb +3 -0
- data/lib/reso_transport.rb +57 -0
- data/reso_transport.gemspec +35 -0
- data/sample_secrets.yml +26 -0
- metadata +172 -0
| @@ -0,0 +1,47 @@ | |
| 1 | 
            +
            module ResoTransport
         | 
| 2 | 
            +
              EntityType = Struct.new(:name, :base_type, :primary_key, :schema) do
         | 
| 3 | 
            +
             | 
| 4 | 
            +
                def self.from_stream(args)
         | 
| 5 | 
            +
                  new(args["Name"], args["BaseType"])
         | 
| 6 | 
            +
                end
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                def parse(record)
         | 
| 9 | 
            +
                  record.each_pair do |k,v|
         | 
| 10 | 
            +
                    next if v.nil?
         | 
| 11 | 
            +
                    if property = (property_map[k] || navigation_property_map[k])
         | 
| 12 | 
            +
                      record[k] = property.parse(v)
         | 
| 13 | 
            +
                    end
         | 
| 14 | 
            +
                  end
         | 
| 15 | 
            +
                end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                def parse_value(record)
         | 
| 18 | 
            +
                  record.each_pair do |k,v|
         | 
| 19 | 
            +
                    next if v.nil?
         | 
| 20 | 
            +
                    if property = (property_map[k] || navigation_property_map[k])
         | 
| 21 | 
            +
                      record[k] = property.parse(v)
         | 
| 22 | 
            +
                    end
         | 
| 23 | 
            +
                  end
         | 
| 24 | 
            +
                end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                def property_map
         | 
| 27 | 
            +
                  @property_map ||= properties.inject({}) {|hsh, p| hsh[p.name] = p; hsh }
         | 
| 28 | 
            +
                end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                def properties
         | 
| 31 | 
            +
                  @properties ||= []
         | 
| 32 | 
            +
                end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                def navigation_property_map
         | 
| 35 | 
            +
                  @navigation_property_map ||= navigation_properties.inject({}) {|hsh, p| hsh[p.name] = p; hsh }
         | 
| 36 | 
            +
                end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                def navigation_properties
         | 
| 39 | 
            +
                  @navigation_properties ||= []
         | 
| 40 | 
            +
                end
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                def enumerations
         | 
| 43 | 
            +
                  @enumerations ||= []
         | 
| 44 | 
            +
                end
         | 
| 45 | 
            +
             | 
| 46 | 
            +
              end
         | 
| 47 | 
            +
            end
         | 
| @@ -0,0 +1,37 @@ | |
| 1 | 
            +
            module ResoTransport
         | 
| 2 | 
            +
              Member = Struct.new(:name, :value, :annotation) do
         | 
| 3 | 
            +
                def self.from_stream(args)
         | 
| 4 | 
            +
                  new(args["Name"], args["Value"])
         | 
| 5 | 
            +
                end
         | 
| 6 | 
            +
              end
         | 
| 7 | 
            +
             | 
| 8 | 
            +
              Enum = Struct.new(:name, :type, :is_flags) do
         | 
| 9 | 
            +
                def self.from_stream(args)
         | 
| 10 | 
            +
                  new("#{args[:schema].namespace}.#{args["Name"]}", args["UnderlyingType"], args["IsFlags"])
         | 
| 11 | 
            +
                end
         | 
| 12 | 
            +
                
         | 
| 13 | 
            +
                def members
         | 
| 14 | 
            +
                  @members ||= []
         | 
| 15 | 
            +
                end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                def parse_value(value)
         | 
| 18 | 
            +
                  mapping.fetch(value, value)
         | 
| 19 | 
            +
                end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                def encode_value(value)
         | 
| 22 | 
            +
                  "'#{mapping.invert.fetch(value, value)}'"
         | 
| 23 | 
            +
                end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                def mapping
         | 
| 26 | 
            +
                  @mapping ||= generate_member_map || {}
         | 
| 27 | 
            +
                end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                def generate_member_map
         | 
| 30 | 
            +
                  members.map {|mem|
         | 
| 31 | 
            +
                    { mem.name => mem.annotation || mem.name }  
         | 
| 32 | 
            +
                  }.reduce(:merge!)
         | 
| 33 | 
            +
                end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
              end
         | 
| 36 | 
            +
            end
         | 
| 37 | 
            +
             | 
| @@ -0,0 +1,48 @@ | |
| 1 | 
            +
            module ResoTransport
         | 
| 2 | 
            +
              Metadata = Struct.new(:client) do 
         | 
| 3 | 
            +
             | 
| 4 | 
            +
                MIME_TYPES = {
         | 
| 5 | 
            +
                  xml: "application/xml",
         | 
| 6 | 
            +
                  json: "application/json"
         | 
| 7 | 
            +
                }
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                def entity_sets
         | 
| 10 | 
            +
                  parser.entity_sets
         | 
| 11 | 
            +
                end
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                def schemas
         | 
| 14 | 
            +
                  parser.schemas
         | 
| 15 | 
            +
                end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                def parser
         | 
| 18 | 
            +
                  @parser ||= MetadataParser.new.parse(get_data)
         | 
| 19 | 
            +
                end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                def get_data
         | 
| 22 | 
            +
                  if client.md_file 
         | 
| 23 | 
            +
                    if File.exist?(client.md_file) && File.size(client.md_file) > 0
         | 
| 24 | 
            +
                      File.new(client.md_file)
         | 
| 25 | 
            +
                    else
         | 
| 26 | 
            +
                      File.open(client.md_file, "w") {|f| f.write(raw) }
         | 
| 27 | 
            +
                      File.new(client.md_file)
         | 
| 28 | 
            +
                    end
         | 
| 29 | 
            +
                  else
         | 
| 30 | 
            +
                    raw
         | 
| 31 | 
            +
                  end
         | 
| 32 | 
            +
                end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                def raw
         | 
| 35 | 
            +
                  resp = client.connection.get("$metadata") do |req|
         | 
| 36 | 
            +
                    req.headers['Accept'] = MIME_TYPES[client.vendor.fetch(:metadata_format, :xml).to_sym]
         | 
| 37 | 
            +
                  end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                  if resp.success?
         | 
| 40 | 
            +
                    resp.body
         | 
| 41 | 
            +
                  else
         | 
| 42 | 
            +
                    puts resp.body
         | 
| 43 | 
            +
                    raise "Error getting metadata!"
         | 
| 44 | 
            +
                  end
         | 
| 45 | 
            +
                end
         | 
| 46 | 
            +
             | 
| 47 | 
            +
              end
         | 
| 48 | 
            +
            end
         | 
| @@ -0,0 +1,104 @@ | |
| 1 | 
            +
            module ResoTransport
         | 
| 2 | 
            +
              class MetadataParser
         | 
| 3 | 
            +
                include REXML::StreamListener
         | 
| 4 | 
            +
             | 
| 5 | 
            +
                attr_reader :schemas, :entity_sets, :enumerations
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                def initialize
         | 
| 8 | 
            +
                  @schemas = []
         | 
| 9 | 
            +
                  @entity_sets = []
         | 
| 10 | 
            +
                  @entity_types = []
         | 
| 11 | 
            +
                  @enumerations = []
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                  @current_entity_type = nil
         | 
| 14 | 
            +
                  @current_complex_type = nil
         | 
| 15 | 
            +
                  @current_enum_type = nil
         | 
| 16 | 
            +
                  @current_member = nil
         | 
| 17 | 
            +
                end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                def parse(doc)
         | 
| 20 | 
            +
                  REXML::Document.parse_stream(doc, self)
         | 
| 21 | 
            +
                  finalize
         | 
| 22 | 
            +
                  return self
         | 
| 23 | 
            +
                end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                def finalize
         | 
| 26 | 
            +
                  schemas.each do |s|
         | 
| 27 | 
            +
                    s.entity_types.each do |et|
         | 
| 28 | 
            +
                      et.properties.each do |p|
         | 
| 29 | 
            +
                        p.finalize_type(self)
         | 
| 30 | 
            +
                      end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                      et.navigation_properties.each do |p|
         | 
| 33 | 
            +
                        p.finalize_type(self)
         | 
| 34 | 
            +
                      end
         | 
| 35 | 
            +
                    end
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                    s.complex_types.each do |et|
         | 
| 38 | 
            +
                      et.properties.each do |p|
         | 
| 39 | 
            +
                        p.finalize_type(self)
         | 
| 40 | 
            +
                      end
         | 
| 41 | 
            +
                    end
         | 
| 42 | 
            +
                  end
         | 
| 43 | 
            +
                end
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                # Schema ->
         | 
| 46 | 
            +
                #   EnumType ->
         | 
| 47 | 
            +
                #     Members ->
         | 
| 48 | 
            +
                #       Annotation
         | 
| 49 | 
            +
                #   EntityType ->
         | 
| 50 | 
            +
                #     Key
         | 
| 51 | 
            +
                #     Properties ->
         | 
| 52 | 
            +
                #       enumerations
         | 
| 53 | 
            +
                #
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                def tag_start(name, args)
         | 
| 56 | 
            +
                  case name
         | 
| 57 | 
            +
                  when "Schema"
         | 
| 58 | 
            +
                    @schemas << ResoTransport::Schema.from_stream(args)
         | 
| 59 | 
            +
                  when "EntitySet"
         | 
| 60 | 
            +
                    @entity_sets << ResoTransport::EntitySet.from_stream(args)
         | 
| 61 | 
            +
                  when "EntityType"
         | 
| 62 | 
            +
                    @current_entity_type = ResoTransport::EntityType.from_stream(args)
         | 
| 63 | 
            +
                  when "ComplexType"
         | 
| 64 | 
            +
                    @current_complex_type = ResoTransport::EntityType.from_stream(args)
         | 
| 65 | 
            +
                  when "PropertyRef"
         | 
| 66 | 
            +
                    @current_entity_type.primary_key = args['Name']
         | 
| 67 | 
            +
                  when "Property"
         | 
| 68 | 
            +
                    @current_entity_type.properties << ResoTransport::Property.from_stream(args.merge(schema: @schemas.last)) if @current_entity_type
         | 
| 69 | 
            +
                    @current_complex_type.properties << ResoTransport::Property.from_stream(args.merge(schema: @schemas.last)) if @current_complex_type
         | 
| 70 | 
            +
                  when "NavigationProperty"
         | 
| 71 | 
            +
                    @current_entity_type.navigation_properties << ResoTransport::Property.from_stream(args)
         | 
| 72 | 
            +
                  when "EnumType"
         | 
| 73 | 
            +
                    @current_enum_type = ResoTransport::Enum.from_stream(args.merge(schema: @schemas.last))
         | 
| 74 | 
            +
                  when "Member"
         | 
| 75 | 
            +
                    @current_member = ResoTransport::Member.from_stream(args)
         | 
| 76 | 
            +
                  when "Annotation"
         | 
| 77 | 
            +
                    if @current_enum_type && @current_member
         | 
| 78 | 
            +
                      @current_member.annotation = args['String']
         | 
| 79 | 
            +
                    end
         | 
| 80 | 
            +
                  end
         | 
| 81 | 
            +
                rescue => e
         | 
| 82 | 
            +
                  puts e.inspect
         | 
| 83 | 
            +
                  puts "Error processing Tag: #{[name, args].inspect}"
         | 
| 84 | 
            +
                end
         | 
| 85 | 
            +
             | 
| 86 | 
            +
                def tag_end(name)
         | 
| 87 | 
            +
                  case name
         | 
| 88 | 
            +
                  when "EntityType"
         | 
| 89 | 
            +
                    @current_entity_type.schema = @schemas.last.namespace
         | 
| 90 | 
            +
                    @schemas.last.entity_types << @current_entity_type
         | 
| 91 | 
            +
                  when "ComplexType"
         | 
| 92 | 
            +
                    @current_complex_type.schema = @schemas.last.namespace
         | 
| 93 | 
            +
                    @schemas.last.complex_types << @current_complex_type
         | 
| 94 | 
            +
                  when "EnumType"
         | 
| 95 | 
            +
                    @enumerations << @current_enum_type
         | 
| 96 | 
            +
                    @current_enum_type = nil
         | 
| 97 | 
            +
                  when "Member"
         | 
| 98 | 
            +
                    @current_enum_type.members << @current_member
         | 
| 99 | 
            +
                    @current_member = nil
         | 
| 100 | 
            +
                  end
         | 
| 101 | 
            +
                end
         | 
| 102 | 
            +
             | 
| 103 | 
            +
              end
         | 
| 104 | 
            +
            end
         | 
| @@ -0,0 +1,101 @@ | |
| 1 | 
            +
            module ResoTransport
         | 
| 2 | 
            +
              Property = Struct.new(:name, :data_type, :attrs, :multi, :enum, :complex_type, :entity_type) do
         | 
| 3 | 
            +
             | 
| 4 | 
            +
                def self.from_stream(args)
         | 
| 5 | 
            +
                  new(args["Name"], args["Type"], args)
         | 
| 6 | 
            +
                end
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                def parse(value)
         | 
| 9 | 
            +
                  case value
         | 
| 10 | 
            +
                  when Array
         | 
| 11 | 
            +
                    value.map {|v| parser_object.parse_value(v) }
         | 
| 12 | 
            +
                  else
         | 
| 13 | 
            +
                    if multi
         | 
| 14 | 
            +
                      value.split(',').map(&:strip).map {|v| parser_object.parse_value(v) }
         | 
| 15 | 
            +
                    else
         | 
| 16 | 
            +
                      parser_object.parse_value(value)
         | 
| 17 | 
            +
                    end
         | 
| 18 | 
            +
                  end
         | 
| 19 | 
            +
                end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                def parse_value(value)
         | 
| 22 | 
            +
                  case data_type
         | 
| 23 | 
            +
                  when "Edm.DateTimeOffset"
         | 
| 24 | 
            +
                    DateTime.parse(value)
         | 
| 25 | 
            +
                  when "Edm.Date"
         | 
| 26 | 
            +
                    Date.parse(value)
         | 
| 27 | 
            +
                  else
         | 
| 28 | 
            +
                    value
         | 
| 29 | 
            +
                  end
         | 
| 30 | 
            +
                end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                def encode(value)
         | 
| 33 | 
            +
                  case value
         | 
| 34 | 
            +
                  when Array
         | 
| 35 | 
            +
                    value.map {|v| parser_object.encode_value(v) }
         | 
| 36 | 
            +
                  else
         | 
| 37 | 
            +
                    parser_object.encode_value(value)
         | 
| 38 | 
            +
                  end
         | 
| 39 | 
            +
                end
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                def encode_value(value)
         | 
| 42 | 
            +
                  case data_type
         | 
| 43 | 
            +
                  when "Edm.String"
         | 
| 44 | 
            +
                    "'#{value}'"
         | 
| 45 | 
            +
                  when "Edm.DateTimeOffset"
         | 
| 46 | 
            +
                    if value.respond_to?(:to_datetime)
         | 
| 47 | 
            +
                      value.to_datetime.strftime(ODATA_TIME_FORMAT)
         | 
| 48 | 
            +
                    else
         | 
| 49 | 
            +
                      DateTime.parse(value).strftime(ODATA_TIME_FORMAT)
         | 
| 50 | 
            +
                    end
         | 
| 51 | 
            +
                  else
         | 
| 52 | 
            +
                    value
         | 
| 53 | 
            +
                  end
         | 
| 54 | 
            +
                end
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                def parser_object
         | 
| 57 | 
            +
                  enum || complex_type || entity_type || self
         | 
| 58 | 
            +
                end
         | 
| 59 | 
            +
             | 
| 60 | 
            +
                def finalize_type(parser)
         | 
| 61 | 
            +
                  type_name, is_collection = case self.data_type
         | 
| 62 | 
            +
                  when /^Collection\((.*)\)$/ 
         | 
| 63 | 
            +
                    [$1, true]
         | 
| 64 | 
            +
                  when /^Edm\.(.*)$/
         | 
| 65 | 
            +
                    [$1, false]
         | 
| 66 | 
            +
                  else
         | 
| 67 | 
            +
                    [self.data_type, false]
         | 
| 68 | 
            +
                  end
         | 
| 69 | 
            +
             | 
| 70 | 
            +
                  if enum = parser.enumerations.detect {|e| e.name == type_name }
         | 
| 71 | 
            +
                    self.multi = is_collection || enum.is_flags
         | 
| 72 | 
            +
                    self.enum = enum
         | 
| 73 | 
            +
                  end
         | 
| 74 | 
            +
             | 
| 75 | 
            +
                  schema_name, collection_name = ResoTransport.split_schema_and_class_name(type_name)
         | 
| 76 | 
            +
                  if schema = parser.schemas.detect {|e| e.namespace == schema_name }
         | 
| 77 | 
            +
                    if complex_type = schema.complex_types.detect {|c| c.name == collection_name }
         | 
| 78 | 
            +
                      self.multi = is_collection
         | 
| 79 | 
            +
                      self.complex_type = complex_type
         | 
| 80 | 
            +
                    end
         | 
| 81 | 
            +
             | 
| 82 | 
            +
                    if entity_type = schema.entity_types.detect {|et| et.name == collection_name }
         | 
| 83 | 
            +
                      self.multi = is_collection
         | 
| 84 | 
            +
                      self.entity_type = entity_type
         | 
| 85 | 
            +
                    end
         | 
| 86 | 
            +
                  end
         | 
| 87 | 
            +
             | 
| 88 | 
            +
                end
         | 
| 89 | 
            +
             | 
| 90 | 
            +
                def method_missing(name, *args, &block)
         | 
| 91 | 
            +
                  self.attrs[name] || self.attrs[camelize(name)] || super
         | 
| 92 | 
            +
                end
         | 
| 93 | 
            +
             | 
| 94 | 
            +
                private
         | 
| 95 | 
            +
             | 
| 96 | 
            +
                def camelize(name)
         | 
| 97 | 
            +
                  name.to_s.split("_").map(&:capitalize).join
         | 
| 98 | 
            +
                end
         | 
| 99 | 
            +
             | 
| 100 | 
            +
              end
         | 
| 101 | 
            +
            end
         | 
| @@ -0,0 +1,147 @@ | |
| 1 | 
            +
            module ResoTransport
         | 
| 2 | 
            +
              Query = Struct.new(:resource) do
         | 
| 3 | 
            +
             | 
| 4 | 
            +
                def all(&block)
         | 
| 5 | 
            +
                  new_query_context('and')
         | 
| 6 | 
            +
                  instance_eval(&block)
         | 
| 7 | 
            +
                  clear_query_context
         | 
| 8 | 
            +
                  return self
         | 
| 9 | 
            +
                end
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                def any(&block)
         | 
| 12 | 
            +
                  new_query_context('or')
         | 
| 13 | 
            +
                  instance_eval(&block)
         | 
| 14 | 
            +
                  clear_query_context
         | 
| 15 | 
            +
                  return self
         | 
| 16 | 
            +
                end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                [:eq, :ne, :gt, :ge, :lt, :le].each do |op|
         | 
| 19 | 
            +
                  define_method(op) do |conditions|
         | 
| 20 | 
            +
                    conditions.each_pair do |k,v|
         | 
| 21 | 
            +
                      current_query_context << "#{k} #{op} #{encode_value(k, v)}"
         | 
| 22 | 
            +
                    end
         | 
| 23 | 
            +
                    return self
         | 
| 24 | 
            +
                  end
         | 
| 25 | 
            +
                end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                def limit(size)
         | 
| 28 | 
            +
                  options[:top] = size
         | 
| 29 | 
            +
                  return self
         | 
| 30 | 
            +
                end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                def offset(size)
         | 
| 33 | 
            +
                  options[:skip] = size
         | 
| 34 | 
            +
                  return self
         | 
| 35 | 
            +
                end
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                def order(field, dir=nil)
         | 
| 38 | 
            +
                  options[:orderby] = [field, dir].join(" ").strip
         | 
| 39 | 
            +
                  return self
         | 
| 40 | 
            +
                end
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                def include_count
         | 
| 43 | 
            +
                  options[:count] = true
         | 
| 44 | 
            +
                  return self
         | 
| 45 | 
            +
                end
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                def select(*fields)
         | 
| 48 | 
            +
                  os = options.fetch(:select, "").split(",")
         | 
| 49 | 
            +
                  options[:select] = (os + Array(fields)).uniq.join(",")
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                  return self
         | 
| 52 | 
            +
                end
         | 
| 53 | 
            +
             | 
| 54 | 
            +
                def expand(*names)
         | 
| 55 | 
            +
                  ex = options.fetch(:expand, "").split(",")
         | 
| 56 | 
            +
                  options[:expand] = (ex + Array(names)).uniq.join(",")
         | 
| 57 | 
            +
             | 
| 58 | 
            +
                  return self
         | 
| 59 | 
            +
                end
         | 
| 60 | 
            +
             | 
| 61 | 
            +
                def results
         | 
| 62 | 
            +
                  resp = execute
         | 
| 63 | 
            +
             | 
| 64 | 
            +
                  if resp[:success]
         | 
| 65 | 
            +
                    resp[:results]
         | 
| 66 | 
            +
                  else
         | 
| 67 | 
            +
                    puts resp[:meta]
         | 
| 68 | 
            +
                    raise "Request Failed"
         | 
| 69 | 
            +
                  end
         | 
| 70 | 
            +
                end
         | 
| 71 | 
            +
             | 
| 72 | 
            +
                def execute
         | 
| 73 | 
            +
                  resp = resource.get(compile_params)
         | 
| 74 | 
            +
                  parsed_body = JSON.parse(resp.body)
         | 
| 75 | 
            +
                  results = Array(parsed_body.delete("value"))
         | 
| 76 | 
            +
             | 
| 77 | 
            +
                  {
         | 
| 78 | 
            +
                    success: resp.success? && !parsed_body.has_key?("error"),
         | 
| 79 | 
            +
                    meta: parsed_body,
         | 
| 80 | 
            +
                    results: resource.parse(results)
         | 
| 81 | 
            +
                  }
         | 
| 82 | 
            +
                end
         | 
| 83 | 
            +
             | 
| 84 | 
            +
                def new_query_context(context)
         | 
| 85 | 
            +
                  @last_query_context ||= 0
         | 
| 86 | 
            +
                  @current_query_context = @last_query_context + 1
         | 
| 87 | 
            +
                  sub_queries[@current_query_context][:context] = context
         | 
| 88 | 
            +
                end
         | 
| 89 | 
            +
             | 
| 90 | 
            +
                def clear_query_context
         | 
| 91 | 
            +
                  @last_query_context = @current_query_context
         | 
| 92 | 
            +
                  @current_query_context = nil
         | 
| 93 | 
            +
                end
         | 
| 94 | 
            +
             | 
| 95 | 
            +
                def current_query_context
         | 
| 96 | 
            +
                  @current_query_context ||= nil
         | 
| 97 | 
            +
                  sub_queries[@current_query_context || :global][:criteria]
         | 
| 98 | 
            +
                end
         | 
| 99 | 
            +
             | 
| 100 | 
            +
                def options
         | 
| 101 | 
            +
                  @options ||= {}
         | 
| 102 | 
            +
                end
         | 
| 103 | 
            +
             | 
| 104 | 
            +
                def sub_queries
         | 
| 105 | 
            +
                  @sub_queries ||= Hash.new {|h,k| h[k] = { context: 'and', criteria: [] } }
         | 
| 106 | 
            +
                end
         | 
| 107 | 
            +
             | 
| 108 | 
            +
                def compile_filters
         | 
| 109 | 
            +
                  groups = sub_queries.dup
         | 
| 110 | 
            +
                  global = groups.delete(:global)
         | 
| 111 | 
            +
                  filter_groups = groups.values
         | 
| 112 | 
            +
             | 
| 113 | 
            +
                  filter_string = ""
         | 
| 114 | 
            +
             | 
| 115 | 
            +
                  if global && global[:criteria]&.any?
         | 
| 116 | 
            +
                    filter_string << global[:criteria].join(" #{global[:context]} ")  
         | 
| 117 | 
            +
                  end
         | 
| 118 | 
            +
             | 
| 119 | 
            +
                  filter_string << filter_groups.map do |g|
         | 
| 120 | 
            +
                    "(#{g[:criteria].join(" #{g[:context]} ")})"
         | 
| 121 | 
            +
                  end.join(" and ")
         | 
| 122 | 
            +
             | 
| 123 | 
            +
                  filter_string
         | 
| 124 | 
            +
                end
         | 
| 125 | 
            +
             | 
| 126 | 
            +
                def compile_params
         | 
| 127 | 
            +
                  params = {}
         | 
| 128 | 
            +
             | 
| 129 | 
            +
                  options.each_pair do |k,v|
         | 
| 130 | 
            +
                    params["$#{k}"] = v
         | 
| 131 | 
            +
                  end
         | 
| 132 | 
            +
             | 
| 133 | 
            +
                  if !sub_queries.empty?
         | 
| 134 | 
            +
                    params["$filter"] = compile_filters
         | 
| 135 | 
            +
                  end
         | 
| 136 | 
            +
             | 
| 137 | 
            +
                  params
         | 
| 138 | 
            +
                end
         | 
| 139 | 
            +
             | 
| 140 | 
            +
                def encode_value(key, v)
         | 
| 141 | 
            +
                  field = resource.property(key.to_s)
         | 
| 142 | 
            +
                  raise "Couldn't find property #{key} for #{resource.name}" if field.nil?
         | 
| 143 | 
            +
                  field.encode(v)
         | 
| 144 | 
            +
                end
         | 
| 145 | 
            +
             | 
| 146 | 
            +
              end
         | 
| 147 | 
            +
            end
         | 
| @@ -0,0 +1,55 @@ | |
| 1 | 
            +
            module ResoTransport
         | 
| 2 | 
            +
              Resource = Struct.new(:client, :entity_set) do
         | 
| 3 | 
            +
             | 
| 4 | 
            +
                def query
         | 
| 5 | 
            +
                  Query.new(self)
         | 
| 6 | 
            +
                end
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                def name
         | 
| 9 | 
            +
                  entity_set.name
         | 
| 10 | 
            +
                end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                def property(name)
         | 
| 13 | 
            +
                  properties.detect {|p| p.name == name }
         | 
| 14 | 
            +
                end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                def properties
         | 
| 17 | 
            +
                  entity_type.properties
         | 
| 18 | 
            +
                end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                def expandable
         | 
| 21 | 
            +
                  entity_type.navigation_properties
         | 
| 22 | 
            +
                end
         | 
| 23 | 
            +
                
         | 
| 24 | 
            +
                def entity_type
         | 
| 25 | 
            +
                  @entity_type ||= schema.entity_types.detect {|et| et.name == entity_set.entity_type }
         | 
| 26 | 
            +
                end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                def schema
         | 
| 29 | 
            +
                  @schema ||= md.schemas.detect {|s| s.namespace == entity_set.schema }
         | 
| 30 | 
            +
                end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                def md
         | 
| 33 | 
            +
                  client.metadata
         | 
| 34 | 
            +
                end
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                def parse(results)
         | 
| 37 | 
            +
                  results.map {|r| entity_type.parse(r) }
         | 
| 38 | 
            +
                end
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                def get(params)
         | 
| 41 | 
            +
                  client.connection.get(name, params) do |req|
         | 
| 42 | 
            +
                    req.headers['Accept'] = 'application/json'
         | 
| 43 | 
            +
                  end
         | 
| 44 | 
            +
                end
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                def to_s
         | 
| 47 | 
            +
                  %(#<ResoTransport::Resource entity_set="#{name}", schema="#{schema&.namespace}">)
         | 
| 48 | 
            +
                end
         | 
| 49 | 
            +
             | 
| 50 | 
            +
                def inspect
         | 
| 51 | 
            +
                  to_s
         | 
| 52 | 
            +
                end
         | 
| 53 | 
            +
             | 
| 54 | 
            +
              end
         | 
| 55 | 
            +
            end
         | 
| @@ -0,0 +1,21 @@ | |
| 1 | 
            +
            module ResoTransport
         | 
| 2 | 
            +
              Schema = Struct.new(:namespace) do
         | 
| 3 | 
            +
             | 
| 4 | 
            +
                def self.from_stream(args)
         | 
| 5 | 
            +
                  new(args["Namespace"])
         | 
| 6 | 
            +
                end
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                def entity_types
         | 
| 9 | 
            +
                  @entity_types ||= []
         | 
| 10 | 
            +
                end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                def complex_types
         | 
| 13 | 
            +
                  @complex_types ||= []
         | 
| 14 | 
            +
                end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                def enumerations
         | 
| 17 | 
            +
                  @enumerations ||= []
         | 
| 18 | 
            +
                end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
              end
         | 
| 21 | 
            +
            end
         | 
| @@ -0,0 +1,57 @@ | |
| 1 | 
            +
            require 'rexml/document'
         | 
| 2 | 
            +
            require 'rexml/streamlistener'
         | 
| 3 | 
            +
            require 'logger'
         | 
| 4 | 
            +
            require 'faraday'
         | 
| 5 | 
            +
            require 'json'
         | 
| 6 | 
            +
            require 'time'
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            require "reso_transport/version"
         | 
| 9 | 
            +
            require "reso_transport/configuration"
         | 
| 10 | 
            +
            require "reso_transport/authentication"
         | 
| 11 | 
            +
            require "reso_transport/client"
         | 
| 12 | 
            +
            require "reso_transport/resource"
         | 
| 13 | 
            +
            require "reso_transport/metadata"
         | 
| 14 | 
            +
            require "reso_transport/metadata_parser"
         | 
| 15 | 
            +
            require "reso_transport/schema"
         | 
| 16 | 
            +
            require "reso_transport/entity_set"
         | 
| 17 | 
            +
            require "reso_transport/entity_type"
         | 
| 18 | 
            +
            require "reso_transport/enum"
         | 
| 19 | 
            +
            require "reso_transport/property"
         | 
| 20 | 
            +
            require "reso_transport/query"
         | 
| 21 | 
            +
             | 
| 22 | 
            +
             | 
| 23 | 
            +
             | 
| 24 | 
            +
            module Faraday
         | 
| 25 | 
            +
              module Utils
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                def escape(str)
         | 
| 28 | 
            +
                  str.to_s.gsub(ESCAPE_RE) do |match|
         | 
| 29 | 
            +
                    '%' + match.unpack('H2' * match.bytesize).join('%').upcase
         | 
| 30 | 
            +
                  end.gsub(" ","%20")
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                end
         | 
| 33 | 
            +
              end
         | 
| 34 | 
            +
            end
         | 
| 35 | 
            +
             | 
| 36 | 
            +
            module ResoTransport
         | 
| 37 | 
            +
              class Error < StandardError; end
         | 
| 38 | 
            +
              class AccessDenied < StandardError; end
         | 
| 39 | 
            +
              ODATA_TIME_FORMAT = "%Y-%m-%dT%H:%M:%S%Z"
         | 
| 40 | 
            +
             | 
| 41 | 
            +
              class << self
         | 
| 42 | 
            +
                attr_writer :configuration
         | 
| 43 | 
            +
              end
         | 
| 44 | 
            +
             | 
| 45 | 
            +
              def self.configuration
         | 
| 46 | 
            +
                @configuration ||= Configuration.new
         | 
| 47 | 
            +
              end
         | 
| 48 | 
            +
             | 
| 49 | 
            +
              def self.configure
         | 
| 50 | 
            +
                yield(configuration)
         | 
| 51 | 
            +
              end
         | 
| 52 | 
            +
             | 
| 53 | 
            +
              def self.split_schema_and_class_name(s)
         | 
| 54 | 
            +
                s.partition(/(\w+)$/).first(2).map {|s| s.sub(/\.$/, '') }
         | 
| 55 | 
            +
              end
         | 
| 56 | 
            +
             | 
| 57 | 
            +
            end
         | 
| @@ -0,0 +1,35 @@ | |
| 1 | 
            +
             | 
| 2 | 
            +
            lib = File.expand_path("../lib", __FILE__)
         | 
| 3 | 
            +
            $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
         | 
| 4 | 
            +
            require "reso_transport/version"
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            Gem::Specification.new do |spec|
         | 
| 7 | 
            +
              spec.name          = "reso_transport"
         | 
| 8 | 
            +
              spec.version       = ResoTransport::VERSION
         | 
| 9 | 
            +
              spec.authors       = ["Jon Druse"]
         | 
| 10 | 
            +
              spec.email         = ["jon@wrstudios.com"]
         | 
| 11 | 
            +
             | 
| 12 | 
            +
              spec.summary       = "A utility for consuming RESO Web API connections"
         | 
| 13 | 
            +
              spec.description   = "Supports Trestle, Spark, Bridge Interactive, MLS Grid"
         | 
| 14 | 
            +
              spec.homepage      = "http://github.com/wrstudios/reso_transport"
         | 
| 15 | 
            +
              spec.license       = "MIT"
         | 
| 16 | 
            +
             | 
| 17 | 
            +
             | 
| 18 | 
            +
              # Specify which files should be added to the gem when it is released.
         | 
| 19 | 
            +
              # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
         | 
| 20 | 
            +
              spec.files         = Dir.chdir(File.expand_path('..', __FILE__)) do
         | 
| 21 | 
            +
                `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
         | 
| 22 | 
            +
              end
         | 
| 23 | 
            +
              spec.bindir        = "exe"
         | 
| 24 | 
            +
              spec.executables   = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
         | 
| 25 | 
            +
              spec.require_paths = ["lib"]
         | 
| 26 | 
            +
             | 
| 27 | 
            +
              spec.add_dependency "faraday", "~> 0.17.0"
         | 
| 28 | 
            +
             | 
| 29 | 
            +
              spec.add_development_dependency "bundler", "~> 1.17"
         | 
| 30 | 
            +
              spec.add_development_dependency "rake", "~> 10.0"
         | 
| 31 | 
            +
              spec.add_development_dependency "minitest", "~> 5.0"
         | 
| 32 | 
            +
              spec.add_development_dependency "minitest-rg", "~> 5.0"
         | 
| 33 | 
            +
              spec.add_development_dependency "vcr", "~> 5.0"
         | 
| 34 | 
            +
              spec.add_development_dependency "byebug"
         | 
| 35 | 
            +
            end
         | 
    
        data/sample_secrets.yml
    ADDED
    
    | @@ -0,0 +1,26 @@ | |
| 1 | 
            +
            :bridge:
         | 
| 2 | 
            +
              :md_file: PATH_TO_METADATA_FILE
         | 
| 3 | 
            +
              :vendor:
         | 
| 4 | 
            +
                :name: bridge_interactive
         | 
| 5 | 
            +
              :endpoint: ENDPOINT_URL_HERE
         | 
| 6 | 
            +
              :authentication:
         | 
| 7 | 
            +
                :access_token: ACCESS_TOKEN_HERE
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            :trestle:
         | 
| 10 | 
            +
              :md_file: PATH_TO_METADATA_FILE
         | 
| 11 | 
            +
              :vendor:
         | 
| 12 | 
            +
                :name: trestle
         | 
| 13 | 
            +
              :endpoint: ENDPOINT_URL_HERE
         | 
| 14 | 
            +
              :authentication:
         | 
| 15 | 
            +
                :endpoint: AUTH_ENDPOINT_HERE
         | 
| 16 | 
            +
                :client_id: CLIENT_ID_HERE
         | 
| 17 | 
            +
                :client_secret: CLIENT_SECRET_HERE
         | 
| 18 | 
            +
             | 
| 19 | 
            +
            :spark:
         | 
| 20 | 
            +
              :md_file: PATH_TO_METADATA_FILE
         | 
| 21 | 
            +
              :vendor: 
         | 
| 22 | 
            +
                :name: fbs
         | 
| 23 | 
            +
              :endpoint: ENDPOINT_URL_HERE
         | 
| 24 | 
            +
              :authentication:
         | 
| 25 | 
            +
                :access_token: ACCESS_TOKEN_HERE
         | 
| 26 | 
            +
             |