Skip to content

Commit

Permalink
Merge branch 'master' of github.com:slt/pact-ruby into slt-master
Browse files Browse the repository at this point in the history
  • Loading branch information
YOU54F committed Aug 9, 2024
2 parents 17ce64b + f55adf1 commit 8b5fc68
Show file tree
Hide file tree
Showing 28 changed files with 786 additions and 12 deletions.
16 changes: 16 additions & 0 deletions lib/pact/provider/generator/boolean.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
module Pact
module Provider
module Generator
# Boolean provides the boolean generator which will give a true or false value
class Boolean
def can_generate?(hash)
hash.key?('type') && hash['type'] == 'Boolean'
end

def call(_hash, _params = nil, _example_value = nil)
[true, false].sample
end
end
end
end
end
64 changes: 64 additions & 0 deletions lib/pact/provider/generator/date.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
require 'date'

module Pact
module Provider
module Generator
# Date provides the time generator which will give the current date in the defined format
class Date
def can_generate?(hash)
hash.key?('type') && hash['type'] == type
end

def call(hash, _params = nil, _example_value = nil)
format = hash['format'] || default_format
::Time.now.strftime(convert_from_java_simple_date_format(format))
end

def type
'Date'
end

def default_format
'yyyy-MM-dd'
end

# Format for the pact specficiation should be the Java DateTimeFormmater
# This tries to convert to something Ruby can format.
def convert_from_java_simple_date_format(format)
# Year
format.sub!(/(?<!%)y{4,}/, '%Y')
format.sub!(/(?<!%)y{1,}/, '%y')

# Month
format.sub!(/(?<!%)M{4,}/, '%B')
format.sub!(/(?<!%)M{3}/, '%b')
format.sub!(/(?<!%)M{1,2}/, '%m')

# Week
format.sub!(/(?<!%)M{1,}/, '%W')

# Day
format.sub!(/(?<!%)D{1,}/, '%j')
format.sub!(/(?<!%)d{1,}/, '%d')
format.sub!(/(?<!%)E{4,}/, '%A')
format.sub!(/(?<!%)D{1,}/, '%a')
format.sub!(/(?<!%)u{1,}/, '%u')

# Time
format.sub!(/(?<!%)a{1,}/, '%p')
format.sub!(/(?<!%)k{1,}/, '%H')
format.sub!(/(?<!%)n{1,}/, '%M')
format.sub!(/(?<!%)s{1,}/, '%S')
format.sub!(/(?<!%)S{1,}/, '%L')

# Timezone
format.sub!(/(?<!%)z{1,}/, '%z')
format.sub!(/(?<!%)Z{1,}/, '%z')
format.sub!(/(?<!%)X{1,}/, '%Z')

format
end
end
end
end
end
18 changes: 18 additions & 0 deletions lib/pact/provider/generator/datetime.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
require 'date'

module Pact
module Provider
module Generator
# DateTime provides the time generator which will give the current date time in the defined format
class DateTime < Date
def type
'DateTime'
end

def default_format
'yyyy-MM-dd HH:mm'
end
end
end
end
end
57 changes: 57 additions & 0 deletions lib/pact/provider/generator/provider_state.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
require 'pact/logging'

module Pact
module Provider
module Generator
# ProviderState provides the provider state generator which will inject
# values provided by the provider state setup url.
class ProviderState
include Pact::Logging

# rewrite of https://github.com/DiUS/pact-jvm/blob/master/core/support/src/main/kotlin/au/com/dius/pact/core/support/expressions/ExpressionParser.kt#L27
VALUES_SEPARATOR = ','
START_EXPRESSION = "\${"
END_EXPRESSION = '}'

def can_generate?(hash)
hash.key?('type') && hash['type'] == 'ProviderState'
end

def call(hash, params = nil, _example_value = nil)
params ||= {}
parse_expression hash['expression'], params
end

def parse_expression(expression, params)
return_string = []
buffer = expression
# initial value
position = buffer.index(START_EXPRESSION)

while position && position >= 0
if position.positive?
# add string
return_string.push(buffer[0...position])
end
end_position = buffer.index(END_EXPRESSION, position)
raise 'Missing closing brace in expression string' if !end_position || end_position.negative?

variable = buffer[position + 2...end_position]

if !params[variable]
logger.info "Could not subsitute provider state key #{variable}, have #{params}"
end

expression = params[variable] || ''
return_string.push(expression)

buffer = buffer[end_position + 1...-1]
position = buffer.index(START_EXPRESSION)
end

return_string.join('')
end
end
end
end
end
39 changes: 39 additions & 0 deletions lib/pact/provider/generator/random_decimal.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
require 'bigdecimal'

module Pact
module Provider
module Generator
# RandomDecimal provides the random decimal generator which will generate a decimal value of digits length
class RandomDecimal
def can_generate?(hash)
hash.key?('type') && hash['type'] == 'RandomDecimal'
end

def call(hash, _params = nil, _example_value = nil)
digits = hash['digits'] || 6

raise 'RandomDecimalGenerator digits must be > 0, got $digits' if digits < 1

return rand(0..9) if digits == 1

return rand(0..9) + rand(1..9) / 10 if digits == 2

pos = rand(1..digits - 1)
precision = digits - pos
integers = ''
decimals = ''
while pos.positive?
integers += String(rand(1..9))
pos -= 1
end
while precision.positive?
decimals += String(rand(1..9))
precision -= 1
end

Float("#{integers}.#{decimals}")
end
end
end
end
end
21 changes: 21 additions & 0 deletions lib/pact/provider/generator/random_hexadecimal.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
require 'securerandom'

module Pact
module Provider
module Generator
# RandomHexadecimal provides the random hexadecimal generator which will generate a hexadecimal
class RandomHexadecimal
def can_generate?(hash)
hash.key?('type') && hash['type'] == 'RandomHexadecimal'
end

def call(hash, _params = nil, _example_value = nil)
digits = hash['digits'] || 8
bytes = (digits / 2).ceil
string = SecureRandom.hex(bytes)
string[0, digits]
end
end
end
end
end
18 changes: 18 additions & 0 deletions lib/pact/provider/generator/random_int.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
module Pact
module Provider
module Generator
# RandomInt provides the random int generator which generate a random integer, with a min/max
class RandomInt
def can_generate?(hash)
hash.key?('type') && hash['type'] == 'RandomInt'
end

def call(hash, _params = nil, _example_value = nil)
min = hash['min'] || 0
max = hash['max'] || 2_147_483_647
rand(min..max)
end
end
end
end
end
18 changes: 18 additions & 0 deletions lib/pact/provider/generator/random_string.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
module Pact
module Provider
module Generator
# RandomString provides the random string generator which generate a random string of size length
class RandomString
def can_generate?(hash)
hash.key?('type') && hash['type'] == 'RandomString'
end

def call(hash, _params = nil, _example_value = nil)
size = hash['size'] || 20
string = rand(36**(size + 2)).to_s(36)
string[0, size]
end
end
end
end
end
19 changes: 19 additions & 0 deletions lib/pact/provider/generator/regex.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
require 'string_pattern'

module Pact
module Provider
module Generator
# Regex provides the regex generator which will generate a value based on the regex pattern provided
class Regex
def can_generate?(hash)
hash.key?('type') && hash['type'] == 'Regex'
end

def call(hash, _params = nil, _example_value = nil)
pattern = hash['pattern'] || ''
StringPattern.generate(Regexp.new(pattern))
end
end
end
end
end
18 changes: 18 additions & 0 deletions lib/pact/provider/generator/time.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
require 'date'

module Pact
module Provider
module Generator
# Time provides the time generator which will give the current time in the defined format
class Time < Date
def type
'Time'
end

def default_format
'HH:mm'
end
end
end
end
end
21 changes: 21 additions & 0 deletions lib/pact/provider/generator/uuid.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
require 'securerandom'

module Pact
module Provider
module Generator
# Uuid provides the uuid generator
class Uuid
def can_generate?(hash)
hash.key?('type') && hash['type'] == 'Uuid'
end

# If we had the example value, we could determine what type of uuid
# to send, this is what pact-jvm does
# See https://github.com/pact-foundation/pact-jvm/blob/master/core/model/src/main/kotlin/au/com/dius/pact/core/model/generators/Generator.kt
def call(_hash, _params = nil, _example_value = nil)
SecureRandom.uuid
end
end
end
end
end
66 changes: 66 additions & 0 deletions lib/pact/provider/generators.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
require 'pact/provider/generator/boolean'
require 'pact/provider/generator/date'
require 'pact/provider/generator/datetime'
require 'pact/provider/generator/provider_state'
require 'pact/provider/generator/random_decimal'
require 'pact/provider/generator/random_hexadecimal'
require 'pact/provider/generator/random_int'
require 'pact/provider/generator/random_string'
require 'pact/provider/generator/regex'
require 'pact/provider/generator/time'
require 'pact/provider/generator/uuid'
require 'pact/matching_rules/jsonpath'
require 'pact/matching_rules/v3/extract'
require 'jsonpath'

module Pact
module Provider
class Generators
def self.add_generator(generator)
generators.unshift(generator)
end

def self.generators
@generators ||= []
end

def self.execute_generators(object, state_params = nil, example_value = nil)
generators.each do |parser|
return parser.call(object, state_params, example_value) if parser.can_generate?(object)
end

raise Pact::UnrecognizePactFormatError, "This document does not use a recognised Pact generator: #{object}"
end

def self.apply_generators(expected_request, component, example_value, state_params)
# Latest pact-support is required to have generators exposed
if expected_request.methods.include?(:generators) && expected_request.generators[component]
# Some component will have single generator without selectors, i.e. path
generators = expected_request.generators[component]
if generators.is_a?(Hash) && generators.key?('type')
return execute_generators(generators, state_params, example_value)
end

generators.each do |selector, generator|
val = JsonPath.new(selector).on(example_value)
replace = execute_generators(generator, state_params, val)
example_value = JsonPath.for(example_value).gsub(selector) { |_v| replace }.to_hash
end
end
example_value
end

add_generator(Generator::Boolean.new)
add_generator(Generator::Date.new)
add_generator(Generator::DateTime.new)
add_generator(Generator::ProviderState.new)
add_generator(Generator::RandomDecimal.new)
add_generator(Generator::RandomHexadecimal.new)
add_generator(Generator::RandomInt.new)
add_generator(Generator::RandomString.new)
add_generator(Generator::Regex.new)
add_generator(Generator::Time.new)
add_generator(Generator::Uuid.new)
end
end
end
Loading

0 comments on commit 8b5fc68

Please sign in to comment.