paradox460/http_api_builder

View on GitHub
lib/http_api_builder/dsl.rb

Summary

Maintainability
A
1 hr
Test Coverage
require 'forwardable'

module HttpApiBuilder
  # Module for restful api dsl commands
  module Dsl
    VERBS =
      %i(get head post put delete trace options connect) + # HTTP 1.1
      %i(propfind proppatch mkcol copy move lock unlock) + # WebDAV
      %i(orderpatch) + # WebDAV Ordered Collections protocol
      %i(acl) + # WebDAV Access Control protocol
      %i(patch) + # PATCH method for HTTP
      %i(search) # WebDAV search

    # Set the initial URL used by the gem
    def base_url(value = nil)
      value.nil? ? @base_url : (@base_url = value)
    end

    protected

    # Generate whiny and quiet API consumer methods.
    #
    # Whiny methods have a bang suffix and raise errors if they fail
    # Quiet methods do not have the bang suffix and return nil if they fail
    #
    # eg:
    #   endpoint '/path', as: :mymethod
    # results in:
    #   mymethod! <-- whiny
    #   mymethod  <-- quiet
    def endpoint(path, as:, using: :get, params: nil, form: nil, body: nil, processors: nil, json: nil) # rubocop:disable Metrics/ParameterLists
      def_whiny_method as, path, using, processors, params, form, body, json
      def_quiet_method as
    end

    VERBS.each do |v|
      define_method v do |path, **opts|
        endpoint path, using: v, **opts
      end
    end

    private

    # Generate a consumer method that raises exceptions when requests raise an error
    #
    def def_whiny_method(name, path, using, processors, params, form, body, json) # rubocop:disable Metrics/ParameterLists, Metrics/AbcSize, Metrics/MethodLength
      required, optional = requirements(path, params)

      define_method :"#{name}!" do |opts = {}|
        json = opts.delete(:json) || json
        body = opts.delete(:body) || body
        validate_args! opts.keys, required, optional

        reqpath = interpolate_path(path, opts)
        query = query_params(path, opts, required, optional)

        form = Hash(form).merge(Hash(opts[:form]))
        perform(using, reqpath, form: form, query: query, body: body, json: json) do |resource, *_|
          run_processors resource, processors
        end
      end
    end

    # Generate a consumer method that returns nil when requests raise errors
    #
    def def_quiet_method(name)
      define_method name do |opts = {}|
        begin
          send(:"#{name}!", opts)
        rescue StandardError
          nil
        end
      end
    end

    # Extract param segments from a path, paperclip style. Returns an array of symbols matching the names of the param segments.
    #
    # Param segments are sections of the path that begin with `:`, and run to the next /
    def interpolated_params(path)
      path.split('/').reject { |i| i.length.zero? || i !~ /^:/ }.uniq.map { |i| i[1..-1].to_sym }
    end

    # Parse out and return the required and optional arguments
    #
    # Required are any that are in the URL or in the required hash
    # Optional are any other arguments.
    def requirements(path, params)
      required, optional = Hash(params).values_at(*%i(required optional)).map { |list| Array(list) }

      required += interpolated_params(path)
      optional += %i(form body json)

      [required, optional]
    end
  end
end