twiglet 3.0.5 → 3.1.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 44c51be445d04ae84c5289cbeaa7508b9d3f733ce7b8d5001c7925bd0ed44dd4
4
- data.tar.gz: 2f04af22bf6c01fd12c8863e3c9350791d0d6c1ffaaf920797f9430e8e9d1e74
3
+ metadata.gz: 5aa4993d40d0b1f658c6b5f8c79f364e4bc1fef111d094b48446bde0fe7a0701
4
+ data.tar.gz: 8c6dee85c7622b274d570eb400bb8ca065c70e311a11699a990bc5548a2479d7
5
5
  SHA512:
6
- metadata.gz: 602d459a46ecb262457ba2b4cd3cb3900c3cdc77b3663087717018911bb61ef921e0892b7a24749182264e1e1ee970b9c92c72663ef0ed587a408de3863f8140
7
- data.tar.gz: 342cc5eebdc1054be2ae04c434218eeb93902328bfbed79cf66df4d3445a6604206084d88aae4a5998656138e3edec4eb37421079c3d8a78a6a27f822f399860
6
+ metadata.gz: 863b7bb7f3848a2345ffe5c8639add2f77fa7849d4f29d873bb25eb97c3510a2edec4c78cc6a95b343c1ee305571ca43534f88b494d983e0f00b23e7dd5ae5d5
7
+ data.tar.gz: 5827e6d6eee5fb90ca06822f7d5df2ac757a00f5d281453f74c3f3f3efcfee603639003eb711ccb026738ff9cd35e54405fc2ee125eb44b13f413f69f365f4c7
@@ -0,0 +1,16 @@
1
+ name: "dobby action"
2
+ on:
3
+ issue_comment:
4
+ types: [created]
5
+ jobs:
6
+ pr_commented:
7
+ runs-on: ubuntu-20.04
8
+ if: startsWith(github.event.comment.body, '/dobby')
9
+
10
+ steps:
11
+ - name: bump version
12
+ uses: simplybusiness/dobby@v2.0.0
13
+ env:
14
+ DOBBY_APP_ID: ${{ secrets.DOBBY_APP_ID }}
15
+ DOBBY_PRIVATE_KEY: ${{ secrets.DOBBY_PRIVATE_KEY }}
16
+ VERSION_FILE_PATH: lib/twiglet/version.rb
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'
data/README.md CHANGED
@@ -110,7 +110,7 @@ This writes:
110
110
  It may be that when making a series of logs that write information about a single event, you may want to avoid duplication by creating an event specific logger that includes the context:
111
111
 
112
112
  ```ruby
113
- request_log = logger.with({ event: { action: 'HTTP request'}, trace: { id: '1c8a5fb2-fecd-44d8-92a4-449eb2ce4dcb' }})
113
+ request_logger = logger.with({ event: { action: 'HTTP request'}, trace: { id: '1c8a5fb2-fecd-44d8-92a4-449eb2ce4dcb' }})
114
114
  ```
115
115
 
116
116
  This can be used like any other Logger instance:
@@ -140,6 +140,10 @@ To access the formatter:
140
140
  logger.formatter
141
141
  ```
142
142
 
143
+ ### HTTP Request Logging
144
+ Take a look at this sample [Rack application](examples/rack/example_rack_app.rb#L15) with an ECS compliant
145
+ [request logger](/examples/rack/request_logger.rb) as a template when configuring your own request logging middleware with Twiglet.
146
+
143
147
  ## Use of dotted keys (DEPRECATED)
144
148
 
145
149
  Writing nested json objects could be confusing. This library has a built-in feature to convert dotted keys into nested objects, so if you log like this:
data/example_app.rb CHANGED
@@ -7,28 +7,32 @@ PORT = 8080
7
7
  logger = Twiglet::Logger.new('petshop')
8
8
 
9
9
  # Start our petshop
10
- logger.info({
11
- event: {
12
- action: 'startup'
13
- },
14
- message: "Ready to go, listening on port #{PORT}",
15
- server: {
16
- port: PORT
10
+ logger.info(
11
+ {
12
+ event: {
13
+ action: 'startup'
14
+ },
15
+ message: "Ready to go, listening on port #{PORT}",
16
+ server: {
17
+ port: PORT
18
+ }
17
19
  }
18
- })
20
+ )
19
21
 
20
22
  # Use text logging
21
23
  logger.info("Ready to go, listening on port #{PORT}")
22
24
  #
23
25
  # We get a request
24
- request_logger = logger.with({
25
- event: {
26
- action: 'HTTP request'
27
- },
28
- trace: {
29
- id: '126bb6fa-28a2-470f-b013-eefbf9182b2d'
26
+ request_logger = logger.with(
27
+ {
28
+ event: {
29
+ action: 'HTTP request'
30
+ },
31
+ trace: {
32
+ id: '126bb6fa-28a2-470f-b013-eefbf9182b2d'
33
+ }
30
34
  }
31
- })
35
+ )
32
36
 
33
37
  # Oh noes!
34
38
  db_err = StandardError.new('Connection timed-out')
@@ -36,17 +40,19 @@ db_err = StandardError.new('Connection timed-out')
36
40
  request_logger.error({ message: 'DB connection failed.' }, db_err) if db_err
37
41
 
38
42
  # We return an error to the requester
39
- request_logger.info({
40
- message: 'Internal Server Error',
41
- http: {
42
- request: {
43
- method: 'get'
44
- },
45
- response: {
46
- status_code: 500
43
+ request_logger.info(
44
+ {
45
+ message: 'Internal Server Error',
46
+ http: {
47
+ request: {
48
+ method: 'get'
49
+ },
50
+ response: {
51
+ status_code: 500
52
+ }
47
53
  }
48
54
  }
49
- })
55
+ )
50
56
 
51
57
  # Logging with an empty message is an anti-pattern and is therefore forbidden
52
58
  # Both of the following lines would throw an error
@@ -15,10 +15,12 @@ describe RequestLogger do
15
15
  end
16
16
 
17
17
  it 'logs the request data' do
18
- request.get("/some/path?some_var=1", 'HTTP_ACCEPT' => 'application/json',
19
- 'REMOTE_ADDR' => '0.0.0.0',
20
- 'HTTP_VERSION' => 'HTTP/1.1',
21
- 'HTTP_USER_AGENT' => 'Mozilla/5.0 (Macintosh)')
18
+ request.get(
19
+ "/some/path?some_var=1", 'HTTP_ACCEPT' => 'application/json',
20
+ 'REMOTE_ADDR' => '0.0.0.0',
21
+ 'HTTP_VERSION' => 'HTTP/1.1',
22
+ 'HTTP_USER_AGENT' => 'Mozilla/5.0 (Macintosh)'
23
+ )
22
24
  log = JSON.parse(output.string)
23
25
 
24
26
  expected_log = {
@@ -55,7 +57,7 @@ describe RequestLogger do
55
57
  end
56
58
 
57
59
  it 'does not log PII' do
58
- request.post("/user/info", input_data: {credit_card_no: '1234'})
60
+ request.post("/user/info", input_data: { credit_card_no: '1234' })
59
61
  log = output.string
60
62
  assert_includes log, "POST: /user/info"
61
63
  refute_includes log, 'credit_card_no'
@@ -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,16 +26,27 @@ 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("#{__dir__}/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)
34
45
  if error
35
46
  error_fields = {
36
- 'error': {
37
- 'type': error.class,
38
- 'message': error.message
47
+ error: {
48
+ type: error.class,
49
+ message: error.message
39
50
  }
40
51
  }
41
52
  add_stack_trace(error_fields, error)
@@ -46,11 +57,13 @@ module Twiglet
46
57
  end
47
58
 
48
59
  def with(default_properties)
49
- Logger.new(@service_name,
50
- default_properties: default_properties,
51
- now: @now,
52
- output: @output,
53
- level: @level)
60
+ Logger.new(
61
+ @service_name,
62
+ default_properties: default_properties,
63
+ now: @now,
64
+ output: @output,
65
+ level: @level
66
+ )
54
67
  end
55
68
 
56
69
  alias_method :warning, :warn
@@ -1,24 +1,15 @@
1
1
  module Twiglet
2
2
  class Message < Hash
3
3
  def initialize(msg)
4
+ super
4
5
  case msg
5
6
  when String
6
7
  self[:message] = msg
7
8
  when Hash
8
9
  replace(msg.transform_keys!(&:to_sym))
10
+ else
11
+ super(msg)
9
12
  end
10
-
11
- validate!
12
- end
13
-
14
- private
15
-
16
- def validate!
17
- raise 'Message must be initialized with a String or a non-empty Hash' if empty?
18
-
19
- raise 'Log object must have a \'message\' property' unless self[:message]
20
-
21
- raise 'The \'message\' property of the log object must not be empty' if self[:message].strip.empty?
22
13
  end
23
14
  end
24
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.5'
4
+ VERSION = '3.1.2'
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 = [
@@ -18,9 +19,11 @@ describe Twiglet::Logger do
18
19
  before do
19
20
  @now = -> { Time.utc(2020, 5, 11, 15, 1, 1) }
20
21
  @buffer = StringIO.new
21
- @logger = Twiglet::Logger.new('petshop',
22
- now: @now,
23
- output: @buffer)
22
+ @logger = Twiglet::Logger.new(
23
+ 'petshop',
24
+ now: @now,
25
+ output: @buffer
26
+ )
24
27
  end
25
28
 
26
29
  it 'should throw an error with an empty service name' do
@@ -30,21 +33,29 @@ describe Twiglet::Logger do
30
33
  end
31
34
 
32
35
  it 'conforms to the standard Ruby Logger API' do
33
- [:debug, :debug?, :info, :info?, :warn, :warn?, :fatal, :fatal?, :error, :error?,
34
- :level, :level=, :sev_threshold=].each do |call|
36
+ [
37
+ :debug, :debug?, :info, :info?, :warn, :warn?, :fatal, :fatal?, :error, :error?,
38
+ :level, :level=, :sev_threshold=
39
+ ].each do |call|
35
40
  assert @logger.respond_to?(call), "Logger does not respond to #{call}"
36
41
  end
37
42
  end
38
43
 
39
44
  describe 'JSON logging' do
40
45
  it 'should throw an error with an empty message' do
41
- assert_raises RuntimeError do
42
- @logger.info({message: ''})
46
+ assert_raises JSON::Schema::ValidationError, "The property '#/message' was not of a minimum string length of 1" do
47
+ @logger.info({ message: '' })
48
+ end
49
+ end
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' })
43
54
  end
44
55
  end
45
56
 
46
57
  it 'should log mandatory attributes' do
47
- @logger.error({message: 'Out of pets exception'})
58
+ @logger.error({ message: 'Out of pets exception' })
48
59
  actual_log = read_json(@buffer)
49
60
 
50
61
  expected_log = {
@@ -65,9 +76,13 @@ describe Twiglet::Logger do
65
76
  end
66
77
 
67
78
  it 'should log the provided message' do
68
- @logger.error({event:
69
- {action: 'exception'},
70
- message: 'Emergency! Emergency!'})
79
+ @logger.error(
80
+ {
81
+ event:
82
+ { action: 'exception' },
83
+ message: 'Emergency! Emergency!'
84
+ }
85
+ )
71
86
  log = read_json(@buffer)
72
87
 
73
88
  assert_equal 'exception', log[:event][:action]
@@ -82,17 +97,19 @@ describe Twiglet::Logger do
82
97
  service: {
83
98
  type: 'shop'
84
99
  },
85
- request: {method: 'get'},
86
- response: {status_code: 200}
100
+ request: { method: 'get' },
101
+ response: { status_code: 200 }
87
102
  }
88
103
 
89
104
  output = StringIO.new
90
- logger = Twiglet::Logger.new('petshop',
91
- now: @now,
92
- output: output,
93
- default_properties: extra_properties)
94
-
95
- logger.error({message: 'GET /cats'})
105
+ logger = Twiglet::Logger.new(
106
+ 'petshop',
107
+ now: @now,
108
+ output: output,
109
+ default_properties: extra_properties
110
+ )
111
+
112
+ logger.error({ message: 'GET /cats' })
96
113
  log = read_json output
97
114
 
98
115
  assert_equal '1c8a5fb2-fecd-44d8-92a4-449eb2ce4dcb', log[:trace][:id]
@@ -104,17 +121,21 @@ describe Twiglet::Logger do
104
121
 
105
122
  it "should be able to add properties with '.with'" do
106
123
  # Let's add some context to this customer journey
107
- purchase_logger = @logger.with({
108
- trace: {id: '1c8a5fb2-fecd-44d8-92a4-449eb2ce4dcb'},
109
- customer: {full_name: 'Freda Bloggs'},
110
- event: {action: 'pet purchase'}
111
- })
124
+ purchase_logger = @logger.with(
125
+ {
126
+ trace: { id: '1c8a5fb2-fecd-44d8-92a4-449eb2ce4dcb' },
127
+ customer: { full_name: 'Freda Bloggs' },
128
+ event: { action: 'pet purchase' }
129
+ }
130
+ )
112
131
 
113
132
  # do stuff
114
- purchase_logger.info({
115
- message: 'customer bought a dog',
116
- pet: {name: 'Barker', species: 'dog', breed: 'Bitsa'}
117
- })
133
+ purchase_logger.info(
134
+ {
135
+ message: 'customer bought a dog',
136
+ pet: { name: 'Barker', species: 'dog', breed: 'Bitsa' }
137
+ }
138
+ )
118
139
 
119
140
  log = read_json @buffer
120
141
 
@@ -135,8 +156,8 @@ describe Twiglet::Logger do
135
156
  end
136
157
 
137
158
  it "should log multiple messages properly" do
138
- @logger.debug({message: 'hi'})
139
- @logger.info({message: 'there'})
159
+ @logger.debug({ message: 'hi' })
160
+ @logger.info({ message: 'there' })
140
161
 
141
162
  expected_output =
142
163
  '{"ecs":{"version":"1.5.0"},"@timestamp":"2020-05-11T15:01:01.000Z",'\
@@ -174,7 +195,7 @@ describe Twiglet::Logger do
174
195
 
175
196
  LEVELS.each do |attrs|
176
197
  it "should correctly log level when calling #{attrs[:method]}" do
177
- @logger.public_send(attrs[:method], {message: 'a log message'})
198
+ @logger.public_send(attrs[:method], { message: 'a log message' })
178
199
  actual_log = read_json(@buffer)
179
200
 
180
201
  assert_equal attrs[:level], actual_log[:log][:level]
@@ -188,7 +209,7 @@ describe Twiglet::Logger do
188
209
  begin
189
210
  1 / 0
190
211
  rescue StandardError => e
191
- @logger.error({message: 'Artificially raised exception'}, e)
212
+ @logger.error({ message: 'Artificially raised exception' }, e)
192
213
  end
193
214
 
194
215
  actual_log = read_json(@buffer)
@@ -201,7 +222,7 @@ describe Twiglet::Logger do
201
222
 
202
223
  it 'should log an error without backtrace' do
203
224
  e = StandardError.new('Connection timed-out')
204
- @logger.error({message: 'Artificially raised exception'}, e)
225
+ @logger.error({ message: 'Artificially raised exception' }, e)
205
226
 
206
227
  actual_log = read_json(@buffer)
207
228
 
@@ -225,7 +246,7 @@ describe Twiglet::Logger do
225
246
 
226
247
  describe 'text logging' do
227
248
  it 'should throw an error with an empty message' do
228
- assert_raises RuntimeError do
249
+ assert_raises JSON::Schema::ValidationError, "The property '#/message' was not of a minimum string length of 1" do
229
250
  @logger.info('')
230
251
  end
231
252
  end
@@ -284,13 +305,15 @@ describe Twiglet::Logger do
284
305
 
285
306
  describe 'dotted keys' do
286
307
  it 'should be able to convert dotted keys to nested objects' do
287
- @logger.debug({
288
- "trace.id": '1c8a5fb2-fecd-44d8-92a4-449eb2ce4dcb',
289
- message: 'customer bought a dog',
290
- "pet.name": 'Barker',
291
- "pet.species": 'dog',
292
- "pet.breed": 'Bitsa'
293
- })
308
+ @logger.debug(
309
+ {
310
+ "trace.id": '1c8a5fb2-fecd-44d8-92a4-449eb2ce4dcb',
311
+ message: 'customer bought a dog',
312
+ "pet.name": 'Barker',
313
+ "pet.species": 'dog',
314
+ "pet.breed": 'Bitsa'
315
+ }
316
+ )
294
317
  log = read_json(@buffer)
295
318
 
296
319
  assert_equal '1c8a5fb2-fecd-44d8-92a4-449eb2ce4dcb', log[:trace][:id]
@@ -301,12 +324,14 @@ describe Twiglet::Logger do
301
324
  end
302
325
 
303
326
  it 'should be able to mix dotted keys and nested objects' do
304
- @logger.debug({
305
- "trace.id": '1c8a5fb2-fecd-44d8-92a4-449eb2ce4dcb',
306
- message: 'customer bought a dog',
307
- pet: {name: 'Barker', breed: 'Bitsa'},
308
- "pet.species": 'dog'
309
- })
327
+ @logger.debug(
328
+ {
329
+ "trace.id": '1c8a5fb2-fecd-44d8-92a4-449eb2ce4dcb',
330
+ message: 'customer bought a dog',
331
+ pet: { name: 'Barker', breed: 'Bitsa' },
332
+ "pet.species": 'dog'
333
+ }
334
+ )
310
335
  log = read_json(@buffer)
311
336
 
312
337
  assert_equal '1c8a5fb2-fecd-44d8-92a4-449eb2ce4dcb', log[:trace][:id]
@@ -334,6 +359,27 @@ describe Twiglet::Logger do
334
359
  end
335
360
  end
336
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
+
337
383
  private
338
384
 
339
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
@@ -7,7 +7,11 @@ SimpleCov.start do
7
7
  if ENV['CI']
8
8
  formatter SimpleCov::Formatter::SimpleFormatter
9
9
  else
10
- formatter SimpleCov::Formatter::MultiFormatter.new([SimpleCov::Formatter::SimpleFormatter,
11
- SimpleCov::Formatter::HTMLFormatter])
10
+ formatter SimpleCov::Formatter::MultiFormatter.new(
11
+ [
12
+ SimpleCov::Formatter::SimpleFormatter,
13
+ SimpleCov::Formatter::HTMLFormatter
14
+ ]
15
+ )
12
16
  end
13
17
  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.5
4
+ version: 3.1.2
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-01-25 00:00:00.000000000 Z
11
+ date: 2021-02-16 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Like a log, only smaller.
14
14
  email:
@@ -19,6 +19,7 @@ extra_rdoc_files: []
19
19
  files:
20
20
  - ".github/CODEOWNERS"
21
21
  - ".github/dependabot.yml"
22
+ - ".github/workflows/dobby-actions.yml"
22
23
  - ".github/workflows/gem-publish.yml"
23
24
  - ".github/workflows/ruby.yml"
24
25
  - ".github/workflows/version-forget-me-not.yml"
@@ -41,12 +42,15 @@ files:
41
42
  - lib/twiglet/formatter.rb
42
43
  - lib/twiglet/logger.rb
43
44
  - lib/twiglet/message.rb
45
+ - lib/twiglet/validation_schema.json
46
+ - lib/twiglet/validator.rb
44
47
  - lib/twiglet/version.rb
45
48
  - test/formatter_test.rb
46
49
  - test/hash_extensions_test.rb
47
50
  - test/logger_test.rb
48
51
  - test/message_test.rb
49
52
  - test/test_coverage.rb
53
+ - test/validator_test.rb
50
54
  - twiglet.gemspec
51
55
  homepage: https://github.com/simplybusiness/twiglet-ruby
52
56
  licenses:
@@ -67,7 +71,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
67
71
  - !ruby/object:Gem::Version
68
72
  version: '0'
69
73
  requirements: []
70
- rubygems_version: 3.1.4
74
+ rubygems_version: 3.2.3
71
75
  signing_key:
72
76
  specification_version: 4
73
77
  summary: Twiglet