Skip to content

Commit

Permalink
Encode serialized output to avoid encoding errors. (#44)
Browse files Browse the repository at this point in the history
  • Loading branch information
ioquatix authored Aug 3, 2023
1 parent 9262be1 commit af79b3a
Show file tree
Hide file tree
Showing 4 changed files with 102 additions and 2 deletions.
40 changes: 40 additions & 0 deletions lib/console/output/encoder.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# frozen_string_literal: true

# Released under the MIT License.
# Copyright, 2023, by Samuel Williams.

module Console
module Output
class Encoder
def initialize(output, encoding = ::Encoding::UTF_8)
@output = output
@encoding = encoding
end

attr :output

attr :encoding

def call(subject = nil, *arguments, **options, &block)
subject = encode(subject)
arguments = encode(arguments)
options = encode(options)

@output.call(subject, *arguments, **options, &block)
end

def encode(value)
case value
when String
value.encode(@encoding, invalid: :replace, undef: :replace)
when Array
value.map{|item| encode(item)}
when Hash
value.transform_values{|item| encode(item)}
else
value
end
end
end
end
end
6 changes: 5 additions & 1 deletion lib/console/output/json.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,16 @@
# Copyright, 2021-2022, by Samuel Williams.

require_relative '../serialized/logger'
require_relative 'encoder'

module Console
module Output
module JSON
def self.new(output, **options)
Serialized::Logger.new(output, format: ::JSON, **options)
# The output encoder can prevent encoding issues (e.g. invalid UTF-8):
Output::Encoder.new(
Serialized::Logger.new(output, format: ::JSON, **options)
)
end
end
end
Expand Down
18 changes: 17 additions & 1 deletion test/console/output.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
let(:output) {File.open('/tmp/console.log', 'w')}

it 'should use a serialized format' do
expect(Console::Output.new(output, env)).to be_a(Console::Serialized::Logger)
expect(Console::Output.new(output, env).output).to be_a(Console::Serialized::Logger)
end
end

Expand All @@ -31,7 +31,9 @@

it 'can set output to Serialized and format to JSON by ENV' do
output = Console::Output.new(StringIO.new, {'CONSOLE_OUTPUT' => 'JSON'})
expect(output).to be_a(Console::Output::Encoder)

output = output.output
expect(output).to be_a Console::Serialized::Logger
expect(output.format).to be == JSON
end
Expand Down Expand Up @@ -64,4 +66,18 @@
expect(output.terminal).to be_a Console::Terminal::Text
end
end

with "invalid UTF-8" do
let(:capture) {StringIO.new}

it "should replace invalid characters" do
expect(capture).to receive(:tty?).and_return(false)
output = Console::Output.new(capture, {})

output.call("Hello \xFF")

message = JSON.parse(capture.string)
expect(message['subject']).to be == "Hello \uFFFD"
end
end
end
40 changes: 40 additions & 0 deletions test/console/output/encoder.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# frozen_string_literal: true

# Released under the MIT License.
# Copyright, 2023, by Samuel Williams.

require 'console/output/encoder'
require 'console/capture'

describe Console::Output::Encoder do
let(:output) {Console::Capture.new}
let(:encoder) {subject.new(output)}

let(:invalid_string) {"hello \xc3\x28 world"}
it "is an invalid string" do
expect(invalid_string).not.to be(:valid_encoding?)
end

it "can fix encoding" do
valid_string = encoder.encode(invalid_string)
expect(valid_string).to be(:valid_encoding?)
end

it "can encode hashes" do
invalid = {key: invalid_string}
valid = encoder.encode(invalid)

expect(valid[:key]).to be(:valid_encoding?)
end

it "can encode arrays" do
invalid = [invalid_string]
valid = encoder.encode(invalid)

expect(valid.first).to be(:valid_encoding?)
end

it "ignores non-strings" do
expect(encoder.encode(1)).to be == 1
end
end

0 comments on commit af79b3a

Please sign in to comment.