Module: RESTHelpers

Includes:
JSONModel
Included in:
ArchivesSpaceService
Defined in:
backend/app/lib/rest.rb

Defined Under Namespace

Modules: ResponseHelpers Classes: BooleanParam, Endpoint, IdSet, NonNegativeInteger, PageSize, UploadFile

Class Method Summary (collapse)

Methods included from JSONModel

JSONModel, #JSONModel, add_error_handler, all, allow_unmapped_enum_value, backend_url, client_mode?, custom_validations, destroy_model, enum_default_value, enum_values, handle_error, init, load_schema, #models, models, parse_jsonmodel_ref, parse_reference, repository, repository_for, schema_src, set_repository, strict_mode, strict_mode?, with_repository

Class Method Details

+ (Object) included(base)



319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
# File 'backend/app/lib/rest.rb', line 319

def self.included(base)

  base.extend(JSONModel)

  base.helpers do

    def coerce_type(value, type)
      if type == Integer
        Integer(value)
      elsif type == DateTime
        DateTime.parse(value)
      elsif type == Date
        Date.parse(value)
      elsif type.respond_to? :from_json

        # Allow the request to specify how the incoming JSON is encoded, but
        # convert to UTF-8 for processing
        if request.content_charset
          value = value.force_encoding(request.content_charset).encode("UTF-8")
        end

        type.from_json(value)
      elsif type.is_a? Array
        if value.is_a? Array
          value.map {|elt| coerce_type(elt, type[0])}
        else
          raise ArgumentError.new("Not an array")
        end
      elsif type.is_a? Regexp
        raise ArgumentError.new("Value '#{value}' didn't match #{type}") if value !~ type
        value
      elsif type.respond_to? :value
        type.value(value)
      elsif type == String
        value
      elsif type == :body_stream
        value
      else
        raise BadParamsException.new("Type not recognized: #{type}")
      end
    end


    def process_pagination_params(params, known_params, errors)
      known_params['resolve'] = known_params['modified_since'] = true

      params['modified_since'] = coerce_type((params[:modified_since] || '0'),
                                            NonNegativeInteger)

      if params[:page]
        known_params['page_size'] = known_params['page'] = true
        params['page_size'] = coerce_type((params[:page_size] || AppConfig[:default_page_size]), PageSize)
        params['page'] = coerce_type(params[:page], NonNegativeInteger)

      elsif params[:id_set]
        known_params['id_set'] = true
        params['id_set'] = coerce_type(params[:id_set], IdSet)

      elsif params[:all_ids]
        params['all_ids'] = known_params['all_ids'] = true

      else
        # Must provide either page, id_set or all_ids
        ['page', 'id_set', 'all_ids'].each do |name|
          errors[:missing] << {
            :name => name,
            :doc => "Must provide either 'page' (a number), 'id_set' (an array of record IDs), or 'all_ids' (a boolean)"
          }
        end
      end
    end


    def process_indexed_params(name, params)
      if params[name] && params[name].is_a?(Hash)
        params[name] = params[name].sort_by(&:first).map(&:last)
      end
    end


    def process_declared_params(declared_params, params, known_params, errors)
      declared_params.each do |definition|

        (name, type, doc, opts) = definition
        opts ||= {}

        if (type.is_a?(Array))
          process_indexed_params(name, params)
        end

        known_params[name] = true

        if opts[:body]
          params[name] = request.body.read
        elsif type == :body_stream
          params[name] = request.body
        end

        if not params[name] and not opts[:optional] and not opts[:default]
          errors[:missing] << {:name => name, :doc => doc}
        else

          if type and params[name]
            begin
              params[name.intern] = coerce_type(params[name], type)
              params.delete(name)

            rescue ArgumentError
              errors[:bad_type] << {:name => name, :doc => doc, :type => type}
            end
          elsif type and opts[:default]
            params[name.intern] = opts[:default]
            params.delete(name)
          end

          if opts[:validation]
            if not opts[:validation][1].call(params[name.intern])
              errors[:failed_validation] << {:name => name, :doc => doc, :type => type, :validation => opts[:validation][0]}
            end
          end

        end
      end
    end


    def ensure_params(declared_params, paginated)

      errors = {
        :missing => [],
        :bad_type => [],
        :failed_validation => []
      }

      known_params = {}

      process_declared_params(declared_params, params, known_params, errors)
      process_pagination_params(params, known_params, errors) if paginated

      # Any params that were passed in that aren't declared by our endpoint get dropped here.
      unknown_params = params.keys.reject {|p| known_params[p.to_s] }

      unknown_params.each do |p|
        params.delete(p)
      end


      if not errors.values.flatten.empty?
        result = {}

        errors[:missing].each do |missing|
          result[missing[:name]] = ["Parameter required but no value provided"]
        end

        errors[:bad_type].each do |bad|
          result[bad[:name]] = ["Wanted type #{bad[:type]} but got '#{params[bad[:name]]}'"]
        end

        errors[:failed_validation].each do |failed|
          result[failed[:name]] = ["Failed validation -- #{failed[:validation]}"]
        end

        raise BadParamsException.new(result)
      end
    end
  end
end