twiglet 3.0.8 → 3.2.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: 6970a1b98fbd77737c3742429691e3c03c7966e894e2f6a523824df785941858
4
+ data.tar.gz: 3a878416f432a4c9e38b0299afb6204fd2202168646d028298d40ab2c6cc7746
5
5
  SHA512:
6
- metadata.gz: d5e68a4b343c861c7236e4a301365d738221bbe5eb0b23426e3cba84fad2358046d1b868bef5248c1c3398f4c2cfd132445259b7db976937024f73663699c2d4
7
- data.tar.gz: 28b619fd981103d5baea53d07e032e52e21cf88e84b5229d1ca1ff61cedc284d6825ebf26fb977f060d3af4c34f276b8a69c733e66321952a726d452dc82164b
6
+ metadata.gz: 8a2e0c5bd321f495f4cf2c5d69a8caaff889e352305c5a900c1c7415e8637d0127729c91bd030353c1dd3b56e9c5623880f46849d3ff3eac1d502c3df8752c54
7
+ data.tar.gz: 0076c02f4070de277e488278f9f858d529b0ab39d8f8c07bc59a9d583c40421f0231b769a7ffb6a6ad1b422ed17f22ee4cd2a7eb68e436dc3b2b6c70c0246c8e
@@ -1,4 +1,4 @@
1
- name: "version update action"
1
+ name: "dobby action"
2
2
  on:
3
3
  issue_comment:
4
4
  types: [created]
@@ -8,8 +8,9 @@ jobs:
8
8
  if: startsWith(github.event.comment.body, '/dobby')
9
9
 
10
10
  steps:
11
- - name: 'bump version'
12
- uses: simplybusiness/dobby@v1.0.0
11
+ - name: bump version
12
+ uses: simplybusiness/dobby@v2.0.0
13
13
  env:
14
- ACCESS_TOKEN: ${{ secrets.GITHUB_TOKEN }}
14
+ DOBBY_APP_ID: ${{ secrets.DOBBY_APP_ID }}
15
+ DOBBY_PRIVATE_KEY: ${{ secrets.DOBBY_PRIVATE_KEY }}
15
16
  VERSION_FILE_PATH: lib/twiglet/version.rb
data/Gemfile CHANGED
@@ -1,9 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  source 'https://rubygems.org'
4
- group :development, :test do
5
- gem 'minitest'
6
- gem 'rake'
7
- gem 'simplecov', '0.17.1'
8
- gem 'simplycop'
9
- end
4
+
5
+ gemspec
@@ -7,18 +7,21 @@ module Twiglet
7
7
  Hash.include HashExtensions
8
8
 
9
9
  def initialize(service_name,
10
- default_properties: {},
10
+ validator:, default_properties: {},
11
11
  now: -> { Time.now.utc })
12
12
  @service_name = service_name
13
13
  @now = now
14
14
  @default_properties = default_properties
15
+ @validator = validator
15
16
 
16
17
  super()
17
18
  end
18
19
 
19
20
  def call(severity, _time, _progname, msg)
20
21
  level = severity.downcase
21
- log(level: level, message: Message.new(msg))
22
+ message = Message.new(msg)
23
+ @validator.validate(message)
24
+ log(level: level, message: message)
22
25
  end
23
26
 
24
27
  private
@@ -2,10 +2,10 @@
2
2
 
3
3
  require 'logger'
4
4
  require 'time'
5
- require 'json'
6
5
  require_relative 'formatter'
7
6
  require_relative '../hash_extensions'
8
7
  require_relative 'message'
8
+ require_relative 'validator'
9
9
 
10
10
  module Twiglet
11
11
  class Logger < ::Logger
@@ -13,23 +13,35 @@ module Twiglet
13
13
 
14
14
  def initialize(
15
15
  service_name,
16
- default_properties: {},
17
- now: -> { Time.now.utc },
18
- output: $stdout,
19
- level: Logger::DEBUG
16
+ **args
20
17
  )
21
18
  @service_name = service_name
22
- @now = now
23
- @output = output
24
- @level = level
19
+ default_properties = args.delete(:default_properties) || {}
20
+ @args = args
21
+
22
+ now = args.fetch(:now, -> { Time.now.utc })
23
+ output = args.fetch(:output, $stdout)
24
+ level = args.fetch(:level, Logger::DEBUG)
25
+ validation_schema = args.fetch(:validation_schema, File.read("#{__dir__}/validation_schema.json"))
25
26
 
26
27
  raise 'Service name is mandatory' \
27
28
  unless service_name.is_a?(String) && !service_name.strip.empty?
28
29
 
29
- formatter = Twiglet::Formatter.new(service_name, default_properties: default_properties, now: now)
30
+ @validator = Validator.new(validation_schema)
31
+
32
+ formatter = Twiglet::Formatter.new(
33
+ service_name,
34
+ default_properties: default_properties,
35
+ now: now,
36
+ validator: @validator
37
+ )
30
38
  super(output, formatter: formatter, level: level)
31
39
  end
32
40
 
41
+ def configure_validation_error_response(&block)
42
+ @validator.custom_error_handler = block
43
+ end
44
+
33
45
  def error(message = nil, error = nil, &block)
34
46
  if error
35
47
  error_fields = {
@@ -48,10 +60,7 @@ module Twiglet
48
60
  def with(default_properties)
49
61
  Logger.new(
50
62
  @service_name,
51
- default_properties: default_properties,
52
- now: @now,
53
- output: @output,
54
- level: @level
63
+ **@args.merge(default_properties: default_properties)
55
64
  )
56
65
  end
57
66
 
@@ -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,21 @@
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 = JSON.parse(schema)
12
+ @custom_error_handler = ->(e) { raise e }
13
+ end
14
+
15
+ def validate(message)
16
+ JSON::Validator.validate!(@schema, message)
17
+ rescue JSON::Schema::ValidationError => e
18
+ custom_error_handler.call(e)
19
+ end
20
+ end
21
+ 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.2.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({}.to_json))
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,82 @@ 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
+
383
+ describe 'validation schema' do
384
+ before do
385
+ validation_schema = <<-JSON
386
+ {
387
+ "type": "object",
388
+ "required": ["pet"],
389
+ "properties": {
390
+ "pet": {
391
+ "type": "object",
392
+ "required": ["name", "best_boy_or_girl?"],
393
+ "properties": {
394
+ "name": {
395
+ "type": "string",
396
+ "minLength": 1
397
+ },
398
+ "best_boy_or_girl?": {
399
+ "type": "boolean"
400
+ }
401
+ }
402
+ }
403
+ }
404
+ }
405
+ JSON
406
+
407
+ @logger = Twiglet::Logger.new(
408
+ 'petshop',
409
+ now: @now,
410
+ output: @buffer,
411
+ validation_schema: validation_schema
412
+ )
413
+ end
414
+
415
+ it 'allows for the configuration of custom validation rules' do
416
+ @logger.debug(
417
+ {
418
+ pet: { name: 'Davis', best_boy_or_girl?: true, species: 'dog' }
419
+ }
420
+ )
421
+ log = read_json(@buffer)
422
+
423
+ assert_equal true, log[:pet][:best_boy_or_girl?]
424
+ end
425
+
426
+ it 'raises when custom validation rules are broken' do
427
+ nonconformant = {
428
+ pet: { name: 'Davis' }
429
+ }
430
+
431
+ assert_raises JSON::Schema::ValidationError,
432
+ "The property '#/pet' did not contain a required property of 'best_boy_or_girl?'" do
433
+ @logger.debug(nonconformant)
434
+ end
435
+ end
436
+ end
437
+
355
438
  private
356
439
 
357
440
  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.to_json)
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
data/twiglet.gemspec CHANGED
@@ -21,4 +21,10 @@ Gem::Specification.new do |gem|
21
21
  gem.required_ruby_version = '>= 2.6'
22
22
 
23
23
  gem.license = 'Copyright SimplyBusiness'
24
+
25
+ gem.add_runtime_dependency 'json-schema'
26
+ gem.add_development_dependency 'minitest'
27
+ gem.add_development_dependency 'rake'
28
+ gem.add_development_dependency 'simplecov', '0.17.1'
29
+ gem.add_development_dependency 'simplycop'
24
30
  end
metadata CHANGED
@@ -1,15 +1,85 @@
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.2.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
12
- dependencies: []
11
+ date: 2021-02-16 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: json-schema
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: minitest
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: simplecov
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '='
60
+ - !ruby/object:Gem::Version
61
+ version: 0.17.1
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '='
67
+ - !ruby/object:Gem::Version
68
+ version: 0.17.1
69
+ - !ruby/object:Gem::Dependency
70
+ name: simplycop
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
13
83
  description: Like a log, only smaller.
14
84
  email:
15
85
  - tech@simplybusiness.co.uk
@@ -19,10 +89,10 @@ extra_rdoc_files: []
19
89
  files:
20
90
  - ".github/CODEOWNERS"
21
91
  - ".github/dependabot.yml"
92
+ - ".github/workflows/dobby-actions.yml"
22
93
  - ".github/workflows/gem-publish.yml"
23
94
  - ".github/workflows/ruby.yml"
24
95
  - ".github/workflows/version-forget-me-not.yml"
25
- - ".github/workflows/version-update.yml"
26
96
  - ".gitignore"
27
97
  - ".rubocop.yml"
28
98
  - ".ruby-version"
@@ -42,12 +112,15 @@ files:
42
112
  - lib/twiglet/formatter.rb
43
113
  - lib/twiglet/logger.rb
44
114
  - lib/twiglet/message.rb
115
+ - lib/twiglet/validation_schema.json
116
+ - lib/twiglet/validator.rb
45
117
  - lib/twiglet/version.rb
46
118
  - test/formatter_test.rb
47
119
  - test/hash_extensions_test.rb
48
120
  - test/logger_test.rb
49
121
  - test/message_test.rb
50
122
  - test/test_coverage.rb
123
+ - test/validator_test.rb
51
124
  - twiglet.gemspec
52
125
  homepage: https://github.com/simplybusiness/twiglet-ruby
53
126
  licenses: