class LUSI::API::Core::API

Provides a wrapper for the LUSI web service API

Public Class Methods

new(api_user: nil, api_password: nil, api_root: nil, logger: nil, timeout: nil) click to toggle source

Initializes a new API instance @param api_user [String] the service account username @param api_password [String] the service account password @param logger [Logger, nil] a Logger instance for optional logging @param timeout [Integer] the timeout in seconds for LUSI API calls

# File lib/lusi_api/core/api.rb, line 22
def initialize(api_user: nil, api_password: nil, api_root: nil, logger: nil, timeout: nil)
  @api_password = api_password
  @api_root = api_root || 'https://lusiservice.lancs.ac.uk'
  @api_user = api_user
  @logger = logger
  @timeout = timeout.to_i
end

Public Instance Methods

call(path, endpoint, method, headers: nil, options: nil, **params) click to toggle source

Calls a LUSI API method and return the parsed XML response Any undocumented keyword parameters are passed as parameters to the LUSI API call. The LUSI 'Username' and 'Password' parameters are automatically added. @param path [String] the path of the API call (e.g. 'LUSIReference') @param endpoint [String] the endpoint of the API call (e.g. 'General.asmx') @param method [String] the method name of the API call (e.g. 'GetServiceAccountDetails') @param headers [Hash<String, String>] optional HTTP headers for the API call @param options [Hash<String, Object>] options for the REST client @return [Nokogiri::XML::Node] the parsed XML <body> content from the API response @raise [LUSIAPI::Core::APITimeoutError] if the LUSI API call times out @raise [LUSIAPI::Core::APICallError] if the LUSI API call fails for any other reason (e.g. network unavailable) @raise [LUSIAPI::Core::APIContentError] if the LUSI API call returns an XML document with an error response

# File lib/lusi_api/core/api.rb, line 42
def call(path, endpoint, method, headers: nil, options: nil, **params)

  # Add the username and password to the params
  params[:Username] ||= @api_user
  params[:Password] ||= @api_password

  # Set the REST client headers
  rest_headers = {}.merge(headers || {})

  # Set the REST client options
  rest_options = {
      method: :post,
      headers: rest_headers,
      payload: params,
      url: url(path, endpoint, method),
  }
  rest_options[:timeout] = @timeout if @timeout > 0
  rest_options.merge(options) if options

  RestClient.log = @log if @log

  # Make the API call
  begin
    response = RestClient::Request.execute(**rest_options)
  rescue RestClient::Exceptions::Timeout => e
    raise APITimeoutError.new(url: rest_options[:url])
  rescue RestClient::Exception => e
    raise APICallError.new(url: rest_options[:url])
  rescue => e
    raise APIError.new(url: rest_options[:url])
  end

  # Extract and return the XML content
  xml(response)

end
Also aliased as: post
get_service_account_details() click to toggle source

Return a ServiceAccount instance representing the API's service user @return [LUSI::API::ServiceAccount] the ServiceAccount instance representing the API's service user

# File lib/lusi_api/core/api.rb, line 81
def get_service_account_details
  # There should only be one instance but get_instance returns an array for consistnecy with other classes
  result = LUSI::API::ServiceAccount.get_instance(self)
  result.length == 0 ? nil : result[0]
end
post(path, endpoint, method, headers: nil, options: nil, **params)

Make a POST API call @see call

Alias for: call
update_service_account_password(password = nil) click to toggle source

Update the service user's password via the LUSI API @param password [String, nil] the new password (a random 16-character password is generated if this is omitted) @return [String] the new password

# File lib/lusi_api/core/api.rb, line 94
def update_service_account_password(password = nil)
  @api_password = LUSI::API::ServiceAccount.update_service_account_password(self, password)
end
url(path = nil, endpoint = nil, method = nil) click to toggle source

Construct a full API method URL from its constituents @param path [String] the path of the API call (e.g. 'LUSIReference') @param endpoint [String] the endpoint of the API call (e.g. 'General.asmx') @param method [String] the method name of the API call (e.g. 'GetServiceAccountDetails') @return [String] the fully-qualified URL of the API method

# File lib/lusi_api/core/api.rb, line 103
def url(path = nil, endpoint = nil, method = nil)
  "#{@api_root}/#{path}/#{endpoint}/#{method}"
end

Protected Instance Methods

validate(xml) click to toggle source

Validate the XML response from an API call @param xml [Nokogiri::XML::Node] the parsed XML response @return [Nokogiri::XML::Node] the parsed <body> element of the XML response @raise [LUSI::API::Core::APIContentError] if the XML response indicates an error

# File lib/lusi_api/core/api.rb, line 113
def validate(xml)
  header = LUSI::API::Core::XML.xml_at(xml, '//xmlns:Header')
  status = LUSI::API::Core::XML.xml_content_at(header, 'xmlns:Status')
  if status == 'Success'
    # No errors, return the body content
    LUSI::API::Core::XML.xml_at(xml, '//xmlns:Body')
  else
    # Errors present, raise APIContentError with the details
    error = {
      error_code: LUSI::API::Core::XML.xml_content_at(header, 'xmlns:ErrorCode'),
      fault: LUSI::API::Core::XML.xml_content_at(header, 'xmlns:Fault'),
      status: status
    }
    error_message = LUSI::API::Core::XML.xml_content_at(header, 'xmlns:ErrorMessage')
    case error[:error_code]
      when 'CLIENT004'
        raise APIPermissionError.new(error_message, **error)
      else
        raise APIContentError.new(error_message, **error)
    end
  end
end
xml(response) click to toggle source

Parse and validate the XML content from a LUSI API call @param response [String] the string-serialized XML response from the API @return [Nokogiri::XML::Document] the parsed <body> element of the XML response @raise [LUSIAPI::Core::APIContentError] if the XML parsing fails or the response indicates an error

# File lib/lusi_api/core/api.rb, line 140
def xml(response)

  # Parse the HTTP response as XML
  begin
    xml = Nokogiri::XML(response.to_s)
  rescue Nokogiri::SyntaxError => e
    raise APIContentError('XML syntax error')
  end

  # Validate the XML and return the <body> element if successful
  validate(xml)

end