randrizer 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: be06076a3c75b00ab51471e44ef3f7b6794423f9c5dbe4f9ff9b01efb4493fb6
4
+ data.tar.gz: cea424a0ca6abd167ec418afbb7952c346304252f1da937f13d5eb85373ade6c
5
+ SHA512:
6
+ metadata.gz: 656b7243cc9ac2be824c6060a27ed88940316f57a5acd93140b3a99043283443130bc34b774c9ed0e43867beb6b54cf941dbebf46b1509b114dad37b4d30be41
7
+ data.tar.gz: c5ca2206521f575429fbf281d45072f3025ba3ec5c367d971c55663209db339b7f7f0d8b173f3db1a0ceac4b6b3c6058bf162457523761138227d2ba1030b7ba
data/.gitignore ADDED
@@ -0,0 +1,12 @@
1
+ .DS_Store
2
+
3
+ .data/
4
+ .bundle/
5
+ log/
6
+ tmp/
7
+ .idea/
8
+
9
+ .rspec_status
10
+
11
+ .local_files
12
+ Gemfile.lock
data/.rubocop.yml ADDED
@@ -0,0 +1,107 @@
1
+ require: rubocop-rspec
2
+
3
+ AllCops:
4
+ TargetRubyVersion: 2.6.5
5
+ DisplayCopNames: true
6
+ DisplayStyleGuide: true
7
+ ExtraDetails: true
8
+ Exclude:
9
+ - .*/**/*
10
+ - vendor/**/*
11
+ - bin/**/*
12
+
13
+ Lint/UnneededCopDisableDirective:
14
+ Severity: error
15
+
16
+ Lint/AmbiguousBlockAssociation:
17
+ Exclude:
18
+ - "spec/**/*"
19
+
20
+ Metrics/AbcSize:
21
+ Max: 59
22
+
23
+ Metrics/CyclomaticComplexity:
24
+ Max: 10
25
+
26
+ Metrics/PerceivedComplexity:
27
+ Max: 9
28
+
29
+ Metrics/ClassLength:
30
+ Enabled: false
31
+
32
+ Metrics/MethodLength:
33
+ Max: 58
34
+
35
+ Metrics/LineLength:
36
+ Max: 90
37
+ Exclude:
38
+ - 'Gemfile'
39
+
40
+ Metrics/BlockLength:
41
+ Enabled: false
42
+
43
+ Metrics/ModuleLength:
44
+ Enabled: false
45
+
46
+ Metrics/ParameterLists:
47
+ Max: 10
48
+ CountKeywordArgs: false
49
+
50
+ Naming/FileName:
51
+ Exclude:
52
+ - 'Gemfile'
53
+
54
+ Style/Documentation:
55
+ Enabled: false
56
+
57
+ Style/StringLiterals:
58
+ EnforcedStyle: "double_quotes"
59
+ Exclude:
60
+ - Gemfile
61
+
62
+ Layout/IndentFirstArrayElement:
63
+ EnforcedStyle: consistent
64
+
65
+ Layout/IndentFirstHashElement:
66
+ EnforcedStyle: consistent
67
+
68
+ Style/YodaCondition:
69
+ Enabled: true
70
+
71
+ RSpec/ExampleLength:
72
+ Enabled: false
73
+
74
+ RSpec/MultipleExpectations:
75
+ Max: 10
76
+
77
+ RSpec/NestedGroups:
78
+ Max: 8
79
+
80
+ RSpec/ImplicitSubject:
81
+ Enabled: false
82
+
83
+ RSpec/NamedSubject:
84
+ Enabled: false
85
+
86
+ RSpec/LetSetup:
87
+ Enabled: false
88
+
89
+ RSpec/ContextWording:
90
+ Prefixes:
91
+ - when
92
+ - with
93
+ - without
94
+ - if
95
+ - unless
96
+ - for
97
+ - and
98
+
99
+ Naming/UncommunicativeMethodParamName:
100
+ AllowedNames:
101
+ # These are the default allowed names, set by Rubocop
102
+ - io
103
+ - id
104
+ # These are some custom names that we want to allow, since they aren't
105
+ # uncommunicative - they're actually rather meaningful!
106
+ - to
107
+ - "_"
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.7.0
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ gemspec
data/LICENCE.txt ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2020 Vito Giuliani
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,82 @@
1
+ # Randrizer
2
+
3
+ Randrizer is a small library that allows consumers to generate random data
4
+ for testing.
5
+
6
+ ```ruby
7
+ irb(main):001:0> definition = Randrizer::Types::Dict[{
8
+ Randrizer::Types::Const["hello"] => Randrizer::Types::String[],
9
+ Randrizer::Types::Const["world"] => Randrizer::Types::Int[min: 5, max: 10],
10
+ Randrizer::Types::Const["nested"] => Randrizer::Types::Dict[
11
+ {
12
+ Randrizer::Types::Const["nested1"] => Randrizer::Types::Float[],
13
+ Randrizer::Types::Optional[inner_type: Randrizer::Types::Const["nested2"]] =>
14
+ Randrizer::Types::Int[]
15
+ }
16
+ ]
17
+ }]
18
+
19
+ irb(main):002:0> definition.eval
20
+ => {"hello"=>"XN3#tQd1%5#HWXI*p9zJD!tV^\\e%Wgais]vJQRNp$Z60FymI[=8~xy0IdVrmSb)me59zbqJjTbgZsv1NQA",
21
+ "world"=>6,
22
+ "nested"=>{"nested1"=>2274302953.724733}}
23
+ ```
24
+
25
+ ## Use cases
26
+
27
+ * Fuzzy testing: create massive amounts of random data for your application
28
+ to consume. Are the inputs sanitised correctly? Are invalid inputs accepted?
29
+ * Performance testing: generate random data to be fed to your application to
30
+ measure throughput and performance
31
+ * Anonymisation: replace your production data with random values
32
+
33
+ ## Drivers
34
+
35
+ Currently there's a single driver out of the box for simple JSON schema files.
36
+
37
+ ```json
38
+ $ cat json_schema.json
39
+ {
40
+ "$schema": "http://json-schema.org/draft-06/schema#",
41
+ "properties": {
42
+ "resource": {
43
+ "description": "A type",
44
+ "type": "string",
45
+ "enum": ["type 1", "type 2"]
46
+ },
47
+ "amount": {
48
+ "description": "Amount in cents",
49
+ "type": "integer",
50
+ "minimum": 0
51
+ },
52
+ "the_date": {
53
+ "description": "Birthday",
54
+ "type": "string",
55
+ "format": "date"
56
+ },
57
+ "country": {
58
+ "description": "Country code",
59
+ "type": "string",
60
+ "minLength": 2,
61
+ "maxLength": 2
62
+ }
63
+ },
64
+ "type": "object",
65
+ "required": [
66
+ "resource",
67
+ "amount",
68
+ "the_date",
69
+ "country"
70
+ ]
71
+ }
72
+ ```
73
+
74
+ ```json
75
+ $ randrizer-json-schema json_schema.json
76
+ {
77
+ "resource": "type 1",
78
+ "amount": 302365828,
79
+ "the_date": "2157-09-13",
80
+ "country": "g-"
81
+ }
82
+ ```
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "bundler/setup"
5
+ require "randrizer"
6
+ require "randrizer/cli"
7
+
8
+ Randrizer::Cli.run
9
+ exit 0
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "randrizer/version"
4
+ require "randrizer"
5
+
6
+ module Randrizer
7
+ class Cli
8
+ def self.run
9
+ return help if ARGV.empty?
10
+
11
+ fname = ARGV[0]
12
+ unless File.exist?(fname)
13
+ raise ArgumentError, "#{fname} does not exist or cannot be accessed"
14
+ end
15
+
16
+ input_json = File.read(fname)
17
+ type_tree = Drivers::JSONSchema::Driver.for(content: input_json).type_tree
18
+ output = type_tree.eval
19
+ puts JSON.pretty_generate(output)
20
+ end
21
+
22
+ def self.help
23
+ STDOUT.write("Randrizer #{Randrizer::VERSION}\n")
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ require "randrizer/drivers/json_schema/typegen"
6
+
7
+ module Randrizer
8
+ module Drivers
9
+ module JSONSchema
10
+ class Driver
11
+ # JSON Schemas are much more complex than one would initially imagine. This is a
12
+ # very simple driver for basic schemas, but not all the attributes and properties
13
+ # of types are respected.
14
+ # TODO: support for list and dict datatypes
15
+
16
+ attr_reader :path
17
+
18
+ def self.for(content:)
19
+ parsed_content = JSON.parse(content)
20
+ new(parsed_content)
21
+ end
22
+
23
+ def initialize(parsed_schema)
24
+ @json = parsed_schema
25
+ end
26
+
27
+ def type_tree
28
+ dict_items = []
29
+
30
+ required_keys = json.fetch("required", [])
31
+ json.fetch("properties", []).map do |key, attrs|
32
+ key_type = Types::Const[key]
33
+ root_type = attrs["type"]
34
+ value_type = gen_value_type(root_type, attrs)
35
+
36
+ unless required_keys.include?(key_type)
37
+ key_type = Types::Optional[inner_type: key_type]
38
+ end
39
+
40
+ dict_items.append([key_type, value_type])
41
+ end
42
+
43
+ Types::Dict[dict_items]
44
+ end
45
+
46
+ private
47
+
48
+ attr_reader :json
49
+
50
+ def gen_value_type(type, attrs)
51
+ case type
52
+ when "integer"
53
+ Typegen.t_integer(attrs)
54
+ when "number"
55
+ Typegen.t_number(attrs)
56
+ when "boolean"
57
+ Typegen.t_boolean(attrs)
58
+ when "string"
59
+ Typegen.t_string(attrs)
60
+ when "null"
61
+ Typegen.t_null(attrs)
62
+ when Array
63
+ Types::OneOf.build(type.map { |sub_t| gen_value_type(sub_t, attrs) })
64
+ else
65
+ raise "Unsupported type: #{type}"
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,123 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "randrizer/types"
4
+
5
+ module Randrizer
6
+ module Drivers
7
+ module JSONSchema
8
+ class Typegen
9
+ class << self
10
+ MONTH_INT_TYPE =
11
+ Types::OneOf[(1..12).map { |i| Types::Const.build(i.to_s.rjust(2, "0")) }]
12
+ DAY_INT_TYPE =
13
+ Types::OneOf[(1..31).map { |i| Types::Const.build(i.to_s.rjust(2, "0")) }]
14
+ HOUR_INT_TYPE =
15
+ Types::OneOf[(0..23).map { |i| Types::Const.build(i.to_s.rjust(2, "0")) }]
16
+ MIN_SEC_INT_TYPE =
17
+ Types::OneOf[(0..59).map { |i| Types::Const.build(i.to_s.rjust(2, "0")) }]
18
+
19
+ DATE_SEQUENCE = [
20
+ Types::Int.build(min: 1970, max: 2200),
21
+ Types::Const["-"],
22
+ MONTH_INT_TYPE,
23
+ Types::Const["-"],
24
+ DAY_INT_TYPE
25
+ ].freeze
26
+
27
+ TIMEZONE_SEQUENCE = Types::OneOf.build([
28
+ Types::Const["Z"],
29
+ Types::StringSequence.build([
30
+ Types::OneOf.build([Types::Const["+"], Types::Const["-"]]),
31
+ HOUR_INT_TYPE,
32
+ Types::Const[":"],
33
+ MIN_SEC_INT_TYPE
34
+ ])
35
+ ])
36
+
37
+ TIME_SEQUENCE = [
38
+ HOUR_INT_TYPE,
39
+ Types::Const[":"],
40
+ MIN_SEC_INT_TYPE,
41
+ Types::Const[":"],
42
+ MIN_SEC_INT_TYPE,
43
+ Types::Optional.build(inner_type: TIMEZONE_SEQUENCE)
44
+ ].freeze
45
+
46
+ EMAIL_SEQUENCE = [
47
+ Types::String.build(valid_chars: Types::String::CHARS_ALL_LETTERS + "._+"),
48
+ Types::Const["@"],
49
+ Types::String.build(valid_chars: Types::String::CHARS_ALL_LETTERS + "._")
50
+ ].freeze
51
+
52
+ def t_null(_attrs)
53
+ # `Types::Nullable` would require an inner type, as this will never change
54
+ # we can just return a constant null value.
55
+ Types::Const[nil]
56
+ end
57
+
58
+ def t_boolean(_attrs)
59
+ Types::Bool[]
60
+ end
61
+
62
+ def t_number(attrs)
63
+ # TODO: support multipleOf
64
+
65
+ restrictions = {}
66
+ restrictions[:min] = attrs.fetch("minimum", -999_999_999)
67
+ restrictions[:max] = attrs.fetch("maximum", 999_999_999)
68
+
69
+ restrictions[:min] += 1 if attrs.fetch("exclusiveMinimum", false)
70
+ restrictions[:max] -= 1 if attrs.fetch("exclusiveMaximum", false)
71
+
72
+ Types::Int.build(**restrictions)
73
+ end
74
+
75
+ def t_integer(attrs)
76
+ t_number(attrs)
77
+ end
78
+
79
+ def t_string_with_format(_attrs, format)
80
+ # https://json-schema.org/understanding-json-schema/reference/string.html#built-in-formats
81
+
82
+ case format
83
+ when "date"
84
+ Types::StringSequence.build(DATE_SEQUENCE)
85
+ when "time"
86
+ Types::StringSequence.build(TIME_SEQUENCE)
87
+ when "date-time"
88
+ Types::StringSequence.build([
89
+ DATE_SEQUENCE,
90
+ Types::Const["T"],
91
+ TIME_SEQUENCE
92
+ ].flatten)
93
+ when "email"
94
+ Types::StringSequence.build(EMAIL_SEQUENCE)
95
+ else
96
+ raise "Format not supported: #{format}"
97
+ end
98
+ end
99
+
100
+ def t_string(attrs)
101
+ # TODO: support String patterns format specs
102
+
103
+ if attrs.include?("enum")
104
+ return Types::OneOf[
105
+ attrs["enum"].map { |e| Types::Const[e] }
106
+ ]
107
+ end
108
+
109
+ if attrs.include?("format")
110
+ return t_string_with_format(attrs, attrs["format"])
111
+ end
112
+
113
+ props = {}
114
+ props[:min_length] = attrs["minLength"].to_i if attrs.include?("minLength")
115
+ props[:max_length] = attrs["maxLength"].to_i if attrs.include?("maxLength")
116
+
117
+ Types::String.build(**props)
118
+ end
119
+ end
120
+ end
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "randrizer/drivers/json_schema/driver"
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "randrizer/types"
4
+
5
+ module Randrizer
6
+ class Generator
7
+ def self.generate(type)
8
+ type.validate!
9
+ type.eval
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "randrizer/types/type_builder"
4
+
5
+ module Randrizer
6
+ module Types
7
+ class Bool
8
+ include TypeBuilder
9
+
10
+ PRESENCE_MAYBE = 0.5
11
+
12
+ def initialize(true_prob: PRESENCE_MAYBE)
13
+ @true_prob = true_prob
14
+ end
15
+
16
+ def validate!
17
+ raise ValidationError("true_prob must be < 1.0") if @true_prob > 1.0
18
+ raise ValidationError("true_prob must be > 0.0") if @true_prob < 0.0
19
+ end
20
+
21
+ def eval
22
+ rand > (1.0 - @true_prob)
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "randrizer/types/type_builder"
4
+
5
+ module Randrizer
6
+ module Types
7
+ class Const
8
+ include TypeBuilder
9
+
10
+ def initialize(params)
11
+ @params = params
12
+ end
13
+
14
+ def validate!; end
15
+
16
+ def eval
17
+ @params
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "randrizer/types/skip"
4
+ require "randrizer/types/type_builder"
5
+
6
+ module Randrizer
7
+ module Types
8
+ class Dict
9
+ include TypeBuilder
10
+
11
+ def initialize(keys_def)
12
+ @keys_def = keys_def
13
+ end
14
+
15
+ def validate!; end
16
+
17
+ def eval
18
+ @keys_def.each_with_object({}) do |(key_type, value_type), hash|
19
+ key = key_type.eval
20
+ value = value_type.eval
21
+
22
+ next if key == SKIP || value == SKIP
23
+
24
+ hash[key] = value
25
+ end
26
+ end
27
+
28
+ def empty?
29
+ @keys_def.empty?
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "randrizer/types/type_builder"
4
+
5
+ module Randrizer
6
+ module Types
7
+ class Float
8
+ include TypeBuilder
9
+
10
+ DEFAULT_MIN = 0.0
11
+ DEFAULT_MAX = 9_999_999_999.0
12
+
13
+ def initialize(min: DEFAULT_MIN, max: DEFAULT_MAX)
14
+ @min = min
15
+ @max = max
16
+ end
17
+
18
+ def validate!
19
+ return if @max >= @min
20
+
21
+ raise ValidationError("invalid min/max configuration")
22
+ end
23
+
24
+ def eval
25
+ return @min if @min == @max
26
+
27
+ rand * (@max - @min) + @min
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "randrizer/types/type_builder"
4
+
5
+ module Randrizer
6
+ module Types
7
+ class Int
8
+ include TypeBuilder
9
+
10
+ DEFAULT_MIN = 0
11
+ DEFAULT_MAX = 9_999_999_999
12
+
13
+ def initialize(min: DEFAULT_MIN, max: DEFAULT_MAX)
14
+ @min = min
15
+ @max = max
16
+ end
17
+
18
+ def validate!
19
+ return if @max >= @min
20
+
21
+ raise ValidationError("invalid min/max configuration")
22
+ end
23
+
24
+ def eval
25
+ return @min if @min == @max
26
+
27
+ rand(@min..@max)
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "randrizer/types/skip"
4
+ require "randrizer/types/type_builder"
5
+
6
+ module Randrizer
7
+ module Types
8
+ class List
9
+ include TypeBuilder
10
+
11
+ def initialize(list_def)
12
+ @list_def = list_def
13
+ end
14
+
15
+ def validate!
16
+ !@list_def.nil?
17
+ end
18
+
19
+ def eval
20
+ @list_def.map(&:eval).reject { |evaluated| evaluated == SKIP }
21
+ end
22
+
23
+ def empty?
24
+ @list_def.empty?
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "randrizer/types/type_builder"
4
+
5
+ module Randrizer
6
+ module Types
7
+ class Nullable
8
+ include TypeBuilder
9
+
10
+ def initialize(null_prob:, inner_type:)
11
+ @null_prob = null_prob
12
+ @inner_type = inner_type
13
+ end
14
+
15
+ def validate!
16
+ raise ValidationError("null_prob must be < 1.0") if @null_prob > 1.0
17
+ raise ValidationError("null_prob must be > 0.0") if @null_prob < 0.0
18
+ end
19
+
20
+ def eval
21
+ return nil if rand > (1.0 - @null_prob)
22
+
23
+ @inner_type.eval
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "randrizer/types/type_builder"
4
+
5
+ module Randrizer
6
+ module Types
7
+ class OneOf
8
+ include TypeBuilder
9
+
10
+ def initialize(list_def)
11
+ @list_def = list_def
12
+ end
13
+
14
+ def validate!
15
+ !@list_def.nil?
16
+ end
17
+
18
+ def eval
19
+ @list_def.sample.eval
20
+ end
21
+
22
+ def length
23
+ @list_def.length
24
+ end
25
+
26
+ alias count length
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "randrizer/types/skip"
4
+ require "randrizer/types/type_builder"
5
+
6
+ module Randrizer
7
+ module Types
8
+ class Optional
9
+ include TypeBuilder
10
+
11
+ PRESENCE_MAYBE = 0.5
12
+
13
+ def initialize(inner_type:, presence_prob: PRESENCE_MAYBE)
14
+ @inner_type = inner_type
15
+ @presence_prob = presence_prob
16
+ end
17
+
18
+ def validate!
19
+ raise ValidationError("presence_prob must be < 1.0") if @presence_prob > 1.0
20
+ raise ValidationError("presence_prob must be > 0.0") if @presence_prob < 0.0
21
+ end
22
+
23
+ def eval
24
+ return SKIP if rand > @presence_prob
25
+
26
+ @inner_type.eval
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Randrizer
4
+ module Types
5
+ class Skip
6
+ def ==(other)
7
+ other.class == Skip
8
+ end
9
+
10
+ def eval
11
+ self
12
+ end
13
+ end
14
+
15
+ SKIP = Skip.new
16
+ end
17
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "randrizer/types/type_builder"
4
+
5
+ module Randrizer
6
+ module Types
7
+ class String
8
+ include TypeBuilder
9
+
10
+ CHARS_NUMBERS = "0123456789"
11
+ CHARS_SYMBOLS = " !\"£$%^&()=-*/[]#\\~"
12
+ CHARS_LOWERCASE_LETTERS = "abcdfeghijklmnopqrstuvwxyz"
13
+ CHARS_UPPERCASE_LETTERS = CHARS_LOWERCASE_LETTERS.upcase
14
+ CHARS_ALL_LETTERS = CHARS_LOWERCASE_LETTERS + CHARS_UPPERCASE_LETTERS
15
+
16
+ DEFAULT_VALID_CHARS =
17
+ CHARS_NUMBERS +
18
+ CHARS_ALL_LETTERS +
19
+ CHARS_SYMBOLS
20
+
21
+ DEFAULT_MIN_LENGTH = 0
22
+ DEFAULT_MAX_LENGTH = 99
23
+
24
+ def initialize(min_length: DEFAULT_MIN_LENGTH,
25
+ max_length: DEFAULT_MAX_LENGTH,
26
+ valid_chars: DEFAULT_VALID_CHARS)
27
+ @min_length = min_length
28
+ @max_length = max_length
29
+ @valid_chars = valid_chars
30
+ end
31
+
32
+ def validate!
33
+ return if @max_length >= @min_length
34
+
35
+ raise ValidationError("invalid length configuration")
36
+ end
37
+
38
+ def eval
39
+ chars_split = @valid_chars.split("")
40
+ string_length = rand(@min_length..@max_length)
41
+ string_length.times.map { chars_split.sample }.join
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "randrizer/types/type_builder"
4
+
5
+ module Randrizer
6
+ module Types
7
+ class StringSequence
8
+ include TypeBuilder
9
+
10
+ ALLOWED_TYPES = [
11
+ Types::Int,
12
+ Types::String,
13
+ Types::Const,
14
+ Types::Nullable,
15
+ Types::Optional,
16
+ Types::OneOf
17
+ ].freeze
18
+
19
+ def initialize(sequence_def)
20
+ @sequence_def = sequence_def
21
+ end
22
+
23
+ def validate!
24
+ disallowed = @sequence_def.reject { |item| ALLOWED_TYPES.include?(item.class) }
25
+
26
+ raise ValidationError("types not allowed in a string sequence: #{disallowed}")
27
+ end
28
+
29
+ def eval
30
+ @sequence_def.map(&:eval).reject { |evaluated| evaluated == SKIP }.compact.join
31
+ end
32
+
33
+ def empty?
34
+ @sequence_def.empty?
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Randrizer
4
+ module Types
5
+ module TypeBuilder
6
+ class ValidationError < StandardError; end
7
+
8
+ def self.included(base)
9
+ base.extend(ClassMethods)
10
+ end
11
+
12
+ def validate!
13
+ raise NotImplementedError
14
+ end
15
+
16
+ module ClassMethods
17
+ def build(*args, **params)
18
+ new(*args, **params)
19
+ end
20
+
21
+ alias [] build
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Primitive types. They should not depend on each other.
4
+ require "randrizer/types/const"
5
+ require "randrizer/types/bool"
6
+ require "randrizer/types/int"
7
+ require "randrizer/types/float"
8
+ require "randrizer/types/string"
9
+ require "randrizer/types/one_of"
10
+ require "randrizer/types/skip"
11
+ require "randrizer/types/optional"
12
+ require "randrizer/types/nullable"
13
+
14
+ # Iterable types. They might inspect the internal types to special case behaviour and
15
+ # as such must be declared after the primitive types.
16
+ require "randrizer/types/list"
17
+ require "randrizer/types/dict"
18
+ require "randrizer/types/string_sequence"
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Randrizer
4
+ VERSION = "0.0.1"
5
+ end
data/lib/randrizer.rb ADDED
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "randrizer/version"
4
+ require "randrizer/types"
5
+ require "randrizer/drivers/json_schema"
data/randrizer.gemspec ADDED
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ lib = File.expand_path("lib", __dir__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+ require "randrizer/version"
6
+
7
+ Gem::Specification.new do |spec|
8
+ spec.name = "randrizer"
9
+ spec.homepage = "https://github.com/ivgiuliani/randrizer"
10
+ spec.version = Randrizer::VERSION
11
+ spec.authors = ["Ivan Giuliani"]
12
+ spec.email = ["giuliani.v@gmail.com"]
13
+
14
+ spec.summary = "Generate random data for testing"
15
+ spec.description = "Random data generator with type composition"
16
+ spec.license = "MIT"
17
+
18
+ # Specify which files should be added to the gem when it is released.
19
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
20
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
21
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
22
+ end
23
+ spec.bindir = "exe"
24
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
25
+ spec.require_paths = ["lib"]
26
+
27
+ spec.add_development_dependency "bundler", "~> 2.0"
28
+ spec.add_development_dependency "pry-byebug", "~> 3.7.0"
29
+ spec.add_development_dependency "rake", "~> 10.0"
30
+ spec.add_development_dependency "rspec", "~> 3.0"
31
+ spec.add_development_dependency "rubocop", "~> 0.75.0"
32
+ spec.add_development_dependency "rubocop-rspec", "~> 1.36.0"
33
+ end
metadata ADDED
@@ -0,0 +1,157 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: randrizer
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Ivan Giuliani
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2020-02-26 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: pry-byebug
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 3.7.0
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 3.7.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: '10.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '10.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '3.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '3.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rubocop
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: 0.75.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.75.0
83
+ - !ruby/object:Gem::Dependency
84
+ name: rubocop-rspec
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: 1.36.0
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: 1.36.0
97
+ description: Random data generator with type composition
98
+ email:
99
+ - giuliani.v@gmail.com
100
+ executables:
101
+ - randrizer-json-schema
102
+ extensions: []
103
+ extra_rdoc_files: []
104
+ files:
105
+ - ".gitignore"
106
+ - ".rubocop.yml"
107
+ - ".ruby-version"
108
+ - Gemfile
109
+ - LICENCE.txt
110
+ - README.md
111
+ - exe/randrizer-json-schema
112
+ - lib/randrizer.rb
113
+ - lib/randrizer/cli.rb
114
+ - lib/randrizer/drivers/json_schema.rb
115
+ - lib/randrizer/drivers/json_schema/driver.rb
116
+ - lib/randrizer/drivers/json_schema/typegen.rb
117
+ - lib/randrizer/generator.rb
118
+ - lib/randrizer/types.rb
119
+ - lib/randrizer/types/bool.rb
120
+ - lib/randrizer/types/const.rb
121
+ - lib/randrizer/types/dict.rb
122
+ - lib/randrizer/types/float.rb
123
+ - lib/randrizer/types/int.rb
124
+ - lib/randrizer/types/list.rb
125
+ - lib/randrizer/types/nullable.rb
126
+ - lib/randrizer/types/one_of.rb
127
+ - lib/randrizer/types/optional.rb
128
+ - lib/randrizer/types/skip.rb
129
+ - lib/randrizer/types/string.rb
130
+ - lib/randrizer/types/string_sequence.rb
131
+ - lib/randrizer/types/type_builder.rb
132
+ - lib/randrizer/version.rb
133
+ - randrizer.gemspec
134
+ homepage: https://github.com/ivgiuliani/randrizer
135
+ licenses:
136
+ - MIT
137
+ metadata: {}
138
+ post_install_message:
139
+ rdoc_options: []
140
+ require_paths:
141
+ - lib
142
+ required_ruby_version: !ruby/object:Gem::Requirement
143
+ requirements:
144
+ - - ">="
145
+ - !ruby/object:Gem::Version
146
+ version: '0'
147
+ required_rubygems_version: !ruby/object:Gem::Requirement
148
+ requirements:
149
+ - - ">="
150
+ - !ruby/object:Gem::Version
151
+ version: '0'
152
+ requirements: []
153
+ rubygems_version: 3.1.2
154
+ signing_key:
155
+ specification_version: 4
156
+ summary: Generate random data for testing
157
+ test_files: []