diff --git a/Gemfile.lock b/Gemfile.lock index d6613cb..e351129 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - syspro (0.1.0) + syspro-ruby (0.1.0) faraday (~> 0.10) GEM @@ -26,7 +26,7 @@ DEPENDENCIES minitest (~> 5.0) pry (~> 0.11) rake (~> 10.0) - syspro! + syspro-ruby! BUNDLED WITH 1.16.1 diff --git a/lib/syspro.rb b/lib/syspro.rb index c7b64a8..f5ffb1e 100644 --- a/lib/syspro.rb +++ b/lib/syspro.rb @@ -18,6 +18,11 @@ require "syspro/util" require "syspro/version" require "syspro/api_operations/request" +require "syspro/api_operations/query" + +require "syspro/business_objects/combrw" + +require "syspro/business_objects/parsers/combrw_parser" module Syspro @api_base = "http://syspro.wildlandlabs.com:90/SYSPROWCFService/Rest" diff --git a/lib/syspro/api_operations/query.rb b/lib/syspro/api_operations/query.rb new file mode 100644 index 0000000..31c221d --- /dev/null +++ b/lib/syspro/api_operations/query.rb @@ -0,0 +1,44 @@ +module Syspro + module ApiOperations + module Query + module ClassMethods + include Request + + def browse(params) + request(:get, "/Query/Browse", params) + end + + def fetch + end + + def query + end + + def find + end + + private + + def warn_on_opts_in_params(params) + Util::OPTS_USER_SPECIFIED.each do |opt| + if params.key?(opt) + $stderr.puts("WARNING: #{opt} should be in opts instead of params.") + end + end + end + end # ClassMethods + + def self.included(base) + base.extend(ClassMethods) + end + + protected + + def request(method, url, params = {}, opts = {}) + opts = @opts.merge(Util.normalize_opts(opts)) + Request.request(method, url, params, opts) + end + end + end +end + diff --git a/lib/syspro/api_operations/request.rb b/lib/syspro/api_operations/request.rb index 6d27e0d..7bbb2a0 100644 --- a/lib/syspro/api_operations/request.rb +++ b/lib/syspro/api_operations/request.rb @@ -37,13 +37,6 @@ module Syspro def self.included(base) base.extend(ClassMethods) end - - protected - - def request(method, url, params = {}, opts = {}) - opts = @opts.merge(Util.normalize_opts(opts)) - self.class.request(method, url, params, opts) - end end end end diff --git a/lib/syspro/business_objects/combrw.rb b/lib/syspro/business_objects/combrw.rb new file mode 100644 index 0000000..752c6ad --- /dev/null +++ b/lib/syspro/business_objects/combrw.rb @@ -0,0 +1,38 @@ +require "syspro/business_objects/parsers/combrw_parser" +require "erb" + +module Syspro + module BusinessObjects + class ComBrw < ApiResource + include Syspro::ApiOperations::Query + include Syspro::BusinessObjects::Parsers + + attr_accessor :browse_name, :start_condition, :return_rows, :filters, + :table_name, :title, :columns + + def call(user_id) + xml_in = template.result(binding) + params = { "UserId" => user_id, "XmlIn" => xml_in } + resp = ComBrw.browse(params) + parse_response(resp) + end + + def template + ERB.new File.read(File.expand_path("schemas/combrw.xml.erb", File.dirname(__FILE__))), nil, "%" + end + + def parse_response(resp) + handle_errors(resp) + ComBrwParser.parse(resp[0].data) + end + + def handle_errors(resp) + body = resp[0].http_body + if body.match(/^(ERROR)/) + raise SysproError, body + end + end + end + end +end + diff --git a/lib/syspro/business_objects/parsers/combrw_parser.rb b/lib/syspro/business_objects/parsers/combrw_parser.rb new file mode 100644 index 0000000..92713d7 --- /dev/null +++ b/lib/syspro/business_objects/parsers/combrw_parser.rb @@ -0,0 +1,53 @@ +module Syspro + module BusinessObjects + module Parsers + class ComBrwParser + + def self.parse(doc) + next_prev_key = doc.first_element_child.xpath("NextPrevKey") + next_prev_key_obj = next_prev_key.children.map { |el| + if el.name == "text" + next + end + { + name: el.name, + text: el.text + } + }.compact + + header_details = doc.first_element_child.xpath("HeaderDetails") + header_details_obj = header_details.children.map { |el| + if el.name == "text" + next + end + { + name: el.name, + text: el.text + } + }.compact + + rows = doc.first_element_child.xpath('Row') + rows_obj = rows.map { |el| + el.elements.map { |el| + { + name: el.name, + value: el.xpath('Value').text, + data_type: el.xpath('DataType').text + } + } + }.flatten(1).compact + + BrowseObject.new( + doc.first_element_child.xpath("Title").text, + rows_obj, + next_prev_key_obj, + header_details_obj + ) + end + + BrowseObject = Struct.new(:title, :rows, :next_prev_key, :header_details) + end + end + end +end + diff --git a/lib/syspro/business_objects/schemas/combrw.xml.erb b/lib/syspro/business_objects/schemas/combrw.xml.erb new file mode 100644 index 0000000..bd413eb --- /dev/null +++ b/lib/syspro/business_objects/schemas/combrw.xml.erb @@ -0,0 +1,29 @@ + + + + + <%= @browse_name %> + + <%= @start_condition %> + <%= @return_rows %> + <% for @filter in @filters %> + + <%= @filter[:name] %> + <%= @filter[:value] %> + + <% end %> + + <%= @table_name %> + <%= @title %> + <% for @column in @columns %> + + <% unless @column[:name].nil? %><%= @column[:name] %><% end %> + <% unless @column[:description].nil? %><%= @column[:description] %><% end %> + <% unless @column[:key].nil? %><%= @column[:key] %><% end %> + + <% end %> + + + diff --git a/lib/syspro/syspro_client.rb b/lib/syspro/syspro_client.rb index 4532b51..5cdf978 100644 --- a/lib/syspro/syspro_client.rb +++ b/lib/syspro/syspro_client.rb @@ -178,9 +178,9 @@ module Syspro case e when Faraday::ClientError if e.response - handle_error_response(e.response, error_context) + handle_error_response(e.response, context) else - handle_network_error(e, error_context, num_retries, api_base) + handle_network_error(e, context, num_retries, api_base) end # Only handle errors when we know we can do so, and re-raise otherwise. @@ -193,6 +193,35 @@ module Syspro resp end + def handle_network_error(e, context, num_retries, api_base = nil) + Util.log_error("Syspro network error", + error_message: e.message, + request_id: context.request_id) + + case e + when Faraday::ConnectionFailed + message = "Unexpected error communicating when trying to connect to Syspro." + + when Faraday::SSLError + message = "Could not establish a secure connection to Syspro." + + when Faraday::TimeoutError + api_base ||= Syspro.api_base + message = "Could not connect to Syspro (#{api_base}). " \ + "Please check your internet connection and try again. " \ + "If this problem persists, you should check your Syspro service status." + + else + message = "Unexpected error communicating with Syspro. " \ + "If this problem persists, talk to your Syspro implementation team." + + end + + message += " Request was retried #{num_retries} times." if num_retries > 0 + + raise ApiConnectionError, message + "\n\n(Network error: #{e.message})" + end + def self.should_retry?(e, num_retries) return false if num_retries >= Syspro.max_network_retries @@ -249,6 +278,26 @@ module Syspro end private :log_response_error + def handle_error_response(http_resp, context) + begin + resp = SysproResponse.from_faraday_hash(http_resp) + error_data = resp.data[:error] + + raise SysproError, "Indeterminate error" unless error_data + rescue Nokogiri::XML::SyntaxError, SysproError + raise general_api_error(http_resp[:status], http_resp[:body]) + end + + error = if error_data.is_a?(String) + specific_oauth_error(resp, error_data, context) + else + specific_api_error(resp, error_data, context) + end + + error.response = resp + raise(error) + end + # RequestLogContext stores information about a request that's begin made so # that we can log certain information. It's useful because it means that we # don't have to pass around as many parameters. diff --git a/test/query_test.rb b/test/query_test.rb new file mode 100644 index 0000000..d818553 --- /dev/null +++ b/test/query_test.rb @@ -0,0 +1,22 @@ +require "test_helper" + +class QueryTest < Minitest::Test + def test_query + user_id = Syspro::Logon.logon("wland", "piperita2016", "L", "") + + combrw = Syspro::BusinessObjects::ComBrw.new + combrw.browse_name = "InvMaster" + combrw.start_condition = "" + combrw.return_rows = 5 + combrw.filters = [] + combrw.table_name = "InvMaster" + combrw.title = "StockCodes" + combrw.columns = [ + {name: "StockCode"} + ] + + query_result = combrw.call(user_id.guid) + + refute_nil query_result + end +end -- libgit2 0.21.4