# typed: ignore # frozen_string_literal: true module Setsuzoku # The API Type Interface definition. # Any ApiStrategy that implements this interface must implement all abstract methods defined by ApiStrategy. # # Defines all necessary methods for handling interfacing with an external API/Service for any authentication strategy. module ApiStrategy extend Forwardable extend T::Sig extend T::Helpers include HasConfigContext abstract! attr_accessor :current_action attr_accessor :service def_delegators :@service, :plugin, :auth_strategy, :external_api_handler # Initialize the auth_strategy and provide reference to service. # # @param service [Service] the new instance of service with its correct strategies. # # @return [ApiStrategy] the new instance of api_strategy sig(:final) do params( service: T.any( Setsuzoku::Service::WebService::Service, T.untyped ), args: T.untyped ).returns(T.any( Setsuzoku::Service::WebService::ApiStrategies::RestStrategy, T.untyped )) end def initialize(service:, **args) self.service = service self.config_context = args self end # Perform the external call for the external API. # Each ApiStrategy must define how this works. # # It should: # 1. Make the external request # 2. Parse the response # 3. Handle any bad response # 4. Format the response # # @param request [APIRequest] the request object to be used for the request. Each strategy defines its request structure. # @param action_details [Hash] the action_details for the action to execute. # @param options [Any] any additional options needed to pass to correctly perform the request. # # @return [Any] the formatted response object. sig { abstract.params(request: T.untyped, action_details: T::Hash[T.untyped, T.untyped], options: T.untyped).returns(T.untyped) } def perform_external_call(request:, action_details:, **options); end # Perform an external system call. # This provides a uniform way of executing and handling responses for calls to external systems. # # @param request [Hash] the request hash used in the API request. # # @return [Hash] the response from the external system. sig { params(request: T.untyped, strategy: T.nilable(Symbol), options: T.untyped).returns(ApiResponse) } def call_external_api(request:, strategy: nil, **options) self.current_action = request.action formatted_response = T.let(nil, T.nilable(T::Hash[Symbol, T.untyped])) success = T.let(false, T::Boolean) full_object = {} exception = T.let(nil, T.nilable(Setsuzoku::Exception)) action_details = case strategy when :auth { actions: self.auth_strategy.credential.auth_actions, url: self.auth_strategy.credential.auth_base_url } when :webhook { actions: self.plugin.api_actions, url: self.plugin.webhook_base_url } else { actions: self.plugin.api_actions, url: self.plugin.api_base_url } end self.external_api_handler.call_external_api_wrapper(plugin: self.plugin, request: request, action_details: action_details) do begin # raise if the token is invalid and needs refreshed, but don't raise if we are trying to get a refresh token raise Setsuzoku::Exception::InvalidAuthCredentials.new unless (strategy == :auth || self.auth_strategy.auth_credential_valid?) raw_response = self.perform_external_call(request: request, action_details: action_details, **options) formatted_response = self.parse_response(response: raw_response, response_type: action_details[:actions][request.action][:response_type]) success = true rescue ::Exception => e exception = e self.external_api_handler.call_external_api_exception( plugin: self.plugin, request: request, action_details: action_details, options: options, raw_response: raw_response, formatted_response: formatted_response, exception: exception ) end full_object = { success: success, plugin: self.plugin, request: request, options: options, raw_response: raw_response, formatted_response: formatted_response, exception: exception } end self.current_action = nil ApiResponse.new(data: formatted_response, success: success, full_object: full_object) end # Parse the response from the API for the given request. # This should just convert JSON strings/XML/SQL rows to a formatted response Hash. # # @param response [Any] the response from the external request. # @param options [Hash] the parsing options. Generally the response_type. e.g. :xml, :json # # @return [Hash] the parsed hash of the response object. sig { abstract.params(response: T.untyped, options: T.untyped).returns(T.untyped) } def parse_response(response:, **options); end end end