twiglet 3.0.8 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: aadbe159aee695cfd454e0f006ab5eab2b3c06d01ecaa80d3f6b1b69c0d21d5e
4
- data.tar.gz: 53907b03fb29af1fe41d8262619a6d5c84dab554b4e309d7336d969c11600a44
3
+ metadata.gz: 120d52bf23086d2db6f65f068aa087835914bcfe52c3ef1f8b6b886f946e9dcd
4
+ data.tar.gz: 8355c113e09882c4b1346111bba954b82f81d69c0290d1055dca4bb61174fc09
5
5
  SHA512:
6
- metadata.gz: d5e68a4b343c861c7236e4a301365d738221bbe5eb0b23426e3cba84fad2358046d1b868bef5248c1c3398f4c2cfd132445259b7db976937024f73663699c2d4
7
- data.tar.gz: 28b619fd981103d5baea53d07e032e52e21cf88e84b5229d1ca1ff61cedc284d6825ebf26fb977f060d3af4c34f276b8a69c733e66321952a726d452dc82164b
6
+ metadata.gz: 65ca30c0dcf26b8190b7840b70bd5a183b481333bc98af0abfed2835a5a25ec664e2b3c24f10d90f40a6a1d5a18fb90d9f97c2c2aae0ab49b123558049cc11da
7
+ data.tar.gz: 5936e07ac730cd18b39b5777a3fd1a7b8d805852726ad2732d46d6acdb1c7ef820f7dacf87ba68a85352dd3653c0e49b132a90ad71a40dd7ebdc3744ff9e68b8
data/Gemfile CHANGED
@@ -1,6 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  source 'https://rubygems.org'
4
+
5
+ gem 'json-schema'
6
+
4
7
  group :development, :test do
5
8
  gem 'minitest'
6
9
  gem 'rake'
@@ -1,24 +1,28 @@
1
1
  require 'logger'
2
2
  require_relative '../hash_extensions'
3
3
  require_relative 'message'
4
+ require_relative 'validator'
4
5
 
5
6
  module Twiglet
6
7
  class Formatter < ::Logger::Formatter
7
8
  Hash.include HashExtensions
8
9
 
9
10
  def initialize(service_name,
10
- default_properties: {},
11
+ validator:, default_properties: {},
11
12
  now: -> { Time.now.utc })
12
13
  @service_name = service_name
13
14
  @now = now
14
15
  @default_properties = default_properties
16
+ @validator = validator
15
17
 
16
18
  super()
17
19
  end
18
20
 
19
21
  def call(severity, _time, _progname, msg)
20
22
  level = severity.downcase
21
- log(level: level, message: Message.new(msg))
23
+ message = Message.new(msg)
24
+ @validator.validate(message)
25
+ log(level: level, message: message)
22
26
  end
23
27
 
24
28
  private
@@ -26,8 +26,19 @@ module Twiglet
26
26
  raise 'Service name is mandatory' \
27
27
  unless service_name.is_a?(String) && !service_name.strip.empty?
28
28
 
29
- formatter = Twiglet::Formatter.new(service_name, default_properties: default_properties, now: now)
30
- super(output, formatter: formatter, level: level)
29
+ @validator = Validator.from_file('lib/twiglet/validation_schema.json')
30
+
31
+ @formatter = Twiglet::Formatter.new(
32
+ service_name,
33
+ default_properties: default_properties,
34
+ now: now,
35
+ validator: @validator
36
+ )
37
+ super(output, formatter: @formatter, level: level)
38
+ end
39
+
40
+ def configure_validation_error_response(&block)
41
+ @validator.custom_error_handler = block
31
42
  end
32
43
 
33
44
  def error(message = nil, error = nil, &block)
@@ -10,18 +10,6 @@ module Twiglet
10
10
  else
11
11
  super(msg)
12
12
  end
13
-
14
- validate!
15
- end
16
-
17
- private
18
-
19
- def validate!
20
- raise 'Message must be initialized with a String or a non-empty Hash' if empty?
21
-
22
- raise 'Log object must have a \'message\' property' unless self[:message]
23
-
24
- raise 'The \'message\' property of the log object must not be empty' if self[:message].strip.empty?
25
13
  end
26
14
  end
27
15
  end
@@ -0,0 +1,10 @@
1
+ {
2
+ "type": "object",
3
+ "required": ["message"],
4
+ "properties": {
5
+ "message": {
6
+ "type": "string",
7
+ "minLength": 1
8
+ }
9
+ }
10
+ }
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json-schema'
4
+ require 'json'
5
+
6
+ module Twiglet
7
+ class Validator
8
+ attr_accessor :custom_error_handler
9
+
10
+ def initialize(schema)
11
+ @schema = schema
12
+ @custom_error_handler = ->(e) { raise e }
13
+ end
14
+
15
+ def self.from_file(file_path)
16
+ new(JSON.parse(File.read(file_path)))
17
+ end
18
+
19
+ def validate(message)
20
+ JSON::Validator.validate!(@schema, message)
21
+ rescue JSON::Schema::ValidationError => e
22
+ custom_error_handler.call(e)
23
+ end
24
+ end
25
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Twiglet
4
- VERSION = '3.0.8'
4
+ VERSION = '3.1.0'
5
5
  end
@@ -3,11 +3,12 @@
3
3
  require 'minitest/autorun'
4
4
  require 'json'
5
5
  require_relative '../lib/twiglet/formatter'
6
+ require_relative '../lib/twiglet/validator'
6
7
 
7
8
  describe Twiglet::Formatter do
8
9
  before do
9
10
  @now = -> { Time.utc(2020, 5, 11, 15, 1, 1) }
10
- @formatter = Twiglet::Formatter.new('petshop', now: @now)
11
+ @formatter = Twiglet::Formatter.new('petshop', now: @now, validator: Twiglet::Validator.new({}))
11
12
  end
12
13
 
13
14
  it 'initializes an instance of a Ruby Logger Formatter' do
data/test/logger_test.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'minitest/autorun'
4
+ require 'minitest/mock'
4
5
  require_relative '../lib/twiglet/logger'
5
6
 
6
7
  LEVELS = [
@@ -42,11 +43,17 @@ describe Twiglet::Logger do
42
43
 
43
44
  describe 'JSON logging' do
44
45
  it 'should throw an error with an empty message' do
45
- assert_raises RuntimeError do
46
+ assert_raises JSON::Schema::ValidationError, "The property '#/message' was not of a minimum string length of 1" do
46
47
  @logger.info({ message: '' })
47
48
  end
48
49
  end
49
50
 
51
+ it 'should throw an error if message is missing' do
52
+ assert_raises JSON::Schema::ValidationError, "The property '#/message' was not of a minimum string length of 1" do
53
+ @logger.info({ foo: 'bar' })
54
+ end
55
+ end
56
+
50
57
  it 'should log mandatory attributes' do
51
58
  @logger.error({ message: 'Out of pets exception' })
52
59
  actual_log = read_json(@buffer)
@@ -239,7 +246,7 @@ describe Twiglet::Logger do
239
246
 
240
247
  describe 'text logging' do
241
248
  it 'should throw an error with an empty message' do
242
- assert_raises RuntimeError do
249
+ assert_raises JSON::Schema::ValidationError, "The property '#/message' was not of a minimum string length of 1" do
243
250
  @logger.info('')
244
251
  end
245
252
  end
@@ -352,6 +359,27 @@ describe Twiglet::Logger do
352
359
  end
353
360
  end
354
361
 
362
+ describe 'configuring error response' do
363
+ it 'blows up by default' do
364
+ assert_raises JSON::Schema::ValidationError,
365
+ "The property '#/message' of type boolean did not match the following type: string" do
366
+ @logger.debug(message: true)
367
+ end
368
+ end
369
+
370
+ it 'silently swallows errors when configured to do so' do
371
+ mock = Minitest::Mock.new
372
+
373
+ @logger.configure_validation_error_response do |_e|
374
+ mock.notify_error("Logging schema validation error")
375
+ end
376
+
377
+ mock.expect(:notify_error, nil, ["Logging schema validation error"])
378
+ nonconformant_log = { message: true }
379
+ @logger.debug(nonconformant_log)
380
+ end
381
+ end
382
+
355
383
  private
356
384
 
357
385
  def read_json(buffer)
data/test/message_test.rb CHANGED
@@ -2,24 +2,6 @@ require 'minitest/autorun'
2
2
  require_relative '../lib/twiglet/message'
3
3
 
4
4
  describe Twiglet::Message do
5
- it 'raises if message is empty' do
6
- assert_raises RuntimeError do
7
- Twiglet::Message.new(' ')
8
- end
9
- end
10
-
11
- it 'raises if message is not provided' do
12
- assert_raises RuntimeError do
13
- Twiglet::Message.new(foo: 'bar')
14
- end
15
- end
16
-
17
- it 'raises on unrecognized inputs' do
18
- assert_raises RuntimeError do
19
- Twiglet::Message.new(OpenStruct.new(message: 'hello'))
20
- end
21
- end
22
-
23
5
  it 'returns a message hash from a string' do
24
6
  assert_equal Twiglet::Message.new('hello, world'), { message: 'hello, world' }
25
7
  end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'minitest/autorun'
4
+ require_relative '../lib/twiglet/validator'
5
+
6
+ describe Twiglet::Validator do
7
+ let(:valid)
8
+
9
+ before do
10
+ schema = {
11
+ "type" => "object",
12
+ "required" => ["message"],
13
+ "properties" => {
14
+ "message" => {
15
+ "type" => "string"
16
+ }
17
+ }
18
+ }
19
+
20
+ @validator = Twiglet::Validator.new(schema)
21
+ end
22
+
23
+ it 'does not raise when validation passes' do
24
+ assert_equal(@validator.validate({ message: 'this is my message', foo: 'bar' }), true)
25
+ end
26
+
27
+ it 'raises when validation fails' do
28
+ assert_raises JSON::Schema::ValidationError do
29
+ @validator.validate({ message: true })
30
+ end
31
+ end
32
+
33
+ it 'is a no-op when validator is configured to swallow errors' do
34
+ @validator.custom_error_handler = ->(e) { puts e }
35
+
36
+ assert_nil(@validator.validate({ message: true }))
37
+ end
38
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: twiglet
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.8
4
+ version: 3.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Simply Business
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-02-11 00:00:00.000000000 Z
11
+ date: 2021-02-15 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Like a log, only smaller.
14
14
  email:
@@ -42,12 +42,15 @@ files:
42
42
  - lib/twiglet/formatter.rb
43
43
  - lib/twiglet/logger.rb
44
44
  - lib/twiglet/message.rb
45
+ - lib/twiglet/validation_schema.json
46
+ - lib/twiglet/validator.rb
45
47
  - lib/twiglet/version.rb
46
48
  - test/formatter_test.rb
47
49
  - test/hash_extensions_test.rb
48
50
  - test/logger_test.rb
49
51
  - test/message_test.rb
50
52
  - test/test_coverage.rb
53
+ - test/validator_test.rb
51
54
  - twiglet.gemspec
52
55
  homepage: https://github.com/simplybusiness/twiglet-ruby
53
56
  licenses: