safrano 0.4.0 → 0.4.5
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 +4 -4
- data/lib/core_ext/Dir/iter.rb +18 -0
- data/lib/core_ext/Hash/transform.rb +21 -0
- data/lib/core_ext/Integer/edm.rb +13 -0
- data/lib/core_ext/REXML/Document/output.rb +16 -0
- data/lib/core_ext/String/convert.rb +25 -0
- data/lib/core_ext/String/edm.rb +13 -0
- data/lib/core_ext/dir.rb +3 -0
- data/lib/core_ext/hash.rb +3 -0
- data/lib/core_ext/integer.rb +3 -0
- data/lib/core_ext/rexml.rb +3 -0
- data/lib/core_ext/string.rb +5 -0
- data/lib/odata/attribute.rb +15 -10
- data/lib/odata/batch.rb +15 -13
- data/lib/odata/collection.rb +144 -535
- data/lib/odata/collection_filter.rb +47 -40
- data/lib/odata/collection_media.rb +145 -74
- data/lib/odata/collection_order.rb +50 -37
- data/lib/odata/common_logger.rb +36 -34
- data/lib/odata/complex_type.rb +152 -0
- data/lib/odata/edm/primitive_types.rb +184 -0
- data/lib/odata/entity.rb +151 -197
- data/lib/odata/error.rb +175 -32
- data/lib/odata/expand.rb +126 -0
- data/lib/odata/filter/base.rb +74 -0
- data/lib/odata/filter/error.rb +49 -6
- data/lib/odata/filter/parse.rb +44 -36
- data/lib/odata/filter/sequel.rb +136 -67
- data/lib/odata/filter/sequel_function_adapter.rb +148 -0
- data/lib/odata/filter/token.rb +26 -19
- data/lib/odata/filter/tree.rb +113 -63
- data/lib/odata/function_import.rb +168 -0
- data/lib/odata/model_ext.rb +637 -0
- data/lib/odata/navigation_attribute.rb +44 -61
- data/lib/odata/relations.rb +5 -5
- data/lib/odata/select.rb +54 -0
- data/lib/odata/transition.rb +71 -0
- data/lib/odata/url_parameters.rb +128 -37
- data/lib/odata/walker.rb +19 -11
- data/lib/safrano.rb +17 -37
- data/lib/safrano/contract.rb +143 -0
- data/lib/safrano/core.rb +29 -104
- data/lib/safrano/core_ext.rb +13 -0
- data/lib/safrano/deprecation.rb +73 -0
- data/lib/safrano/multipart.rb +39 -43
- data/lib/safrano/rack_app.rb +68 -67
- data/lib/safrano/{odata_rack_builder.rb → rack_builder.rb} +18 -2
- data/lib/safrano/request.rb +102 -51
- data/lib/safrano/response.rb +5 -3
- data/lib/safrano/sequel_join_by_paths.rb +2 -2
- data/lib/safrano/service.rb +264 -220
- data/lib/safrano/version.rb +3 -1
- data/lib/sequel/plugins/join_by_paths.rb +17 -29
- metadata +34 -12
| @@ -1,33 +1,31 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 1 3 | 
             
            require 'json'
         | 
| 2 | 
            -
            require_relative '../safrano/core | 
| 3 | 
            -
            require_relative './entity | 
| 4 | 
            +
            require_relative '../safrano/core'
         | 
| 5 | 
            +
            require_relative './entity'
         | 
| 4 6 |  | 
| 5 | 
            -
            module  | 
| 6 | 
            -
              
         | 
| 7 | 
            -
              # remove the relation between entity and parent by clearing 
         | 
| 7 | 
            +
            module Safrano
         | 
| 8 | 
            +
              # remove the relation between entity and parent by clearing
         | 
| 8 9 | 
             
              # the FK field(s) (if allowed)
         | 
| 9 | 
            -
              def  | 
| 10 | 
            +
              def self.remove_nav_relation(assoc, parent)
         | 
| 10 11 | 
             
                return unless assoc
         | 
| 11 12 |  | 
| 12 | 
            -
                 | 
| 13 | 
            -
             | 
| 14 | 
            -
             | 
| 15 | 
            -
             | 
| 16 | 
            -
             | 
| 17 | 
            -
             | 
| 18 | 
            -
             | 
| 19 | 
            -
             | 
| 20 | 
            -
             | 
| 21 | 
            -
             | 
| 22 | 
            -
             | 
| 23 | 
            -
                    }
         | 
| 24 | 
            -
                  
         | 
| 25 | 
            -
                end  
         | 
| 13 | 
            +
                return unless assoc[:type] == :many_to_one
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                # removes/clear the FK values in parent
         | 
| 16 | 
            +
                # thus deleting the "link" between the entity and the parent
         | 
| 17 | 
            +
                # Note: This is called if we have to delete the child--> can only be
         | 
| 18 | 
            +
                # done after removing the FK in parent (if allowed!)
         | 
| 19 | 
            +
                lks = [assoc[:key]].flatten
         | 
| 20 | 
            +
                lks.each do |lk|
         | 
| 21 | 
            +
                  parent.set(lk => nil)
         | 
| 22 | 
            +
                  parent.save(transaction: false)
         | 
| 23 | 
            +
                end
         | 
| 26 24 | 
             
              end
         | 
| 27 | 
            -
             | 
| 25 | 
            +
             | 
| 28 26 | 
             
              # link newly created entities(child) to an existing parent
         | 
| 29 27 | 
             
              # by following the association_reflection rules
         | 
| 30 | 
            -
              def  | 
| 28 | 
            +
              def self.create_nav_relation(child, assoc, parent)
         | 
| 31 29 | 
             
                return unless assoc
         | 
| 32 30 |  | 
| 33 31 | 
             
                # Note: this coding shares some bits from our sequel/plugins/join_by_paths,
         | 
| @@ -46,16 +44,16 @@ module OData | |
| 46 44 | 
             
                  lks = [leftm.primary_key].flatten
         | 
| 47 45 | 
             
                  rks = [assoc[:key]].flatten
         | 
| 48 46 | 
             
                  join_cond = rks.zip(lks).to_h
         | 
| 49 | 
            -
                  join_cond.each  | 
| 47 | 
            +
                  join_cond.each do |rk, lk|
         | 
| 50 48 | 
             
                    if child.values[rk] # FK in new entity from payload not nil, only check consistency
         | 
| 51 49 | 
             
                      # with the parent - id(s)
         | 
| 52 | 
            -
                      if (child.values[rk] != parent.pk_hash[lk]) # error...
         | 
| 53 | 
            -
             | 
| 54 | 
            -
                      end
         | 
| 50 | 
            +
                      # if (child.values[rk] != parent.pk_hash[lk]) # error...
         | 
| 51 | 
            +
                      # TODO
         | 
| 52 | 
            +
                      # end
         | 
| 55 53 | 
             
                    else # we can set the FK value, thus creating the "link"
         | 
| 56 54 | 
             
                      child.set(rk => parent.pk_hash[lk])
         | 
| 57 55 | 
             
                    end
         | 
| 58 | 
            -
                   | 
| 56 | 
            +
                  end
         | 
| 59 57 | 
             
                when :many_to_one
         | 
| 60 58 | 
             
                  # sets the FK values in parent to corresponding related child key-values
         | 
| 61 59 | 
             
                  # thus creating the "link" between the new entity and the parent
         | 
| @@ -64,29 +62,15 @@ module OData | |
| 64 62 | 
             
                  lks = [assoc[:key]].flatten
         | 
| 65 63 | 
             
                  rks = [child.class.primary_key].flatten
         | 
| 66 64 | 
             
                  join_cond = rks.zip(lks).to_h
         | 
| 67 | 
            -
                  join_cond.each  | 
| 65 | 
            +
                  join_cond.each do |rk, lk|
         | 
| 68 66 | 
             
                    if parent.values[lk] # FK in parent not nil, only check consistency
         | 
| 69 67 | 
             
                      # with the child - id(s)
         | 
| 70 | 
            -
                      if  | 
| 71 | 
            -
             | 
| 72 | 
            -
                      end
         | 
| 68 | 
            +
                      # if parent.values[lk] != child.pk_hash[rk] # error...
         | 
| 69 | 
            +
                      # TODO
         | 
| 70 | 
            +
                      # end
         | 
| 73 71 | 
             
                    else # we can set the FK value, thus creating the "link"
         | 
| 74 72 | 
             
                      parent.set(lk => child.pk_hash[rk])
         | 
| 75 73 | 
             
                    end
         | 
| 76 | 
            -
                  }
         | 
| 77 | 
            -
                end
         | 
| 78 | 
            -
              end
         | 
| 79 | 
            -
             | 
| 80 | 
            -
              module EntityBase
         | 
| 81 | 
            -
                module NavigationInfo
         | 
| 82 | 
            -
                  attr_reader :nav_parent
         | 
| 83 | 
            -
                  attr_reader :navattr_reflection
         | 
| 84 | 
            -
                  attr_reader :nav_name
         | 
| 85 | 
            -
                  def set_relation_info(parent,name)
         | 
| 86 | 
            -
                    @nav_parent = parent
         | 
| 87 | 
            -
                    @nav_name = name
         | 
| 88 | 
            -
                    @navattr_reflection = parent.class.association_reflections[name.to_sym]
         | 
| 89 | 
            -
                    @nav_klass = @navattr_reflection[:class_name].constantize
         | 
| 90 74 | 
             
                  end
         | 
| 91 75 | 
             
                end
         | 
| 92 76 | 
             
              end
         | 
| @@ -94,12 +78,12 @@ module OData | |
| 94 78 | 
             
              # Represents a named but nil-valued navigation-attribute of an Entity
         | 
| 95 79 | 
             
              # (usually resulting from a NULL FK db value)
         | 
| 96 80 | 
             
              class NilNavigationAttribute
         | 
| 97 | 
            -
                include  | 
| 81 | 
            +
                include Safrano::NavigationInfo
         | 
| 98 82 | 
             
                def odata_get(req)
         | 
| 99 83 | 
             
                  if req.walker.media_value
         | 
| 100 | 
            -
                     | 
| 84 | 
            +
                    Safrano::ErrorNotFound.odata_get
         | 
| 101 85 | 
             
                  elsif req.accept?(APPJSON)
         | 
| 102 | 
            -
                    [200,  | 
| 86 | 
            +
                    [200, EMPTY_HASH, to_odata_json(service: req.service)]
         | 
| 103 87 | 
             
                  else # TODO: other formats
         | 
| 104 88 | 
             
                    415
         | 
| 105 89 | 
             
                  end
         | 
| @@ -108,25 +92,22 @@ module OData | |
| 108 92 | 
             
                # create the nav. entity
         | 
| 109 93 | 
             
                def odata_post(req)
         | 
| 110 94 | 
             
                  # delegate to the class method
         | 
| 111 | 
            -
                  @nav_klass.odata_create_entity_and_relation(req, | 
| 112 | 
            -
                                                              @navattr_reflection, | 
| 95 | 
            +
                  @nav_klass.odata_create_entity_and_relation(req,
         | 
| 96 | 
            +
                                                              @navattr_reflection,
         | 
| 113 97 | 
             
                                                              @nav_parent)
         | 
| 114 98 | 
             
                end
         | 
| 115 99 |  | 
| 116 100 | 
             
                # create the nav. entity
         | 
| 117 101 | 
             
                def odata_put(req)
         | 
| 118 | 
            -
            # | 
| 119 | 
            -
             | 
| 120 | 
            -
             | 
| 121 | 
            -
             | 
| 122 | 
            -
                                                                @nav_parent)
         | 
| 123 | 
            -
            #      else
         | 
| 124 | 
            -
            #      end
         | 
| 102 | 
            +
                  # delegate to the class method
         | 
| 103 | 
            +
                  @nav_klass.odata_create_entity_and_relation(req,
         | 
| 104 | 
            +
                                                              @navattr_reflection,
         | 
| 105 | 
            +
                                                              @nav_parent)
         | 
| 125 106 | 
             
                end
         | 
| 126 107 |  | 
| 127 108 | 
             
                # empty output as OData json (v2)
         | 
| 128 109 | 
             
                def to_odata_json(*)
         | 
| 129 | 
            -
                  { 'd' =>  | 
| 110 | 
            +
                  { 'd' => EMPTY_HASH }.to_json
         | 
| 130 111 | 
             
                end
         | 
| 131 112 |  | 
| 132 113 | 
             
                # for testing purpose (assert_equal ...)
         | 
| @@ -137,16 +118,18 @@ module OData | |
| 137 118 | 
             
                # methods related to transitions to next state (cf. walker)
         | 
| 138 119 | 
             
                module Transitions
         | 
| 139 120 | 
             
                  def transition_end(_match_result)
         | 
| 140 | 
            -
                     | 
| 121 | 
            +
                    Safrano::Transition::RESULT_END
         | 
| 141 122 | 
             
                  end
         | 
| 142 123 |  | 
| 143 124 | 
             
                  def transition_value(_match_result)
         | 
| 144 125 | 
             
                    [self, :end_with_value]
         | 
| 145 126 | 
             
                  end
         | 
| 146 127 |  | 
| 128 | 
            +
                  ALLOWED_TRANSITIONS = [Safrano::TransitionEnd,
         | 
| 129 | 
            +
                                         Safrano::TransitionValue].freeze
         | 
| 130 | 
            +
             | 
| 147 131 | 
             
                  def allowed_transitions
         | 
| 148 | 
            -
                     | 
| 149 | 
            -
                     Safrano::TransitionValue]
         | 
| 132 | 
            +
                    ALLOWED_TRANSITIONS
         | 
| 150 133 | 
             
                  end
         | 
| 151 134 | 
             
                end
         | 
| 152 135 | 
             
                include Transitions
         | 
    
        data/lib/odata/relations.rb
    CHANGED
    
    | @@ -1,9 +1,9 @@ | |
| 1 | 
            -
             | 
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 3 | 
             
            require 'set'
         | 
| 4 4 |  | 
| 5 5 | 
             
            # OData relation related classes/module
         | 
| 6 | 
            -
            module  | 
| 6 | 
            +
            module Safrano
         | 
| 7 7 | 
             
              # we represent a relation as a Set (unordered) of two end elements
         | 
| 8 8 | 
             
              class Relation < Set
         | 
| 9 9 | 
             
                #    attr_reader :rid
         | 
| @@ -20,7 +20,7 @@ module OData | |
| 20 20 |  | 
| 21 21 | 
             
                # we need a from/to order independant ID
         | 
| 22 22 | 
             
                def rid
         | 
| 23 | 
            -
                   | 
| 23 | 
            +
                  Safrano::RelationManager.build_id(self)
         | 
| 24 24 | 
             
                end
         | 
| 25 25 |  | 
| 26 26 | 
             
                # we need a from/to order independant OData like name
         | 
| @@ -76,11 +76,11 @@ module OData | |
| 76 76 | 
             
                end
         | 
| 77 77 |  | 
| 78 78 | 
             
                def get(arg)
         | 
| 79 | 
            -
                  rid =  | 
| 79 | 
            +
                  rid = Safrano::RelationManager.build_id(arg)
         | 
| 80 80 | 
             
                  if @list.key?(rid)
         | 
| 81 81 | 
             
                    @list[rid]
         | 
| 82 82 | 
             
                  else
         | 
| 83 | 
            -
                    rel =  | 
| 83 | 
            +
                    rel = Safrano::Relation.new(arg)
         | 
| 84 84 | 
             
                    @list[rid] = rel
         | 
| 85 85 | 
             
                  end
         | 
| 86 86 | 
             
                end
         | 
    
        data/lib/odata/select.rb
    ADDED
    
    | @@ -0,0 +1,54 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'odata/error'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            # all dataset selecting related classes in our OData module
         | 
| 6 | 
            +
            # ie  do eager loading
         | 
| 7 | 
            +
            module Safrano
         | 
| 8 | 
            +
              # base class for selecting. We have to distinguish between
         | 
| 9 | 
            +
              # fields of the current entity, and the navigation properties
         | 
| 10 | 
            +
              # we can have one special case
         | 
| 11 | 
            +
              #  empty, ie no $select specified --> return all fields and all nav props
         | 
| 12 | 
            +
              #            ==> SelectAll
         | 
| 13 | 
            +
             | 
| 14 | 
            +
              class SelectBase
         | 
| 15 | 
            +
                ALL = new # re-useable selecting-all handler
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                def self.factory(selectstr, model)
         | 
| 18 | 
            +
                  case selectstr&.strip
         | 
| 19 | 
            +
                  when nil, '', '*'
         | 
| 20 | 
            +
                    ALL
         | 
| 21 | 
            +
                  else
         | 
| 22 | 
            +
                    Select.new(selectstr, model)
         | 
| 23 | 
            +
                  end
         | 
| 24 | 
            +
                end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                def all_props?
         | 
| 27 | 
            +
                  false
         | 
| 28 | 
            +
                end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                def ALL.all_props?
         | 
| 31 | 
            +
                  true
         | 
| 32 | 
            +
                end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                def parse_error?
         | 
| 35 | 
            +
                  Contract::OK
         | 
| 36 | 
            +
                end
         | 
| 37 | 
            +
              end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
              # single select
         | 
| 40 | 
            +
              class Select < SelectBase
         | 
| 41 | 
            +
                COMASPLIT = /\s*,\s*/.freeze
         | 
| 42 | 
            +
                attr_reader :props
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                def initialize(selstr, model)
         | 
| 45 | 
            +
                  @model = model
         | 
| 46 | 
            +
                  @selectp = selstr.strip
         | 
| 47 | 
            +
                  @props = @selectp.split(COMASPLIT)
         | 
| 48 | 
            +
                end
         | 
| 49 | 
            +
             | 
| 50 | 
            +
                def parse_error?
         | 
| 51 | 
            +
                  (invalids = @model.find_invalid_props(props.to_set)) ? BadRequestSelectInvalidProps.new(@model, invalids) : Contract::OK
         | 
| 52 | 
            +
                end
         | 
| 53 | 
            +
              end
         | 
| 54 | 
            +
            end
         | 
| @@ -0,0 +1,71 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require_relative 'error'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            # our main namespace
         | 
| 6 | 
            +
            module Safrano
         | 
| 7 | 
            +
              # represents a state transition when navigating/parsing the url path
         | 
| 8 | 
            +
              # from left to right
         | 
| 9 | 
            +
              class Transition < Regexp
         | 
| 10 | 
            +
                attr_accessor :trans
         | 
| 11 | 
            +
                attr_accessor :match_result
         | 
| 12 | 
            +
                attr_accessor :rgx
         | 
| 13 | 
            +
                attr_reader :remain_idx
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                EMPTYSTR = ''.freeze
         | 
| 16 | 
            +
                SLASH = '/'.freeze
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                RESULT_BAD_REQ_ERR = [nil, :error, ::Safrano::BadRequestError].freeze
         | 
| 19 | 
            +
                RESULT_NOT_FOUND_ERR = [nil, :error, ::Safrano::ErrorNotFound].freeze
         | 
| 20 | 
            +
                RESULT_SERVER_TR_ERR = [nil, :error, ServerTransitionError].freeze
         | 
| 21 | 
            +
                RESULT_END = [nil, :end].freeze
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                def initialize(arg, trans: nil, remain_idx: 2)
         | 
| 24 | 
            +
                  @rgx = if arg.respond_to? :each_char
         | 
| 25 | 
            +
                           Regexp.new(arg)
         | 
| 26 | 
            +
                         else
         | 
| 27 | 
            +
                           arg
         | 
| 28 | 
            +
                         end
         | 
| 29 | 
            +
                  @trans = trans
         | 
| 30 | 
            +
                  @remain_idx = remain_idx
         | 
| 31 | 
            +
                end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                def do_match(str)
         | 
| 34 | 
            +
                  @match_result = @rgx.match(str)
         | 
| 35 | 
            +
                end
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                # remain_idx is the index of the last match-data. ususally its 2
         | 
| 38 | 
            +
                # but can be overidden
         | 
| 39 | 
            +
                def path_remain
         | 
| 40 | 
            +
                  @match_result[@remain_idx] if @match_result && @match_result[@remain_idx]
         | 
| 41 | 
            +
                end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                def path_done
         | 
| 44 | 
            +
                  if @match_result
         | 
| 45 | 
            +
                    @match_result[1] || EMPTYSTR
         | 
| 46 | 
            +
                  else
         | 
| 47 | 
            +
                    EMPTYSTR
         | 
| 48 | 
            +
                  end
         | 
| 49 | 
            +
                end
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                def do_transition(ctx)
         | 
| 52 | 
            +
                  ctx.method(@trans).call(@match_result)
         | 
| 53 | 
            +
                end
         | 
| 54 | 
            +
              end
         | 
| 55 | 
            +
             | 
| 56 | 
            +
              TransitionEnd = Transition.new('\A(\/?)\z', trans: 'transition_end')
         | 
| 57 | 
            +
              TransitionMetadata = Transition.new('\A(\/\$metadata)(.*)',
         | 
| 58 | 
            +
                                                  trans: 'transition_metadata')
         | 
| 59 | 
            +
              TransitionBatch = Transition.new('\A(\/\$batch)(.*)',
         | 
| 60 | 
            +
                                               trans: 'transition_batch')
         | 
| 61 | 
            +
              TransitionContentId = Transition.new('\A(\/\$(\d+))(.*)',
         | 
| 62 | 
            +
                                                   trans: 'transition_content_id',
         | 
| 63 | 
            +
                                                   remain_idx: 3)
         | 
| 64 | 
            +
              TransitionCount = Transition.new('(\A\/\$count)(.*)\z',
         | 
| 65 | 
            +
                                               trans: 'transition_count')
         | 
| 66 | 
            +
              TransitionValue = Transition.new('(\A\/\$value)(.*)\z',
         | 
| 67 | 
            +
                                               trans: 'transition_value')
         | 
| 68 | 
            +
              TransitionLinks = Transition.new('(\A\/\$links)(.*)\z',
         | 
| 69 | 
            +
                                               trans: 'transition_links')
         | 
| 70 | 
            +
              attr_accessor :allowed_transitions
         | 
| 71 | 
            +
            end
         | 
    
        data/lib/odata/url_parameters.rb
    CHANGED
    
    | @@ -1,58 +1,149 @@ | |
| 1 | 
            -
             | 
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'odata/error'
         | 
| 2 4 |  | 
| 3 5 | 
             
            # url parameters processing . Mostly delegates to specialised classes
         | 
| 4 6 | 
             
            # (filter, order...) to convert into Sequel exprs.
         | 
| 5 | 
            -
            module  | 
| 6 | 
            -
              class  | 
| 7 | 
            +
            module Safrano
         | 
| 8 | 
            +
              class UrlParametersBase
         | 
| 9 | 
            +
                attr_reader :expand
         | 
| 10 | 
            +
                attr_reader :select
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                # url params validation methods.
         | 
| 13 | 
            +
                # nil is the expected return for no errors
         | 
| 14 | 
            +
                # an error class is returned in case of errors
         | 
| 15 | 
            +
                # this way we can combine multiple validation calls with logical ||
         | 
| 16 | 
            +
                def check_expand
         | 
| 17 | 
            +
                  @expand.parse_error?
         | 
| 18 | 
            +
                end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                def check_select
         | 
| 21 | 
            +
                  @select.parse_error?
         | 
| 22 | 
            +
                end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                def initialize(dataset, params = {})
         | 
| 25 | 
            +
                  @model = if dataset.respond_to? :model
         | 
| 26 | 
            +
                             dataset.model
         | 
| 27 | 
            +
                           else
         | 
| 28 | 
            +
                             dataset
         | 
| 29 | 
            +
                           end
         | 
| 30 | 
            +
                  @params = params
         | 
| 31 | 
            +
                end
         | 
| 32 | 
            +
              end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
              # url parameters for a single entity expand/select
         | 
| 35 | 
            +
              class UrlParameters4Single < UrlParametersBase
         | 
| 36 | 
            +
                def initialize(dataset, params)
         | 
| 37 | 
            +
                  super
         | 
| 38 | 
            +
                  @expand = ExpandBase.factory(@params['$expand'], @model)
         | 
| 39 | 
            +
                  @select = SelectBase.factory(@params['$select'], @model)
         | 
| 40 | 
            +
                end
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                def check_all
         | 
| 43 | 
            +
                  return Contract::OK unless @params
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                  check_expand.if_valid do
         | 
| 46 | 
            +
                    check_select
         | 
| 47 | 
            +
                  end
         | 
| 48 | 
            +
                end
         | 
| 49 | 
            +
              end
         | 
| 50 | 
            +
             | 
| 51 | 
            +
              # url parameters for a collection expand/select + filter/order
         | 
| 52 | 
            +
              class UrlParameters4Coll < UrlParametersBase
         | 
| 7 53 | 
             
                attr_reader :filt
         | 
| 8 54 | 
             
                attr_reader :ordby
         | 
| 9 | 
            -
             | 
| 10 | 
            -
             | 
| 55 | 
            +
             | 
| 56 | 
            +
                def initialize(dataset, params = {})
         | 
| 57 | 
            +
                  super
         | 
| 58 | 
            +
                  # join helper is only needed for odering or filtering
         | 
| 59 | 
            +
                  @jh = @model.join_by_paths_helper if params['$orderby'] || params['$filter']
         | 
| 11 60 | 
             
                  @params = params
         | 
| 61 | 
            +
                  @ordby = OrderBase.factory(@params['$orderby'], @jh)
         | 
| 62 | 
            +
                  @filt = FilterBase.factory(@params['$filter'])
         | 
| 63 | 
            +
                  @expand = ExpandBase.factory(@params['$expand'], @model)
         | 
| 64 | 
            +
                  @select = SelectBase.factory(@params['$select'], @model)
         | 
| 12 65 | 
             
                end
         | 
| 13 66 |  | 
| 14 | 
            -
                def  | 
| 15 | 
            -
                  return unless @params['$ | 
| 67 | 
            +
                def check_top
         | 
| 68 | 
            +
                  return Contract::OK unless @params['$top']
         | 
| 16 69 |  | 
| 17 | 
            -
                   | 
| 18 | 
            -
                   | 
| 70 | 
            +
                  itop = number_or_nil(@params['$top'])
         | 
| 71 | 
            +
                  (itop.nil? || itop.zero?) ? BadRequestError : Contract::OK
         | 
| 72 | 
            +
                end
         | 
| 73 | 
            +
             | 
| 74 | 
            +
                def check_skip
         | 
| 75 | 
            +
                  return Contract::OK unless @params['$skip']
         | 
| 19 76 |  | 
| 20 | 
            -
                   | 
| 21 | 
            -
                  nil
         | 
| 77 | 
            +
                  iskip = number_or_nil(@params['$skip'])
         | 
| 78 | 
            +
                  (iskip.nil? || iskip.negative?) ? BadRequestError : Contract::OK
         | 
| 22 79 | 
             
                end
         | 
| 23 80 |  | 
| 24 | 
            -
                def  | 
| 25 | 
            -
                  return unless @params['$ | 
| 81 | 
            +
                def check_inlinecount
         | 
| 82 | 
            +
                  return Contract::OK unless (icp = @params['$inlinecount'])
         | 
| 26 83 |  | 
| 27 | 
            -
                   | 
| 28 | 
            -
             | 
| 29 | 
            -
             | 
| 30 | 
            -
             | 
| 31 | 
            -
             | 
| 32 | 
            -
             | 
| 33 | 
            -
                    return BadRequestError unless @jh.start_model.attrib_path_valid? qualfn
         | 
| 34 | 
            -
                    return BadRequestError unless [nil, 'asc', 'desc'].include? dir
         | 
| 35 | 
            -
                  end
         | 
| 84 | 
            +
                  ((icp == 'allpages') || (icp == 'none')) ? Contract::OK : BadRequestInlineCountParamError
         | 
| 85 | 
            +
                end
         | 
| 86 | 
            +
             | 
| 87 | 
            +
                def check_filter
         | 
| 88 | 
            +
                  (err = @filt.parse_error?) ? err : Contract::OK
         | 
| 89 | 
            +
                end
         | 
| 36 90 |  | 
| 37 | 
            -
             | 
| 91 | 
            +
                def check_orderby
         | 
| 92 | 
            +
                  return Contract::OK if @ordby.empty?
         | 
| 93 | 
            +
                  return BadRequestOrderParseError if @ordby.parse_error?
         | 
| 38 94 |  | 
| 39 | 
            -
                   | 
| 40 | 
            -
                  nil
         | 
| 95 | 
            +
                  Contract::OK
         | 
| 41 96 | 
             
                end
         | 
| 42 97 |  | 
| 43 98 | 
             
                def apply_to_dataset(dtcx)
         | 
| 44 | 
            -
                   | 
| 45 | 
            -
             | 
| 46 | 
            -
                   | 
| 47 | 
            -
             | 
| 48 | 
            -
             | 
| 49 | 
            -
             | 
| 50 | 
            -
                   | 
| 51 | 
            -
             | 
| 52 | 
            -
                   | 
| 53 | 
            -
             | 
| 54 | 
            -
             | 
| 55 | 
            -
             | 
| 99 | 
            +
                  apply_expand_to_dataset(dtcx).if_valid do |dataset|
         | 
| 100 | 
            +
                    apply_filter_order_to_dataset(dataset)
         | 
| 101 | 
            +
                  end
         | 
| 102 | 
            +
                end
         | 
| 103 | 
            +
             | 
| 104 | 
            +
                def apply_expand_to_dataset(dtcx)
         | 
| 105 | 
            +
                  return Contract.valid(dtcx) if @expand.empty?
         | 
| 106 | 
            +
             | 
| 107 | 
            +
                  @expand.apply_to_dataset(dtcx)
         | 
| 108 | 
            +
                end
         | 
| 109 | 
            +
             | 
| 110 | 
            +
                # Warning, the @ordby and @filt objects are coupled by way of the join helper
         | 
| 111 | 
            +
                def apply_filter_order_to_dataset(dtcx)
         | 
| 112 | 
            +
                  return Contract.valid(dtcx) if @filt.empty? && @ordby.empty?
         | 
| 113 | 
            +
             | 
| 114 | 
            +
                  # filter object and join-helper need to be finalized after filter
         | 
| 115 | 
            +
                  # has been parsed and checked
         | 
| 116 | 
            +
                  @filt.finalize(@jh).if_valid do
         | 
| 117 | 
            +
                    # start with the join
         | 
| 118 | 
            +
                    dtcx = @jh.dataset(dtcx)
         | 
| 119 | 
            +
             | 
| 120 | 
            +
                    @filt.apply_to_dataset(dtcx).map_result! do |dataset|
         | 
| 121 | 
            +
                      dtcx = dataset
         | 
| 122 | 
            +
                      dtcx = @ordby.apply_to_dataset(dtcx)
         | 
| 123 | 
            +
                      dtcx.select_all(@jh.start_model.table_name)
         | 
| 124 | 
            +
                    end
         | 
| 125 | 
            +
                  end
         | 
| 126 | 
            +
                end
         | 
| 127 | 
            +
             | 
| 128 | 
            +
                ###########################################################
         | 
| 129 | 
            +
                def check_all
         | 
| 130 | 
            +
                  return Contract::OK unless @params
         | 
| 131 | 
            +
             | 
| 132 | 
            +
                  # lazy nested proc evaluation.
         | 
| 133 | 
            +
                  # if one check fails, it will be passed up the chain and the ones
         | 
| 134 | 
            +
                  # below will not be evaluated
         | 
| 135 | 
            +
                  check_top.if_valid do
         | 
| 136 | 
            +
                    check_skip.if_valid do
         | 
| 137 | 
            +
                      check_orderby.if_valid do
         | 
| 138 | 
            +
                        check_filter.if_valid do
         | 
| 139 | 
            +
                          check_expand.if_valid do
         | 
| 140 | 
            +
                            check_select.if_valid do
         | 
| 141 | 
            +
                              check_inlinecount
         | 
| 142 | 
            +
                            end
         | 
| 143 | 
            +
                          end
         | 
| 144 | 
            +
                        end
         | 
| 145 | 
            +
                      end
         | 
| 146 | 
            +
                    end
         | 
| 56 147 | 
             
                  end
         | 
| 57 148 | 
             
                end
         | 
| 58 149 | 
             
              end
         |