From 5aa64c3832a4a5ba16a1dbd0148696dd32bdc715 Mon Sep 17 00:00:00 2001 From: Valentin Kiselev Date: Tue, 27 Jun 2023 15:11:20 +0300 Subject: [PATCH] fix: use Crystal http client --- .ameba.yml | 1 + src/coverage_reporter/api.cr | 34 +++++++++++++++++--- src/coverage_reporter/api/jobs.cr | 47 +++++++++++++++++++++------- src/coverage_reporter/api/webhook.cr | 19 ++++++----- src/coverage_reporter/cli/cmd.cr | 13 ++++++-- 5 files changed, 88 insertions(+), 26 deletions(-) diff --git a/.ameba.yml b/.ameba.yml index e4eaf432..6a00ca91 100644 --- a/.ameba.yml +++ b/.ameba.yml @@ -14,6 +14,7 @@ Metrics/CyclomaticComplexity: - src/coverage_reporter/parsers/lcov_parser.cr - src/coverage_reporter/parsers/gcov_parser.cr - src/coverage_reporter/parsers/coveragepy_parser.cr + - src/coverage_reporter/cli/cmd.cr Lint/PercentArrays: Enabled: true diff --git a/src/coverage_reporter/api.cr b/src/coverage_reporter/api.cr index e461ee1e..ae55338a 100644 --- a/src/coverage_reporter/api.cr +++ b/src/coverage_reporter/api.cr @@ -1,19 +1,45 @@ require "./config" require "./api/*" +require "http" module CoverageReporter module Api extend self - DEFAULT_HEADERS = { + class HTTPError < Exception + getter status_code : Int32 + getter response : String + + def initialize(response : HTTP::Client::Response) + super(response.status_message) + @status_code = response.status_code + @response = response.body + end + end + + class InternalServerError < HTTPError; end + + class UnprocessableEntity < HTTPError; end + + DEFAULT_HEADERS = HTTP::Headers{ "X-Coveralls-Reporter" => "coverage-reporter", "X-Coveralls-Reporter-Version" => VERSION, "X-Coveralls-Source" => ENV["COVERALLS_SOURCE_HEADER"]?.presence || "cli", + "Accept" => "*/*", + "User-Agent" => "Crystal #{Crystal::VERSION}", } - def show_response(res) - # TODO: include info about account status - Log.info "---\nโœ… API Response: #{res.body}\n- ๐Ÿ’›, Coveralls" + def handle_response(res) + case res.status + when HTTP::Status::OK + Log.info "---\nโœ… API Response: #{res.body}\n- ๐Ÿ’›, Coveralls" + when HTTP::Status::INTERNAL_SERVER_ERROR + raise InternalServerError.new(res) + when HTTP::Status::UNPROCESSABLE_ENTITY + raise UnprocessableEntity.new(res) + else + raise HTTPError.new(res) + end end end end diff --git a/src/coverage_reporter/api/jobs.cr b/src/coverage_reporter/api/jobs.cr index ecd7fe7d..4d381a61 100644 --- a/src/coverage_reporter/api/jobs.cr +++ b/src/coverage_reporter/api/jobs.cr @@ -1,7 +1,7 @@ require "../source_files" require "../git" require "../config" -require "crest" +require "http" require "compress/gzip" require "json" @@ -37,11 +37,11 @@ module CoverageReporter data = build_request api_url = "#{@config.endpoint}/api/#{API_VERSION}/jobs" - headers = DEFAULT_HEADERS.merge({ - "Content-Type" => "application/gzip", + headers = DEFAULT_HEADERS.dup + headers.merge!(HTTP::Headers{ "X-Coveralls-Coverage-Formats" => @source_files.map(&.format.to_s).sort!.uniq!.join(","), - "X-Coveralls-CI" => @config[:service_name]?, - }.compact) + "X-Coveralls-CI" => @config[:service_name]? || "unknown", + }) Log.info " ยทjob_flag: #{@config.flag_name}" if @config.flag_name Log.info "๐Ÿš€ Posting coverage data to #{api_url}" @@ -55,14 +55,17 @@ module CoverageReporter Compress::Gzip::Writer.open(io, &.print(data.to_json.to_s)) end - res = Crest.post( - api_url, - headers: headers, - form: gzipped_json, - tls: ENV["COVERALLS_ENDPOINT"]? ? OpenSSL::SSL::Context::Client.insecure : nil, - ) + with_file(IO::Memory.new(gzipped_json)) do |content_type, body| + headers.merge!(HTTP::Headers{"Content-Type" => content_type}) + response = HTTP::Client.post( + api_url, + body: body, + headers: headers, + tls: ENV["COVERALLS_ENDPOINT"]? ? OpenSSL::SSL::Context::Client.insecure : nil + ) - Api.show_response(res) + Api.handle_response(response) + end end private def build_request @@ -75,5 +78,25 @@ module CoverageReporter } ) end + + private def with_file(gzfile, &) + IO.pipe do |reader, writer| + channel = Channel(String).new(1) + + spawn do + HTTP::FormData.build(writer) do |formdata| + channel.send(formdata.content_type) + + metadata = HTTP::FormData::FileMetadata.new(filename: "json_file") + headers = HTTP::Headers{"Content-Type" => "application/x-gzip"} + formdata.file("json_file", gzfile, metadata, headers) + end + + writer.close + end + + yield(channel.receive, reader) + end + end end end diff --git a/src/coverage_reporter/api/webhook.cr b/src/coverage_reporter/api/webhook.cr index 8ea0dca6..b02704cf 100644 --- a/src/coverage_reporter/api/webhook.cr +++ b/src/coverage_reporter/api/webhook.cr @@ -1,4 +1,5 @@ -require "crest" +# require "crest" +require "http" require "json" module CoverageReporter @@ -22,18 +23,20 @@ module CoverageReporter Log.debug "---\nโ›‘ Debug Output:\n#{data.to_pretty_json}" return if dry_run + headers = DEFAULT_HEADERS.dup + headers.merge!(HTTP::Headers{ + "Content-Type" => "application/json", + "X-Coveralls-CI" => @config[:service_name]? || "unknown", + }) - res = Crest.post( + res = HTTP::Client.post( webhook_url, - headers: DEFAULT_HEADERS.merge({ - "Content-Type" => "application/json", - "X-Coveralls-CI" => @config[:service_name]?, - }.compact), - form: data.to_json, + headers: headers, + body: data.to_json, tls: ENV["COVERALLS_ENDPOINT"]? ? OpenSSL::SSL::Context::Client.insecure : nil ) - Api.show_response(res) + Api.handle_response(res) end end end diff --git a/src/coverage_reporter/cli/cmd.cr b/src/coverage_reporter/cli/cmd.cr index 78b63d2d..d4b993ea 100644 --- a/src/coverage_reporter/cli/cmd.cr +++ b/src/coverage_reporter/cli/cmd.cr @@ -44,10 +44,10 @@ module CoverageReporter::Cli Coveralls Coverage Reporter v#{CoverageReporter::VERSION} ERROR exit 1 - rescue ex : Crest::InternalServerError + rescue ex : Api::InternalServerError Log.error "โš ๏ธ Internal server error. Please contact Coveralls team." exit 1 - rescue ex : Crest::UnprocessableEntity + rescue ex : Api::UnprocessableEntity Log.error <<-ERROR --- Error: #{ex.message} @@ -59,6 +59,15 @@ module CoverageReporter::Cli - ๐Ÿ’›, Coveralls ERROR exit 1 + rescue ex : Api::HTTPError + Log.error <<-ERROR + Unhandled HTTP error: + --- + Error: #{ex.message} (#{ex.status_code}) + Message: #{ex.response} + --- + ERROR + exit 1 rescue ex raise(ex) if opts.try(&.debug?)