fakeit 0.6.2 → 0.8.1

Sign up to get free protection for your applications and to get access to all the features.
data/docs/random.md CHANGED
@@ -11,7 +11,7 @@ The following Openapi properties are supported in random response generation
11
11
  | |minItems|Default: `1`|
12
12
  | |maxItems|Default: `minItems + 2`|
13
13
  |string|enum| |
14
- | |pattern| |
14
+ | |pattern|Random generated once and cached for performance concern|
15
15
  | |format=uri| |
16
16
  | |format=uuid| |
17
17
  | |format=guid| |
data/fakeit.gemspec CHANGED
@@ -22,20 +22,23 @@ Gem::Specification.new do |spec|
22
22
  spec.executables = 'fakeit'
23
23
  spec.require_paths = ['lib']
24
24
 
25
- spec.required_ruby_version = '>= 2.7.0'
25
+ spec.required_ruby_version = '>= 3.0.0'
26
26
 
27
27
  spec.add_development_dependency 'bundler', '~> 2.0'
28
28
  spec.add_development_dependency 'byebug', '~> 11.0'
29
29
  spec.add_development_dependency 'rack-test', '~> 1.1'
30
- spec.add_development_dependency 'rake', '~> 12.0'
30
+ spec.add_development_dependency 'rake', '~> 13.0'
31
31
  spec.add_development_dependency 'rspec', '~> 3.0'
32
- spec.add_development_dependency 'rubocop', '~> 0.80'
32
+ spec.add_development_dependency 'rubocop', '~> 1.10'
33
+ spec.add_development_dependency 'rubocop-rake', '~> 0.5'
33
34
  spec.add_development_dependency 'simplecov', '~> 0.18'
34
35
 
35
36
  spec.add_dependency 'faker', '2.13.0'
36
- spec.add_dependency 'openapi_parser', '0.11.2'
37
+ spec.add_dependency 'openapi_parser', '0.12.1'
37
38
  spec.add_dependency 'rack', '~> 2.0'
38
39
  spec.add_dependency 'rack-cors', '~> 1.0'
39
40
  spec.add_dependency 'rainbow', '~> 3.0'
41
+ spec.add_dependency 'regexp-examples', '1.5.1'
40
42
  spec.add_dependency 'slop', '~> 4.8'
43
+ spec.add_dependency 'webrick', '~> 1.7'
41
44
  end
data/lib/fakeit.rb CHANGED
@@ -4,17 +4,18 @@ require 'open-uri'
4
4
  require 'base64'
5
5
  require 'openapi_parser'
6
6
  require 'faker'
7
+ require 'regexp-examples'
7
8
  require 'rack'
8
9
  require 'logger'
9
10
  require 'rainbow'
10
11
 
11
- Dir.glob(File.join(File.dirname(__FILE__), 'fakeit', '**/*.rb')).sort.each { require _1 }
12
+ Dir.glob(File.join(File.dirname(__FILE__), 'fakeit', '**/*.rb')).each { require _1 }
12
13
 
13
14
  module Fakeit
14
15
  class << self
15
16
  def build(spec_file, options)
16
17
  Rack::Builder.new do
17
- run App.create(spec_file, options)
18
+ run App::AppBuilder.new(spec_file, options).build
18
19
  end
19
20
  end
20
21
  end
@@ -0,0 +1,23 @@
1
+ module Fakeit
2
+ module App
3
+ class AppBuilder
4
+ def initialize(spec_file, options)
5
+ @config_route = Routes::ConfigRoute.new(options)
6
+ @openapi_route = Routes::OpenapiRoute.new(spec_file)
7
+ end
8
+
9
+ def build
10
+ proc do |env|
11
+ request = Rack::Request.new(env)
12
+
13
+ case request.path_info
14
+ when '/__fakeit_config__'
15
+ @config_route.call(request)
16
+ else
17
+ @openapi_route.call(request, @config_route.options)
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,32 @@
1
+ module Fakeit
2
+ module App
3
+ module Helpers
4
+ class BodyParser
5
+ class << self
6
+ def parse(request)
7
+ case request.media_type
8
+ when %r{^application/.*json}
9
+ { media_type: request.media_type, data: parse_json(request.body.read) }
10
+ when 'multipart/form-data'
11
+ { media_type: request.media_type, data: parse_form_data(request.params) }
12
+ else
13
+ { media_type: request.media_type, data: request.body.read }
14
+ end
15
+ end
16
+
17
+ private
18
+
19
+ def parse_json(body)
20
+ body.empty? ? {} : JSON.parse(body)
21
+ rescue StandardError
22
+ raise Fakeit::Validation::ValidationError, 'Invalid json payload'
23
+ end
24
+
25
+ def parse_form_data(params)
26
+ params.transform_values { |v| v.instance_of?(Hash) && v[:tempfile] ? v[:tempfile].read : v }
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,19 @@
1
+ module Fakeit
2
+ module App
3
+ module Helpers
4
+ class ResponseBuilder
5
+ class << self
6
+ def error(code, err) = [code, { 'Content-Type' => 'application/json' }, [{ message: err.message }.to_json]]
7
+
8
+ def not_found = [404, {}, ['Not Found']]
9
+
10
+ def method_not_allowed = [405, {}, ['Method Not Allowed']]
11
+
12
+ def unsupported_media_type = [415, {}, ['Unsupported Media Type']]
13
+
14
+ def ok(body) = [200, { 'Content-Type' => 'application/json' }, [body.to_json]]
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -14,6 +14,16 @@ module Fakeit
14
14
  def use_static?(type: nil, property: nil)
15
15
  @static || @static_types.include?(type) || @static_properties.include?(property)
16
16
  end
17
+
18
+ def to_hash
19
+ {
20
+ permissive: @permissive,
21
+ use_example: @use_example,
22
+ static: @static,
23
+ static_types: @static_types,
24
+ static_properties: @static_properties
25
+ }
26
+ end
17
27
  end
18
28
  end
19
29
  end
@@ -0,0 +1,36 @@
1
+ module Fakeit
2
+ module App
3
+ module Routes
4
+ class ConfigRoute
5
+ attr_reader :options
6
+
7
+ def initialize(options) = @options = options
8
+
9
+ def call(request)
10
+ case [request.request_method, request.media_type]
11
+ in ['GET', _]
12
+ Fakeit::App::Helpers::ResponseBuilder.ok(@options.to_hash)
13
+ in ['PUT', 'application/json']
14
+ update(request)
15
+ in ['PUT', _]
16
+ Fakeit::App::Helpers::ResponseBuilder.unsupported_media_type
17
+ else
18
+ Fakeit::App::Helpers::ResponseBuilder.method_not_allowed
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ def update(request)
25
+ body = Fakeit::App::Helpers::BodyParser.parse(request)[:data]
26
+ @options = Fakeit::App::Options.new(**body.transform_keys(&:to_sym))
27
+
28
+ Fakeit::App::Helpers::ResponseBuilder.ok(@options.to_hash)
29
+ rescue ArgumentError => e
30
+ Logger.warn(Rainbow(e.message).red)
31
+ Fakeit::App::Helpers::ResponseBuilder.error(422, e)
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,52 @@
1
+ module Fakeit
2
+ module App
3
+ module Routes
4
+ class OpenapiRoute
5
+ def initialize(spec_file) = @specification = Fakeit::Openapi::Specification.new(spec_file)
6
+
7
+ def call(request, options)
8
+ @specification
9
+ .operation(request.request_method.downcase.to_sym, request.path_info, options)
10
+ .then { _1 ? handle(_1, request, options) : Fakeit::App::Helpers::ResponseBuilder.not_found }
11
+ end
12
+
13
+ private
14
+
15
+ def handle(operation, request, options)
16
+ validate(operation, request)
17
+ response(operation)
18
+ rescue Fakeit::Validation::ValidationError => e
19
+ Logger.warn(Rainbow(e.message).red)
20
+ options.permissive ? response(operation) : Fakeit::App::Helpers::ResponseBuilder.error(418, e)
21
+ end
22
+
23
+ def response(operation) = [operation.status, operation.headers, [operation.body]]
24
+
25
+ def validate(operation, request)
26
+ operation.validate(
27
+ body: Helpers::BodyParser.parse(request),
28
+ params: parse_query(request.query_string),
29
+ headers: headers(request)
30
+ )
31
+ end
32
+
33
+ def headers(request)
34
+ request
35
+ .each_header
36
+ .select { |k, _| k.start_with? 'HTTP_' }
37
+ .map { |k, v| [k.sub(/^HTTP_/, '').split('_').map(&:capitalize).join('-'), v] }
38
+ .to_h
39
+ end
40
+
41
+ def parse_query(query_string)
42
+ rack_query = Rack::Utils.parse_nested_query(query_string)
43
+ cgi_query = CGI.parse(query_string)
44
+
45
+ rack_query.merge(cgi_query.slice(*rack_query.keys)) do |_, oldval, newval|
46
+ newval.is_a?(Array) && newval.size > 1 ? newval : oldval
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,9 @@
1
+ require 'fakeit/openapi/schema'
2
+
3
+ module OpenAPIParser
4
+ module Schemas
5
+ class Reference
6
+ def to_example(_) = raise(Fakeit::Openapi::ReferenceError, "Invalid $ref at \"#{ref}\"")
7
+ end
8
+ end
9
+ end
@@ -7,9 +7,7 @@ module OpenAPIParser
7
7
 
8
8
  alias old_type type
9
9
 
10
- def type
11
- old_type || inferred_type
12
- end
10
+ def type = old_type || inferred_type
13
11
 
14
12
  private
15
13
 
data/lib/fakeit/logger.rb CHANGED
@@ -1,4 +1,3 @@
1
1
  module Fakeit
2
- STDOUT.sync = true
3
- Logger = ::Logger.new(STDOUT)
2
+ Logger = ::Logger.new($stderr)
4
3
  end
@@ -1,9 +1,7 @@
1
1
  module Fakeit
2
2
  module Middleware
3
3
  class Recorder
4
- def initialize(app)
5
- @app = app
6
- end
4
+ def initialize(app) = @app = app
7
5
 
8
6
  def call(env)
9
7
  env
@@ -20,9 +18,7 @@ module Fakeit
20
18
  &.tap { |body| body.rewind }
21
19
  end
22
20
 
23
- def log_response(response)
24
- Logger.info("Response body: #{response[2].first}")
25
- end
21
+ def log_response(response) = Logger.info("Response body: #{response[2].first}")
26
22
  end
27
23
  end
28
24
  end
@@ -33,25 +33,15 @@ module Fakeit
33
33
  end
34
34
  end
35
35
 
36
- def add_depth(example_options)
37
- { **example_options, depth: example_options[:depth] + 1 }
38
- end
36
+ def add_depth(example_options) = { **example_options, depth: example_options[:depth] + 1 }
39
37
 
40
- def need_retry?(item, result, retries)
41
- uniqueItems && result.include?(item) && retries.positive?
42
- end
38
+ def need_retry?(item, result, retries) = uniqueItems && result.include?(item) && retries.positive?
43
39
 
44
- def non_empty_size
45
- [min_array, 1].max
46
- end
40
+ def non_empty_size = [min_array, 1].max
47
41
 
48
- def min_array
49
- minItems || 1
50
- end
42
+ def min_array = minItems || 1
51
43
 
52
- def max_array(depth)
53
- maxItems || min_array + (depth > 1 ? 2 : 9)
54
- end
44
+ def max_array(depth) = maxItems || min_array + (depth > 1 ? 2 : 9)
55
45
  end
56
46
  end
57
47
  end
@@ -29,21 +29,13 @@ module Fakeit
29
29
  end
30
30
  end
31
31
 
32
- def int_rand_begin
33
- min_int / int_multiple + int_rand_begin_adjust
34
- end
32
+ def int_rand_begin = min_int / int_multiple + int_rand_begin_adjust
35
33
 
36
- def int_rand_end
37
- max_int / int_multiple
38
- end
34
+ def int_rand_end = max_int / int_multiple
39
35
 
40
- def int_rand_begin_adjust
41
- (min_int % int_multiple).zero? ? 0 : 1
42
- end
36
+ def int_rand_begin_adjust = (min_int % int_multiple).zero? ? 0 : 1
43
37
 
44
- def int_multiple
45
- multipleOf || 1
46
- end
38
+ def int_multiple = multipleOf || 1
47
39
 
48
40
  def min_int
49
41
  if minimum
@@ -14,35 +14,22 @@ module Fakeit
14
14
 
15
15
  private
16
16
 
17
- def static_number_example
18
- (num_rand_end * num_multiple)
19
- .then { multipleOf ? _1 : _1.round(2) }
20
- end
17
+ def static_number_example = (num_rand_end * num_multiple).then { multipleOf ? _1 : _1.round(2) }
21
18
 
22
19
  def random_number_example
23
20
  (Faker::Number.between(from: num_rand_begin, to: num_rand_end) * num_multiple)
24
21
  .then { multipleOf ? _1 : _1.round(2) }
25
22
  end
26
23
 
27
- def num_rand_begin
28
- multipleOf ? (min_num / multipleOf).ceil : min_num
29
- end
24
+ def num_rand_begin = multipleOf ? (min_num / multipleOf).ceil : min_num
30
25
 
31
- def num_rand_end
32
- multipleOf ? (max_num / multipleOf).floor : max_num
33
- end
26
+ def num_rand_end = multipleOf ? (max_num / multipleOf).floor : max_num
34
27
 
35
- def num_multiple
36
- multipleOf || 1
37
- end
28
+ def num_multiple = multipleOf || 1
38
29
 
39
- def min_num
40
- (minimum || MIN_NUM).to_f.ceil(2)
41
- end
30
+ def min_num = (minimum || MIN_NUM).to_f.ceil(2)
42
31
 
43
- def max_num
44
- (maximum || MAX_NUM).to_f.floor(2)
45
- end
32
+ def max_num = (maximum || MAX_NUM).to_f.floor(2)
46
33
  end
47
34
  end
48
35
  end
@@ -27,6 +27,8 @@ module Fakeit
27
27
  }.freeze
28
28
 
29
29
  def string_example(example_options)
30
+ @string_pattern ||= Regexp.new(pattern) if pattern
31
+
30
32
  if example_options[:use_static][type: 'string', property: example_options[:property]]
31
33
  static_string_example
32
34
  else
@@ -39,7 +41,7 @@ module Fakeit
39
41
  def static_string_example
40
42
  fixed_faker do
41
43
  if enum then enum.to_a.first
42
- elsif pattern then Faker::Base.regexify(pattern)
44
+ elsif pattern then static_string_pattern
43
45
  elsif format then static_string_format
44
46
  elsif length_constraint then static_string_with_length
45
47
  else 'string'
@@ -56,43 +58,37 @@ module Fakeit
56
58
 
57
59
  def random_string_example
58
60
  if enum then enum.to_a.sample
59
- elsif pattern then Faker::Base.regexify(pattern)
61
+ elsif pattern then random_string_pattern
60
62
  elsif format then random_string_format
61
63
  elsif length_constraint then string_with_length
62
64
  else Faker::Book.title
63
65
  end
64
66
  end
65
67
 
66
- def static_string_with_length
67
- '1' * max_string_length
68
- end
68
+ def static_string_with_length = '1' * max_string_length
69
69
 
70
- def static_string_format
71
- (STATIC_FORMAT_HANDLERS[format] || method(:unknown_format))[]
72
- end
70
+ def static_string_format = (STATIC_FORMAT_HANDLERS[format] || method(:unknown_format))[]
73
71
 
74
- def length_constraint
75
- minLength || maxLength
72
+ def static_string_pattern
73
+ @static_string_pattern ||= @string_pattern.examples(
74
+ max_repeater_variance: 1, max_group_results: 1, max_results_limit: 1
75
+ ).first
76
76
  end
77
77
 
78
- def string_with_length
79
- Faker::Internet.username(specifier: min_string_length..max_string_length)
80
- end
78
+ def length_constraint = minLength || maxLength
81
79
 
82
- def min_string_length
83
- minLength || 0
84
- end
80
+ def string_with_length = Faker::Internet.username(specifier: min_string_length..max_string_length)
85
81
 
86
- def max_string_length
87
- maxLength || min_string_length + 10
88
- end
82
+ def min_string_length = minLength || 0
89
83
 
90
- def random_string_format
91
- (RANDOM_FORMAT_HANDLERS[format] || method(:unknown_format))[]
92
- end
84
+ def max_string_length = maxLength || min_string_length + 10
85
+
86
+ def random_string_format = (RANDOM_FORMAT_HANDLERS[format] || method(:unknown_format))[]
87
+
88
+ def random_string_pattern = @random_string_pattern ||= @string_pattern.random_example(max_repeater_variance: 1)
93
89
 
94
90
  def unknown_format
95
- Fakeit::Logger.info("Unknown string format: #{format}")
91
+ Logger.info("Unknown string format: #{format}")
96
92
  'Unknown string format'
97
93
  end
98
94
  end