diff --git a/lib/trmnl/api/client.rb b/lib/trmnl/api/client.rb new file mode 100644 index 0000000..e56c460 --- /dev/null +++ b/lib/trmnl/api/client.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +require "dry/monads" +require "http" + +module TRMNL + module API + # Provides a low level configurable and monadic API client. + # :reek:DataClump + class Client + include Dry::Monads[:result] + + HEADERS = {}.freeze + + def initialize settings: TRMNL::API::Configuration::Loader.new.call, http: HTTP + @settings = settings + @http = http + + yield settings if block_given? + end + + def get(path, headers: HEADERS, **params) = call(__method__, path, headers, params:) + + def post(path, headers: HEADERS, **json) = call(__method__, path, headers, json:) + + private + + attr_reader :settings, :http + + # rubocop:todo Metrics/ParameterLists + def call method, path, headers, **options + http.headers(settings.headers.merge(headers)) + .public_send(method, "#{settings.uri}/#{path}", options) + .then { |response| response.status.success? ? Success(response) : Failure(response) } + end + # rubocop:enable Metrics/ParameterLists + end + end +end diff --git a/spec/lib/trmnl/api/client_spec.rb b/spec/lib/trmnl/api/client_spec.rb new file mode 100644 index 0000000..70535fa --- /dev/null +++ b/spec/lib/trmnl/api/client_spec.rb @@ -0,0 +1,108 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe TRMNL::API::Client do + using Refinements::Hash + + subject(:client) { described_class.new http: } + + describe "#initialize" do + it "initializes with block" do + client = described_class.new { |settings| settings.content_type = "application/xml" } + body = client.get("bogus").failure.to_s + + expect(body).to include("Not Found") + end + end + + describe "#get" do + context "with success" do + let :http do + HTTP::Fake::Client.new do + get "/api/current_screen" do + headers["Content-Type"] = "application/json" + status 200 + + <<~JSON + { + "status": 200, + "refresh_rate": 3200, + "image_url": "https://test.io/images/test.bmp", + "filename": "test.bmp", + "rendered_at": null + } + JSON + end + end + end + + it "answers response" do + response = client.get "current_screen" + payload = response.fmap(&:parse).bind(&:symbolize_keys!) + + expect(payload).to eq( + status: 200, + refresh_rate: 3200, + image_url: "https://test.io/images/test.bmp", + filename: "test.bmp", + rendered_at: nil + ) + end + end + + context "with custom header" do + let(:http) { class_spy HTTP, headers: HTTP } + let(:response) { instance_spy HTTP::Response } + + it "includes custom header" do + client.get "current_screen", headers: {"ID" => "123"} + + expect(http).to have_received(:headers).with( + "Content-Type" => "application/json", + "ID" => "123" + ) + end + end + + context "with failure" do + let :http do + HTTP::Fake::Client.new do + get "/api/current_screen" do + headers["Content-Type"] = "application/json" + status 404 + + <<~JSON + { + "message": "Danger!" + } + JSON + end + end + end + + it "answers failure response" do + response = client.get "current_screen" + payload = response.alt_map { |result| result.parse.symbolize_keys! } + + expect(payload).to be_failure(message: "Danger!") + end + end + end + + describe "#post" do + let :http do + HTTP::Fake::Client.new do + post "/api/log" do + headers["Content-Type"] = "application/json" + status 204 + end + end + end + + it "answers response" do + result = client.post "log", log: {} + expect(result.success.status).to eq(204) + end + end +end