morning-brew/sanity-ruby

View on GitHub
lib/sanity/http/mutation.rb

Summary

Maintainability
A
55 mins
Test Coverage
# frozen_string_literal: true

require "set" # rubocop:disable Lint/RedundantRequireStatement
using Sanity::Refinements::Strings
using Sanity::Refinements::Arrays

module Sanity
  module Http
    module Mutation
      class << self
        def included(base)
          base.extend(ClassMethods)
          base.extend(Forwardable)
          base.delegate(%i[project_id api_version dataset token api_subdomain] => :"Sanity.config")
          base.delegate(mutatable_api_endpoint: :resource_klass)
        end
      end

      module ClassMethods
        def call(**args)
          new(**args).call
        end
      end

      # See https://www.sanity.io/docs/http-mutations#visibility-937bc4250c79
      ALLOWED_VISIBILITY = %i[sync async deferred]

      # See https://www.sanity.io/docs/http-mutations#aa493b1c2524
      REQUEST_KEY = "mutations"

      # See https://www.sanity.io/docs/http-mutations#952b77deb110
      DEFAULT_QUERY_PARAMS = {
        return_ids: false,
        return_documents: false,
        visibility: :sync
      }.freeze

      attr_reader :options, :params, :resource_klass, :query_set, :serializer

      def initialize(**args)
        @resource_klass = args.delete(:resource_klass)
        @params = args.delete(:params)
        @query_set = Set.new

        warn RESULT_WRAPPER_DEPRECATION_WARNING if args[:result_wrapper]
        @serializer = args.delete(:serializer) ||
          args.delete(:result_wrapper) || # kept for backwards compatibility
          klass_serializer ||
          Sanity::Http::Results

        raise ArgumentError, "resource_klass must be defined" unless resource_klass
        raise ArgumentError, "params argument is missing" unless params

        (args.delete(:options) || {}).then do |opts|
          DEFAULT_QUERY_PARAMS.keys.each do |qup|
            query_set << [qup, opts.fetch(qup, DEFAULT_QUERY_PARAMS[qup])]
          end
        end
        raise ArgumentError, "visibility argument must be one of #{ALLOWED_VISIBILITY}" unless valid_invisibility?
      end

      def body_key
        self.class.name.demodulize.underscore.camelize_lower
      end

      def call
        Net::HTTP.post(uri, {"#{REQUEST_KEY}": body}.to_json, headers).then do |result|
          block_given? ? yield(serializer.call(result)) : serializer.call(result)
        end
      end

      def result_wrapper
        warn RESULT_WRAPPER_DEPRECATION_WARNING
        serializer
      end

      private

      def base_url
        "https://#{project_id}.#{api_subdomain}.sanity.io/#{api_version}/#{mutatable_api_endpoint}/#{dataset}"
      end

      def body
        return Array.wrap({"#{body_key}": params}) if params.is_a?(Hash)

        Array.wrap(params.map { |pam| {"#{body_key}": pam} })
      end

      def camelize_query_set
        query_set.to_h.transform_keys do |key|
          key.to_s.camelize_lower
        end
      end

      def headers
        {
          "Content-Type": "application/json",
          Authorization: "Bearer #{token}"
        }
      end

      def klass_serializer
        return unless @resource_klass.respond_to?(:default_serializer)

        @resource_klass.default_serializer
      end

      def query_params
        camelize_query_set.map do |key, val|
          "#{key}=#{val}"
        end.join("&")
      end

      def uri
        URI(base_url).tap do |obj|
          obj.query = query_params
        end
      end

      def valid_invisibility?
        ALLOWED_VISIBILITY.include? query_set.to_h[:visibility]
      end
    end
  end
end