207
208
209
210
211
212
|
# File 'backend/app/lib/rest.rb', line 207
def example(highlighter, contents = nil)
if block_given?
contents = yield contents
end
if contents
contents = " ```\#{highlighter}\n \#{contents}\n ```\n TEMPLATE\n\n @examples[highlighter] = contents\n end\n self\n end\n\n # useful in cases where a permission presupposes another\n # created earlier and not necessarily propagated to existing repos\n def sufficient_permissions(permissions)\n @has_permissions = true\n @permissions += permissions\n\n @preconditions << proc { |request| permissions.any? { |permission| current_user.can?(permission) } }\n\n self\n end\n\n def permissions(permissions)\n @has_permissions = true\n @permissions += permissions\n\n permissions.each do |permission|\n @preconditions << proc { |request| current_user.can?(permission) }\n end\n\n self\n end\n\n\n def request_context(hash)\n @request_context_keyvals = hash\n\n self\n end\n\n\n def params(*params)\n @required_params = params.map do |p|\n param_name, param_type = p\n\n if @@param_types[param_type]\n # This parameter type has a standard definition\n defn = @@param_types[param_type]\n [param_name, *defn]\n else\n p\n end\n end\n\n self\n end\n\n\n def deprecated(description = nil)\n @deprecated = true\n @deprecated_description = description\n\n self\n end\n\n def paginated(val)\n @paginated = val\n\n self\n end\n\n def paged(val)\n @paged = val\n\n self\n end\n\n def no_data(val)\n @no_data = val\n\n self\n end\n\n def use_transaction(val)\n @use_transaction = val\n\n self\n end\n\n\n def returns(*returns, &block)\n raise \"No .permissions declaration for endpoint \#{@methods.map {|m| m.to_s.upcase}.join('|')} \#{@uri}\" if !@has_permissions\n\n @returns = returns.map { |r| r[1] = @@return_types[r[1]] || r[1]; r }\n\n @@endpoints << self\n\n preconditions = @preconditions\n rp = @required_params\n paginated = @paginated\n paged = @paged\n deprecated = @deprecated\n deprecated_description = @deprecated_description\n use_transaction = @use_transaction\n uri = @uri\n methods = @methods\n request_context = @request_context_keyvals\n\n if ArchivesSpaceService.development?\n # Undefine any pre-existing routes (sinatra reloader seems to have trouble\n # with this for our instances)\n ArchivesSpaceService.instance_eval {\n new_route = compile(uri)\n\n methods.each do |method|\n if @routes[method.to_s.upcase]\n @routes[method.to_s.upcase].reject! do |route|\n route[0..1] == new_route\n end\n end\n end\n }\n end\n\n methods.each do |method|\n ArchivesSpaceService.send(method, uri, {}) do\n if deprecated\n Log.warn(\"\\n\" +\n (\"*\" * 80) +\n \"\\n*** CALLING A DEPRECATED ENDPOINT: \#{method} \#{uri}\\n\" +\n (deprecated_description ? (\"\\n\" + deprecated_description) : \"\") +\n \"\\n\" +\n (\"*\" * 80))\n end\n\n\n RequestContext.open(request_context) do\n DB.open do |db|\n ensure_params(rp, paginated, paged)\n end\n\n Log.debug(\"Post-processed params: \#{Log.filter_passwords(params).inspect}\")\n\n RequestContext.put(:repo_id, params[:repo_id])\n RequestContext.put(:is_high_priority, high_priority_request?)\n\n if Endpoint.is_toplevel_request?(env) || Endpoint.is_potentially_destructive_request?(env)\n unless preconditions.all? { |precondition| self.instance_eval &precondition }\n raise AccessDeniedException.new(\"Access denied\")\n end\n end\n\n use_transaction = (use_transaction == :unspecified) ? true : use_transaction\n db_opts = {}\n\n if use_transaction\n if methods == [:post]\n # Pure POST requests use read committed so that tree position\n # updates can be retried with a chance of succeeding (i.e. we\n # can read the last committed value when determining our\n # position)\n db_opts[:isolation_level] = :committed\n else\n # Anything that might be querying the DB will get repeatable read.\n db_opts[:isolation_level] = :repeatable\n end\n end\n\n DB.open(use_transaction, db_opts) do\n RequestContext.put(:current_username, current_user.username)\n # If the current user is a manager, show them suppressed records\n # too.\n if RequestContext.get(:repo_id)\n if current_user.can?(:index_system)\n # Don't mess with the search user\n RequestContext.put(:enforce_suppression, false)\n else\n RequestContext.put(:enforce_suppression,\n !((current_user.can?(:manage_repository) ||\n current_user.can?(:view_suppressed) ||\n current_user.can?(:suppress_archival_record)) &&\n Preference.defaults['show_suppressed']))\n end\n end\n\n self.instance_eval &block\n end\n end\n end\n end\n end\n end\n\n\n class NonNegativeInteger\n def self.value(s)\n val = Integer(s)\n\n if val < 0\n raise ArgumentError.new(\"Invalid non-negative integer value: \#{s}\")\n end\n\n val\n end\n end\n\n\n class PageSize\n def self.value(s)\n val = Integer(s)\n\n if val < 0\n raise ArgumentError.new(\"Invalid non-negative integer value: \#{s}\")\n end\n\n if val > AppConfig[:max_page_size].to_i\n Log.warn(\"Requested page size of \#{val} exceeds the maximum allowable of \#{AppConfig[:max_page_size]}.\" +\n \" It has been reduced to the maximum.\")\n\n val = AppConfig[:max_page_size].to_i\n end\n\n val\n end\n end\n\n\n class IdSet\n def self.value(val)\n vals = val.is_a?(Array) ? val : val.split(/,/)\n\n result = vals.map {|elt| Integer(elt)}.uniq\n\n if result.length > AppConfig[:max_page_size].to_i\n raise ArgumentError.new(\"ID set cannot contain more than \#{AppConfig[:max_page_size]}n IDs\")\n end\n\n result\n end\n end\n\n\n class BooleanParam\n def self.value(s)\n if s.nil?\n nil\n elsif s.to_s.downcase == 'true'\n true\n elsif s.to_s.downcase == 'false'\n false\n else\n raise ArgumentError.new(\"Invalid boolean value: \#{s}\")\n end\n end\n end\n\n\n class UploadFile\n def self.value(val)\n OpenStruct.new(val)\n end\n end\n\n\n def self.included(base)\n base.extend(JSONModel)\n\n base.helpers do\n\n def coerce_type(value, type)\n if type == Integer\n Integer(value)\n elsif type == DateTime\n DateTime.parse(value)\n elsif type == Date\n Date.parse(value)\n elsif type.respond_to? :from_json\n\n # Allow the request to specify how the incoming JSON is encoded, but\n # convert to UTF-8 for processing\n if request.content_charset\n value = value.force_encoding(request.content_charset).encode(\"UTF-8\")\n end\n value = value.to_json unless value.is_a? String\n type.from_json(value)\n elsif type.is_a? Array\n if value.is_a? Array\n value.map {|elt| coerce_type(elt, type[0])}\n else\n raise ArgumentError.new(\"Not an array\")\n end\n elsif type.is_a? Regexp\n raise ArgumentError.new(\"Value '\#{value}' didn't match \#{type}\") if value !~ type\n value\n elsif type.respond_to? :value\n type.value(value)\n elsif type == String\n value\n elsif type == :body_stream\n value\n else\n raise BadParamsException.new(\"Type not recognized: \#{type}\")\n end\n end\n\n\n def process_pagination_params(params, known_params, errors, paged)\n known_params['resolve'] = known_params['modified_since'] = true\n params['modified_since'] = coerce_type((params[:modified_since] || '0'),\n NonNegativeInteger)\n\n known_params['sort_field'] = true\n known_params['sort_direction'] = true\n params['sort_field'] = params.fetch('sort_field', 'id').to_sym\n params['sort_direction'] = params.fetch('sort_direction', 'asc').to_sym\n\n unless [:asc, :desc].include? params['sort_direction']\n errors[:failed_validation] << {\n name: 'sort_direction', validation: \"must be either 'asc' or 'desc' but given: \#{params['sort_direction']}\"\n }\n end\n\n if params[:page]\n known_params['page_size'] = known_params['page'] = true\n params['page_size'] = coerce_type((params[:page_size] || AppConfig[:default_page_size]), PageSize)\n params['page'] = coerce_type(params[:page], NonNegativeInteger)\n elsif params[:id_set]\n known_params['id_set'] = true\n params['id_set'] = coerce_type(params[:id_set], IdSet)\n\n elsif params[:all_ids]\n params['all_ids'] = known_params['all_ids'] = true\n\n else\n # paged and paginated routes both support accessing results a page at a time,\n # via the page and page_size arguments\n # paginated routes additionally support:\n # - fetching all database ids as an array via all_ids\n # - fetching a set of specific known ids via id_set\n if paged\n # Must provide page\n errors[:missing] << {\n :name => 'page',\n :doc => \"Must provide 'page' (a number)\"\n }\n else\n # Must provide either page, id_set or all_ids\n ['page', 'id_set', 'all_ids'].each do |name|\n errors[:missing] << {\n :name => name,\n :doc => \"Must provide either 'page' (a number), 'id_set' (an array of record IDs), or 'all_ids' (a boolean)\"\n }\n end\n end\n end\n end\n\n\n def process_indexed_params(name, params)\n if params[name] && params[name].is_a?(Hash)\n params[name] = params[name].sort_by(&:first).map(&:last)\n end\n end\n\n\n def process_declared_params(declared_params, params, known_params, errors)\n declared_params.each do |definition|\n (name, type, doc, opts) = definition\n opts ||= {}\n\n if (type.is_a?(Array))\n process_indexed_params(name, params)\n end\n\n known_params[name] = true\n\n if opts[:body]\n params[name] = request.body.read\n elsif type == :body_stream\n params[name] = request.body\n end\n\n if not params[name] and !opts[:optional] and !opts.has_key?(:default)\n errors[:missing] << {:name => name, :doc => doc}\n else\n\n if type and params[name]\n begin\n params[name] = coerce_type(params[name], type)\n rescue ArgumentError\n errors[:bad_type] << {:name => name, :doc => doc, :type => type}\n end\n elsif type and opts[:default]\n params[name] = opts[:default]\n end\n\n if opts[:validation]\n if not opts[:validation][1].call(params[name.intern])\n errors[:failed_validation] << {:name => name, :doc => doc, :type => type, :validation => opts[:validation][0]}\n end\n end\n\n end\n end\n end\n\n\n def ensure_params(declared_params, paginated, paged)\n params.delete('captures') # Sinatra 2.x\n errors = {\n :missing => [],\n :bad_type => [],\n :failed_validation => []\n }\n\n known_params = {}\n\n process_declared_params(declared_params, params, known_params, errors)\n process_pagination_params(params, known_params, errors, paged) if paginated || paged\n\n # Any params that were passed in that aren't declared by our endpoint get dropped here.\n unknown_params = params.keys.reject {|p| known_params[p.to_s] }\n\n unknown_params.each do |p|\n params.delete(p)\n end\n\n\n if not errors.values.flatten.empty?\n result = {}\n\n errors[:missing].each do |missing|\n result[missing[:name]] = [\"Parameter required but no value provided\"]\n end\n\n errors[:bad_type].each do |bad|\n provided_value = params[bad[:name]]\n msg = \"Wanted type \#{bad[:type]} but got '\#{provided_value}'\"\n\n\n if bad[:type].is_a?(Array) &&\n !provided_value.is_a?(Array) &&\n provided_value.is_a?(bad[:type][0])\n\n # The caller got the right type but didn't wrap it in an array.\n # Provide a more useful error message.\n msg << \". Perhaps you meant to specify an array like: \#{bad[:name]}[]=\#{Addressable::URI.escape(provided_value)}\"\n end\n\n result[bad[:name]] = [msg]\n end\n\n errors[:failed_validation].each do |failed|\n result[failed[:name]] = [\"Failed validation -- \#{failed[:validation]}\"]\n end\n\n raise BadParamsException.new(result)\n end\n end\n end\n end\n\nend\n"
|