synthra 0.1.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 +7 -0
- data/.rspec +4 -0
- data/.rubocop.yml +40 -0
- data/.yardopts +18 -0
- data/ACCESS_DOCS.md +116 -0
- data/ADVANCED_FEATURES_SUMMARY.md +245 -0
- data/CHANGELOG.md +498 -0
- data/CODE_OF_CONDUCT.md +122 -0
- data/CONTRIBUTING.md +307 -0
- data/DOCUMENTATION.md +204 -0
- data/DOCUMENTATION_GUIDE.md +194 -0
- data/LICENSE +22 -0
- data/README.md +1458 -0
- data/Rakefile +128 -0
- data/benchmark/README.md +591 -0
- data/benchmark/batch_generation.rb +63 -0
- data/benchmark/engine_comparison.rb +295 -0
- data/benchmark/single_record.rb +108 -0
- data/benchmark/streaming.rb +74 -0
- data/docs/ACTIVERECORD_INFERENCE.md +524 -0
- data/docs/API_SERVER.md +278 -0
- data/docs/CONFIG_FILE.md +315 -0
- data/docs/DATA_CONTRACTS.md +312 -0
- data/docs/ENHANCED_REPL.md +304 -0
- data/docs/FACTORY_BOT.md +271 -0
- data/docs/GITHUB_ACTION.md +420 -0
- data/docs/GRAPHQL_EXPORT.md +303 -0
- data/docs/GRAPHQL_FEDERATION.md +651 -0
- data/docs/HOW_TO_GENERATE_DOCS.md +252 -0
- data/docs/LSP.md +189 -0
- data/docs/MIGRATION_GENERATOR.md +547 -0
- data/docs/MOCK_SERVER.md +280 -0
- data/docs/NATIVE_ENGINE.md +298 -0
- data/docs/OPENAPI_EXPORT.md +252 -0
- data/docs/PERFORMANCE_MODE.md +228 -0
- data/docs/PERSONAS.md +547 -0
- data/docs/PROPERTY_TESTING.md +284 -0
- data/docs/PROTOBUF_EXPORT.md +276 -0
- data/docs/QUALITY_METRICS.md +555 -0
- data/docs/QUICK_REFERENCE.md +88 -0
- data/docs/RAILS_ENGINE.md +404 -0
- data/docs/RAILS_INTEGRATION.md +454 -0
- data/docs/README.md +175 -0
- data/docs/REPL_QUICK_REFERENCE.md +149 -0
- data/docs/RUST_INTEGRATION_GUIDE.md +1376 -0
- data/docs/SCENARIOS.md +576 -0
- data/docs/SECURITY_TESTING.md +318 -0
- data/docs/SNAPSHOT_TESTING.md +308 -0
- data/docs/STRING_TEXT_TYPES_REFERENCE.md +512 -0
- data/docs/TERRAFORM_EXPORT.md +447 -0
- data/docs/TIME_TRAVEL.md +507 -0
- data/docs/USAGE_GUIDE.md +980 -0
- data/docs/WEBHOOK_SIMULATOR.md +579 -0
- data/docs/advanced_features.md +183 -0
- data/docs/api_reference.md +153 -0
- data/docs/behaviors.md +138 -0
- data/docs/best_practices.md +191 -0
- data/docs/dsl_reference.md +172 -0
- data/docs/examples.md +162 -0
- data/docs/generation_modes.md +125 -0
- data/docs/getting_started.md +92 -0
- data/docs/index.html +177 -0
- data/docs/strategic/CLOUD_REGISTRY_ARCHITECTURE.md +268 -0
- data/docs/strategic/SIMD_OPTIMIZATION_STRATEGY.md +153 -0
- data/docs/troubleshooting.md +215 -0
- data/docs/type_reference.md +224 -0
- data/examples/hostile_mode_demo.rb +64 -0
- data/exe/synthra +6 -0
- data/ext/synthra_native/Cargo.lock +972 -0
- data/ext/synthra_native/Cargo.toml +46 -0
- data/ext/synthra_native/extconf.rb +41 -0
- data/ext/synthra_native/src/generator.rs +397 -0
- data/ext/synthra_native/src/lib.rs +229 -0
- data/ext/synthra_native/src/types.rs +1485 -0
- data/lib/generators/synthra/install_generator.rb +86 -0
- data/lib/generators/synthra/templates/api_response.dsl +15 -0
- data/lib/generators/synthra/templates/fake_data.rake +116 -0
- data/lib/generators/synthra/templates/synthra.yml +31 -0
- data/lib/generators/synthra/templates/synthra_support.rb +19 -0
- data/lib/generators/synthra/templates/user.dsl +11 -0
- data/lib/synthra/activerecord_inference.rb +394 -0
- data/lib/synthra/api.rb +381 -0
- data/lib/synthra/api_server.rb +718 -0
- data/lib/synthra/behaviors/applicator.rb +165 -0
- data/lib/synthra/behaviors/base.rb +155 -0
- data/lib/synthra/behaviors/close_connection.rb +60 -0
- data/lib/synthra/behaviors/deprecated.rb +100 -0
- data/lib/synthra/behaviors/failure.rb +69 -0
- data/lib/synthra/behaviors/latency.rb +103 -0
- data/lib/synthra/behaviors/partial_data.rb +83 -0
- data/lib/synthra/behaviors/randomize_order.rb +74 -0
- data/lib/synthra/behaviors/registry.rb +238 -0
- data/lib/synthra/behaviors/simulate_error.rb +79 -0
- data/lib/synthra/cli/commands/base.rb +105 -0
- data/lib/synthra/cli/commands/diff.rb +227 -0
- data/lib/synthra/cli/commands/docs.rb +67 -0
- data/lib/synthra/cli/commands/export.rb +150 -0
- data/lib/synthra/cli/commands/generate.rb +72 -0
- data/lib/synthra/cli/commands/import.rb +80 -0
- data/lib/synthra/cli/commands/lint.rb +53 -0
- data/lib/synthra/cli/commands/live.rb +57 -0
- data/lib/synthra/cli/commands/seed.rb +99 -0
- data/lib/synthra/cli/commands/validate.rb +28 -0
- data/lib/synthra/cli.rb +2471 -0
- data/lib/synthra/config_file.rb +129 -0
- data/lib/synthra/configuration.rb +281 -0
- data/lib/synthra/contracts_registry.rb +408 -0
- data/lib/synthra/database_seeder.rb +268 -0
- data/lib/synthra/deterministic_ids.rb +218 -0
- data/lib/synthra/documentation_generator.rb +414 -0
- data/lib/synthra/engine.rb +251 -0
- data/lib/synthra/errors.rb +1169 -0
- data/lib/synthra/export/base.rb +85 -0
- data/lib/synthra/export/csv.rb +101 -0
- data/lib/synthra/export/graphql.rb +266 -0
- data/lib/synthra/export/graphql_federation.rb +377 -0
- data/lib/synthra/export/graphviz.rb +258 -0
- data/lib/synthra/export/javascript.rb +327 -0
- data/lib/synthra/export/json_data.rb +61 -0
- data/lib/synthra/export/json_schema.rb +290 -0
- data/lib/synthra/export/openapi.rb +514 -0
- data/lib/synthra/export/protobuf.rb +483 -0
- data/lib/synthra/export/python.rb +560 -0
- data/lib/synthra/export/sql.rb +381 -0
- data/lib/synthra/export/sql_insert.rb +152 -0
- data/lib/synthra/export/terraform.rb +501 -0
- data/lib/synthra/export/type_mapping.rb +389 -0
- data/lib/synthra/export/typescript.rb +300 -0
- data/lib/synthra/export/xml_data.rb +104 -0
- data/lib/synthra/export/yaml_data.rb +56 -0
- data/lib/synthra/export.rb +404 -0
- data/lib/synthra/factory_bot_integration.rb +157 -0
- data/lib/synthra/field.rb +440 -0
- data/lib/synthra/functions/registry.rb +104 -0
- data/lib/synthra/generator/context.rb +372 -0
- data/lib/synthra/generator/engine.rb +336 -0
- data/lib/synthra/generator/faker_adapter.rb +425 -0
- data/lib/synthra/generator/modes.rb +444 -0
- data/lib/synthra/generator/resolver.rb +256 -0
- data/lib/synthra/generator/rng.rb +273 -0
- data/lib/synthra/generator/streamer.rb +63 -0
- data/lib/synthra/generator/uniqueness.rb +118 -0
- data/lib/synthra/initializer.rb +97 -0
- data/lib/synthra/limits.rb +279 -0
- data/lib/synthra/live_preview.rb +518 -0
- data/lib/synthra/loader_config.rb +78 -0
- data/lib/synthra/lsp/server.rb +689 -0
- data/lib/synthra/migration_generator.rb +301 -0
- data/lib/synthra/mixin.rb +174 -0
- data/lib/synthra/mock_server.rb +458 -0
- data/lib/synthra/native_engine.rb +304 -0
- data/lib/synthra/openapi_importer.rb +228 -0
- data/lib/synthra/output/json_formatter.rb +40 -0
- data/lib/synthra/output/ndjson_formatter.rb +55 -0
- data/lib/synthra/parser/ast.rb +1287 -0
- data/lib/synthra/parser/lexer.rb +1152 -0
- data/lib/synthra/parser/parser.rb +1664 -0
- data/lib/synthra/parser/tokens.rb +461 -0
- data/lib/synthra/performance_mode.rb +364 -0
- data/lib/synthra/personas.rb +397 -0
- data/lib/synthra/property_testing.rb +172 -0
- data/lib/synthra/quality_metrics.rb +405 -0
- data/lib/synthra/rails_test_helper.rb +248 -0
- data/lib/synthra/registry.rb +533 -0
- data/lib/synthra/relationships.rb +193 -0
- data/lib/synthra/repl/enhanced_repl.rb +607 -0
- data/lib/synthra/repl/formatter.rb +191 -0
- data/lib/synthra/scenarios.rb +423 -0
- data/lib/synthra/schema.rb +605 -0
- data/lib/synthra/schema_inheritance.rb +104 -0
- data/lib/synthra/schema_versioning.rb +212 -0
- data/lib/synthra/snapshot_testing.rb +199 -0
- data/lib/synthra/time_travel.rb +338 -0
- data/lib/synthra/type_definitions.rb +274 -0
- data/lib/synthra/types/address_location/addresses.rb +125 -0
- data/lib/synthra/types/address_location/airports.rb +200 -0
- data/lib/synthra/types/address_location/banks_hospitals.rb +207 -0
- data/lib/synthra/types/address_location/locations.rb +406 -0
- data/lib/synthra/types/base.rb +48 -0
- data/lib/synthra/types/commerce_products/commerce.rb +119 -0
- data/lib/synthra/types/commerce_products/companies.rb +71 -0
- data/lib/synthra/types/commerce_products/construction.rb +126 -0
- data/lib/synthra/types/commerce_products/products.rb +205 -0
- data/lib/synthra/types/core/collections.rb +513 -0
- data/lib/synthra/types/core/defaults.rb +76 -0
- data/lib/synthra/types/core/enums.rb +102 -0
- data/lib/synthra/types/core/identifiers.rb +445 -0
- data/lib/synthra/types/core/primitives.rb +586 -0
- data/lib/synthra/types/core/references.rb +466 -0
- data/lib/synthra/types/core/sequences.rb +304 -0
- data/lib/synthra/types/crypto/crypto.rb +162 -0
- data/lib/synthra/types/date_time/dates.rb +387 -0
- data/lib/synthra/types/finance_banking/banking.rb +424 -0
- data/lib/synthra/types/finance_banking/credit_cards.rb +24 -0
- data/lib/synthra/types/finance_banking/identifiers.rb +93 -0
- data/lib/synthra/types/formula.rb +184 -0
- data/lib/synthra/types/health_medical/medical.rb +150 -0
- data/lib/synthra/types/hostile_payloads.rb +122 -0
- data/lib/synthra/types/json_array.rb +51 -0
- data/lib/synthra/types/media_entertainment/media.rb +62 -0
- data/lib/synthra/types/naughty_string.rb +30 -0
- data/lib/synthra/types/personal_names/chinese.rb +41 -0
- data/lib/synthra/types/personal_names/identifiers.rb +187 -0
- data/lib/synthra/types/personal_names/names.rb +246 -0
- data/lib/synthra/types/personal_names/national_id.rb +89 -0
- data/lib/synthra/types/personal_names/titles_suffixes.rb +41 -0
- data/lib/synthra/types/regex.rb +248 -0
- data/lib/synthra/types/registry.rb +182 -0
- data/lib/synthra/types/repeating_element.rb +50 -0
- data/lib/synthra/types/scenario.rb +29 -0
- data/lib/synthra/types/technology_internet/apps.rb +67 -0
- data/lib/synthra/types/technology_internet/communication.rb +162 -0
- data/lib/synthra/types/technology_internet/devices.rb +80 -0
- data/lib/synthra/types/technology_internet/formats.rb +139 -0
- data/lib/synthra/types/technology_internet/networking.rb +143 -0
- data/lib/synthra/types/template.rb +92 -0
- data/lib/synthra/types/text_content/business.rb +128 -0
- data/lib/synthra/types/text_content/colors.rb +70 -0
- data/lib/synthra/types/text_content/misc.rb +237 -0
- data/lib/synthra/types/text_content/security.rb +130 -0
- data/lib/synthra/types/text_content/text_generation.rb +532 -0
- data/lib/synthra/types/travel/travel.rb +150 -0
- data/lib/synthra/utils/string_distance.rb +133 -0
- data/lib/synthra/validator/dsl_validator.rb +339 -0
- data/lib/synthra/validator/path_validator.rb +615 -0
- data/lib/synthra/version.rb +35 -0
- data/lib/synthra/webhook_simulator.rb +341 -0
- data/lib/synthra.rb +259 -0
- data/schemas/address.dsl +16 -0
- data/schemas/api_response.dsl +8 -0
- data/schemas/error_payload.dsl +9 -0
- data/schemas/order.dsl +10 -0
- data/schemas/order_item.dsl +8 -0
- data/schemas/payment.dsl +11 -0
- data/schemas/social_post.dsl +13 -0
- data/schemas/user.dsl +10 -0
- data/scripts/batch_fix_all.rb +96 -0
- data/scripts/delete_old_files.rb +32 -0
- data/scripts/fix_all_grouped_files.rb +137 -0
- data/scripts/fix_all_indentation.rb +48 -0
- data/scripts/fix_all_syntax.rb +124 -0
- data/scripts/fix_grouped_files.rb +184 -0
- data/scripts/fix_syntax_errors.rb +108 -0
- data/scripts/group_domain_types.rb +112 -0
- data/scripts/group_domain_types_fixed.rb +150 -0
- data/scripts/merge_domains_to_one_file.rb +68 -0
- data/scripts/move_existing_types.rb +130 -0
- data/scripts/split_grouped_types.rb +142 -0
- data/tech_docs/README.md +134 -0
- data/tech_docs/advanced/streaming.md +405 -0
- data/tech_docs/advanced/thread_safety.md +253 -0
- data/tech_docs/api/overview.md +485 -0
- data/tech_docs/appendices/type_chart.md +193 -0
- data/tech_docs/basic_concepts.md +383 -0
- data/tech_docs/behaviors/overview.md +346 -0
- data/tech_docs/dsl/complex_types.md +790 -0
- data/tech_docs/dsl/core_types.md +464 -0
- data/tech_docs/dsl/datetime_types.md +325 -0
- data/tech_docs/dsl/field_modifiers.md +414 -0
- data/tech_docs/dsl/grammar.md +431 -0
- data/tech_docs/dsl/schema_definition.md +399 -0
- data/tech_docs/export/README.md +276 -0
- data/tech_docs/installation.md +273 -0
- data/tech_docs/integration/ci_cd.md +707 -0
- data/tech_docs/integration/ci_cd_guide.md +579 -0
- data/tech_docs/integration/factory_bot.md +485 -0
- data/tech_docs/integration/rails.md +630 -0
- data/tech_docs/integration/rspec.md +449 -0
- data/tech_docs/modes/overview.md +350 -0
- data/tech_docs/performance/NATIVE_RUST_EXTENSION.md +1270 -0
- data/tech_docs/performance/OPTIMIZATION_GUIDE.md +901 -0
- data/tech_docs/quick_start.md +256 -0
- data/tech_docs/templates/README.md +805 -0
- data/tech_docs/tutorials/advanced.md +371 -0
- data/tech_docs/tutorials/getting_started.md +189 -0
- data/tech_docs/tutorials/intermediate.md +231 -0
- data/tech_docs/tutorials/template_gallery.md +569 -0
- data/tech_example/01_basic_usage.rb +238 -0
- data/tech_example/02_types_demo.rb +336 -0
- data/tech_example/04_cli_usage.md +429 -0
- data/tech_example/05_database_seeding.rb +283 -0
- data/tech_example/07_rspec_integration.rb +359 -0
- data/tech_example/10_custom_types.rb +387 -0
- data/tech_example/12_behaviors.rb +372 -0
- data/tech_example/13_twitter_dm_example.rb +120 -0
- data/tech_example/14_exact_json_structure.rb +253 -0
- data/tech_example/16_lsp_server.rb +56 -0
- data/tech_example/17_property_testing.rb +92 -0
- data/tech_example/18_enhanced_repl.rb +120 -0
- data/tech_example/19_security_fuzzing.rb +145 -0
- data/tech_example/NEW_FEATURES.md +169 -0
- data/tech_example/README.md +118 -0
- data/tech_example/schemas/api_response.dsl +123 -0
- data/tech_example/schemas/ecommerce.dsl +121 -0
- data/tech_example/schemas/twitter_dm.dsl +109 -0
- data/tech_example/schemas/user.dsl +36 -0
- data/vscode-extension/README.md +246 -0
- data/vscode-extension/language-configuration.json +31 -0
- data/vscode-extension/package.json +55 -0
- data/vscode-extension/snippets/fakedatadsl.json +198 -0
- data/vscode-extension/syntaxes/fakedatadsl.tmLanguage.json +128 -0
- metadata +478 -0
|
@@ -0,0 +1,513 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Synthra
|
|
4
|
+
module Types
|
|
5
|
+
module Core
|
|
6
|
+
# Array type generator
|
|
7
|
+
#
|
|
8
|
+
# Generates arrays of elements using a specified type generator.
|
|
9
|
+
# Supports both positional and named argument syntax.
|
|
10
|
+
#
|
|
11
|
+
# @example Generate array of text
|
|
12
|
+
# array_type = ArrayType.new
|
|
13
|
+
# array_type.generate(rng, context, { element: "text", size: 1..5 }, :random)
|
|
14
|
+
#
|
|
15
|
+
class ArrayType < Base
|
|
16
|
+
# Generate a random array
|
|
17
|
+
#
|
|
18
|
+
# @param rng [Generator::RNG] random number generator
|
|
19
|
+
# @param context [Generator::Context] generation context
|
|
20
|
+
# @param args [Hash] type arguments
|
|
21
|
+
# @option args [String, Object] :element element type name or generator
|
|
22
|
+
# @option args [Range, Integer, Hash] :size size specification (default: 0..10)
|
|
23
|
+
# @return [Array] generated array
|
|
24
|
+
#
|
|
25
|
+
def generate(rng, context, args, mode)
|
|
26
|
+
element_type = resolve_type(args[:element] || args[:element_type] || "text")
|
|
27
|
+
size_range = extract_size_range(args)
|
|
28
|
+
size = rng.int(size_range.min, size_range.max)
|
|
29
|
+
|
|
30
|
+
size.times.map do
|
|
31
|
+
element_type.generate(rng, context, {}, mode)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Generate a random array
|
|
36
|
+
#
|
|
37
|
+
# @param rng [Generator::RNG] random number generator
|
|
38
|
+
# @param context [Generator::Context] generation context
|
|
39
|
+
# @param args [Hash] type arguments
|
|
40
|
+
# @return [Array] generated array
|
|
41
|
+
#
|
|
42
|
+
def generate_random(rng, context, args)
|
|
43
|
+
generate(rng, context || Generator::Context.new, args, :random)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Generate edge case array values
|
|
47
|
+
#
|
|
48
|
+
# @param rng [Generator::RNG] random number generator
|
|
49
|
+
# @param context [Generator::Context] generation context
|
|
50
|
+
# @param args [Hash] type arguments
|
|
51
|
+
# @return [Array, nil, Integer] edge case value
|
|
52
|
+
#
|
|
53
|
+
def generate_edge(rng, context, args)
|
|
54
|
+
rng.sample([
|
|
55
|
+
[],
|
|
56
|
+
[nil],
|
|
57
|
+
[""],
|
|
58
|
+
[0],
|
|
59
|
+
[1, 2, 3]
|
|
60
|
+
])
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Generate invalid array values
|
|
64
|
+
#
|
|
65
|
+
# @param rng [Generator::RNG] random number generator
|
|
66
|
+
# @param context [Generator::Context] generation context
|
|
67
|
+
# @param args [Hash] type arguments
|
|
68
|
+
# @return [Object] invalid array value
|
|
69
|
+
#
|
|
70
|
+
def generate_invalid(rng, context, args)
|
|
71
|
+
rng.sample([
|
|
72
|
+
nil,
|
|
73
|
+
"not an array",
|
|
74
|
+
123,
|
|
75
|
+
{}
|
|
76
|
+
])
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
private
|
|
80
|
+
|
|
81
|
+
# Resolve element type to a type generator
|
|
82
|
+
#
|
|
83
|
+
# Looks up the type generator for the element type. If not found
|
|
84
|
+
# in the registry, assumes it's a schema reference and creates
|
|
85
|
+
# an ObjectType generator.
|
|
86
|
+
#
|
|
87
|
+
# @param element_type [String, Object] element type name or generator
|
|
88
|
+
# @return [Base] type generator instance
|
|
89
|
+
#
|
|
90
|
+
def resolve_type(element_type)
|
|
91
|
+
case element_type
|
|
92
|
+
when String
|
|
93
|
+
begin
|
|
94
|
+
Types::Registry.lookup(element_type)
|
|
95
|
+
rescue Errors::UnknownTypeError
|
|
96
|
+
# Assume it's a schema reference
|
|
97
|
+
Core::ObjectType.new(element_type)
|
|
98
|
+
end
|
|
99
|
+
when Base
|
|
100
|
+
element_type
|
|
101
|
+
else
|
|
102
|
+
# Default to Text if element_type is not a string or Base instance
|
|
103
|
+
Types::Registry.lookup("text")
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Extract size range from arguments
|
|
108
|
+
#
|
|
109
|
+
# Converts various size specifications into a Range object.
|
|
110
|
+
#
|
|
111
|
+
# @param args [Hash] type arguments
|
|
112
|
+
# @return [Range] size range
|
|
113
|
+
#
|
|
114
|
+
def extract_size_range(args)
|
|
115
|
+
case args[:size]
|
|
116
|
+
when Range
|
|
117
|
+
args[:size]
|
|
118
|
+
when Integer
|
|
119
|
+
args[:size]..args[:size]
|
|
120
|
+
when Hash
|
|
121
|
+
min = args[:size][:min] || args[:size]["min"] || 0
|
|
122
|
+
max = args[:size][:max] || args[:size]["max"] || 10
|
|
123
|
+
min..max
|
|
124
|
+
else
|
|
125
|
+
0..10 # Default range
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# Object type generator (nested schema reference)
|
|
131
|
+
#
|
|
132
|
+
# Generates a complete object by referencing another schema definition.
|
|
133
|
+
# The referenced schema is looked up in the registry and generated
|
|
134
|
+
# recursively.
|
|
135
|
+
#
|
|
136
|
+
# @example Generate nested object
|
|
137
|
+
# object_type = ObjectType.new("Address")
|
|
138
|
+
# object_type.generate(rng, context, {}, :random)
|
|
139
|
+
#
|
|
140
|
+
class ObjectType < Base
|
|
141
|
+
attr_reader :schema_name
|
|
142
|
+
|
|
143
|
+
# Create a new ObjectType generator
|
|
144
|
+
#
|
|
145
|
+
# @param schema_name [String, nil] name of the schema to reference
|
|
146
|
+
#
|
|
147
|
+
def initialize(schema_name = nil)
|
|
148
|
+
@schema_name = schema_name
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# Generate a nested schema object
|
|
152
|
+
#
|
|
153
|
+
# Looks up the referenced schema in the registry and generates
|
|
154
|
+
# a complete object from it.
|
|
155
|
+
#
|
|
156
|
+
# @param rng [Generator::RNG] random number generator
|
|
157
|
+
# @param context [Generator::Context] generation context
|
|
158
|
+
# @param args [Hash] type arguments
|
|
159
|
+
# @option args [String] :schema schema name (if not set in initialize)
|
|
160
|
+
# @return [Hash] generated object
|
|
161
|
+
#
|
|
162
|
+
def generate(rng, context, args, mode)
|
|
163
|
+
case mode
|
|
164
|
+
when :invalid
|
|
165
|
+
return generate_invalid(rng, context, args)
|
|
166
|
+
when :edge
|
|
167
|
+
return generate_edge(rng, context, args)
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
schema_name = @schema_name || args[:schema] || args["schema"]
|
|
171
|
+
return {} unless schema_name
|
|
172
|
+
|
|
173
|
+
registry = context&.registry
|
|
174
|
+
return {} unless registry
|
|
175
|
+
|
|
176
|
+
schema = registry.schema(schema_name)
|
|
177
|
+
return {} unless schema
|
|
178
|
+
|
|
179
|
+
# Get depth and resolver from context for proper recursion handling
|
|
180
|
+
# :nocov: context is guaranteed non-nil here (the `return {} unless registry`
|
|
181
|
+
# guard above already exits when context — and thus registry — is nil), so the
|
|
182
|
+
# `&.` nil-receiver else-arms below are unreachable.
|
|
183
|
+
depth = context&.depth || 0
|
|
184
|
+
resolver = context&.resolver
|
|
185
|
+
# :nocov:
|
|
186
|
+
|
|
187
|
+
# Generate new seed from current rng for unique nested values
|
|
188
|
+
# This ensures each nested schema generates different data
|
|
189
|
+
nested_seed = rng.int(0, 2**31 - 1)
|
|
190
|
+
|
|
191
|
+
# Pass current context as parent_context so nested schemas can access parent fields via copy()
|
|
192
|
+
schema.generate(seed: nested_seed, registry: registry, mode: mode, depth: depth + 1, resolver: resolver, parent_context: context)
|
|
193
|
+
|
|
194
|
+
rescue StandardError
|
|
195
|
+
{}
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
# Generate a random nested object
|
|
200
|
+
#
|
|
201
|
+
# @param rng [Generator::RNG] random number generator
|
|
202
|
+
# @param context [Generator::Context] generation context
|
|
203
|
+
# @param args [Hash] type arguments
|
|
204
|
+
# @return [Hash] generated object
|
|
205
|
+
#
|
|
206
|
+
def generate_random(rng, context, args)
|
|
207
|
+
generate(rng, context, args, :random)
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
# Generate edge case object values
|
|
211
|
+
#
|
|
212
|
+
# @param rng [Generator::RNG] random number generator
|
|
213
|
+
# @param context [Generator::Context] generation context
|
|
214
|
+
# @param args [Hash] type arguments
|
|
215
|
+
# @return [Hash] edge case value
|
|
216
|
+
#
|
|
217
|
+
def generate_edge(rng, context, args)
|
|
218
|
+
{}
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
# Generate invalid object values
|
|
222
|
+
#
|
|
223
|
+
# @param rng [Generator::RNG] random number generator
|
|
224
|
+
# @param context [Generator::Context] generation context
|
|
225
|
+
# @param args [Hash] type arguments
|
|
226
|
+
# @return [Object] invalid object value
|
|
227
|
+
#
|
|
228
|
+
def generate_invalid(rng, context, args)
|
|
229
|
+
rng.sample([
|
|
230
|
+
nil,
|
|
231
|
+
[],
|
|
232
|
+
"not an object",
|
|
233
|
+
123
|
|
234
|
+
])
|
|
235
|
+
end
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
# Special hash for map_by_field that supports both ID keys and entry name access
|
|
239
|
+
# This allows copy("users.sender.id") to work while JSON serializes only ID keys
|
|
240
|
+
class MapByFieldResult < Hash
|
|
241
|
+
def initialize(id_keyed_data, entries_by_name)
|
|
242
|
+
super()
|
|
243
|
+
merge!(id_keyed_data)
|
|
244
|
+
@entries_by_name = entries_by_name
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
def [](key)
|
|
248
|
+
# First check entry names (for copy() access)
|
|
249
|
+
return @entries_by_name[key] if @entries_by_name&.key?(key)
|
|
250
|
+
return @entries_by_name[key.to_s] if @entries_by_name&.key?(key.to_s)
|
|
251
|
+
super
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
# JSON serialization only includes the ID-keyed data
|
|
255
|
+
def to_json(*args)
|
|
256
|
+
Hash[self].to_json(*args)
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
def as_json(*args)
|
|
260
|
+
Hash[self]
|
|
261
|
+
end
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
# MapByField type - creates objects with dynamic keys from a field value
|
|
265
|
+
#
|
|
266
|
+
# Generates multiple objects and uses a specified field as the key.
|
|
267
|
+
# Perfect for Twitter-style user maps where ID is the key.
|
|
268
|
+
#
|
|
269
|
+
# @example DSL usage
|
|
270
|
+
# users: map_by_field("id", sender: TwitterUser, recipient: TwitterUser)
|
|
271
|
+
# # Generates: { "123": { id: "123", ... }, "456": { id: "456", ... } }
|
|
272
|
+
#
|
|
273
|
+
class MapByField < Base
|
|
274
|
+
# Generate a map with dynamic keys
|
|
275
|
+
#
|
|
276
|
+
# @param rng [Generator::RNG] random number generator
|
|
277
|
+
# @param context [Generator::Context] generation context
|
|
278
|
+
# @param args [Hash] type arguments with :key_field and schema entries
|
|
279
|
+
# @return [Hash] map with field values as keys
|
|
280
|
+
#
|
|
281
|
+
def generate(rng, context, args, mode)
|
|
282
|
+
case mode
|
|
283
|
+
when :invalid
|
|
284
|
+
return generate_invalid(rng, context, args)
|
|
285
|
+
when :edge
|
|
286
|
+
return generate_edge(rng, context, args)
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
key_field = args[:key_field] || args["key_field"] || "id"
|
|
290
|
+
entries = args[:entries] || args["entries"] || {}
|
|
291
|
+
|
|
292
|
+
registry = context&.registry
|
|
293
|
+
return {} unless registry
|
|
294
|
+
|
|
295
|
+
result = {}
|
|
296
|
+
entries_by_name = {} # For copy() access via users.sender.id
|
|
297
|
+
# :nocov: context is guaranteed non-nil here (the `return {} unless registry`
|
|
298
|
+
# guard above exits when context — and thus registry — is nil), so the `&.`
|
|
299
|
+
# nil-receiver else-arms on these three reads are unreachable.
|
|
300
|
+
depth = context&.depth || 0
|
|
301
|
+
resolver = context&.resolver
|
|
302
|
+
|
|
303
|
+
# Get shared context for batch-level caching
|
|
304
|
+
shared_context = context&.shared_context || {}
|
|
305
|
+
# :nocov:
|
|
306
|
+
|
|
307
|
+
entries.each do |entry_name, entry_config|
|
|
308
|
+
# Handle both simple schema name and { schema:, shared: } hash
|
|
309
|
+
is_shared = false
|
|
310
|
+
if entry_config.is_a?(Hash)
|
|
311
|
+
schema_name = entry_config[:schema] || entry_config["schema"]
|
|
312
|
+
is_shared = entry_config[:shared] || entry_config["shared"]
|
|
313
|
+
else
|
|
314
|
+
schema_name = entry_config.to_s
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
schema = registry.schema(schema_name) rescue nil
|
|
318
|
+
next unless schema
|
|
319
|
+
|
|
320
|
+
# Check if this entry should be shared across batch
|
|
321
|
+
shared_key = "map_by_field_#{entry_name}"
|
|
322
|
+
if is_shared && shared_context.key?(shared_key)
|
|
323
|
+
# Use cached value from shared context
|
|
324
|
+
obj = shared_context[shared_key]
|
|
325
|
+
else
|
|
326
|
+
# Generate new value
|
|
327
|
+
nested_seed = rng.int(0, 2**31 - 1)
|
|
328
|
+
obj = schema.generate(
|
|
329
|
+
seed: nested_seed,
|
|
330
|
+
registry: registry,
|
|
331
|
+
mode: mode,
|
|
332
|
+
depth: depth + 1,
|
|
333
|
+
resolver: resolver,
|
|
334
|
+
parent_context: context
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
# Cache in shared context if marked as shared
|
|
338
|
+
if is_shared
|
|
339
|
+
shared_context[shared_key] = obj
|
|
340
|
+
# :nocov: context is non-nil here (guarded by `return {} unless registry`
|
|
341
|
+
# earlier, where registry derives from context), so `if context` is always true.
|
|
342
|
+
context.shared_context = shared_context if context
|
|
343
|
+
# :nocov:
|
|
344
|
+
end
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
# Store by entry name for copy() to access
|
|
348
|
+
entries_by_name[entry_name.to_s] = obj
|
|
349
|
+
|
|
350
|
+
# Use the specified field as the key for the result
|
|
351
|
+
key = obj[key_field] || obj[key_field.to_sym]
|
|
352
|
+
result[key] = obj if key
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
# Create a special hash that has both ID keys and entry name access
|
|
356
|
+
MapByFieldResult.new(result, entries_by_name)
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
def generate_random(rng, context, args)
|
|
360
|
+
generate(rng, context, args, :random)
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
def generate_edge(rng, context, args)
|
|
364
|
+
{}
|
|
365
|
+
end
|
|
366
|
+
|
|
367
|
+
def generate_invalid(rng, context, args)
|
|
368
|
+
rng.sample([nil, [], "not a map", 123])
|
|
369
|
+
end
|
|
370
|
+
end
|
|
371
|
+
|
|
372
|
+
# OneOf type - randomly selects one of multiple schemas
|
|
373
|
+
#
|
|
374
|
+
# Generates data from one randomly chosen schema from a list.
|
|
375
|
+
# Perfect for polymorphic types like message payloads, events, etc.
|
|
376
|
+
#
|
|
377
|
+
# @example DSL usage
|
|
378
|
+
# payload: one_of(MessageCreatePayload, TypingPayload, ReadReceiptPayload)
|
|
379
|
+
# # Randomly generates one of the three payload types
|
|
380
|
+
#
|
|
381
|
+
# @example With weights
|
|
382
|
+
# payload: one_of(CommonPayload:80%, RarePayload:20%)
|
|
383
|
+
# # 80% chance of CommonPayload, 20% chance of RarePayload
|
|
384
|
+
#
|
|
385
|
+
class OneOf < Base
|
|
386
|
+
# Generate data from one of the specified schemas
|
|
387
|
+
#
|
|
388
|
+
# @param rng [Generator::RNG] random number generator
|
|
389
|
+
# @param context [Generator::Context] generation context
|
|
390
|
+
# @param args [Hash] type arguments with :schemas array
|
|
391
|
+
# @return [Hash] generated data from the selected schema
|
|
392
|
+
#
|
|
393
|
+
def generate(rng, context, args, mode)
|
|
394
|
+
case mode
|
|
395
|
+
when :invalid
|
|
396
|
+
return generate_invalid(rng, context, args)
|
|
397
|
+
when :edge
|
|
398
|
+
return generate_edge(rng, context, args)
|
|
399
|
+
end
|
|
400
|
+
|
|
401
|
+
schemas = args[:schemas] || args["schemas"] || []
|
|
402
|
+
return nil if schemas.empty?
|
|
403
|
+
|
|
404
|
+
registry = context&.registry
|
|
405
|
+
return nil unless registry
|
|
406
|
+
|
|
407
|
+
# Select a schema based on weights (if provided)
|
|
408
|
+
selected = select_schema(rng, schemas)
|
|
409
|
+
return nil unless selected
|
|
410
|
+
|
|
411
|
+
# Get schema name (could be string or hash with weight)
|
|
412
|
+
schema_name = selected.is_a?(Hash) ? (selected[:schema] || selected["schema"]) : selected.to_s
|
|
413
|
+
|
|
414
|
+
schema = registry.schema(schema_name) rescue nil
|
|
415
|
+
return nil unless schema
|
|
416
|
+
|
|
417
|
+
# Generate from the selected schema
|
|
418
|
+
# :nocov: context is guaranteed non-nil here (the `return nil unless registry`
|
|
419
|
+
# guard above exits when context — and thus registry — is nil), so the `&.`
|
|
420
|
+
# nil-receiver else-arms on these three reads are unreachable.
|
|
421
|
+
depth = context&.depth || 0
|
|
422
|
+
resolver = context&.resolver
|
|
423
|
+
shared_context = context&.shared_context
|
|
424
|
+
# :nocov:
|
|
425
|
+
|
|
426
|
+
nested_seed = rng.int(0, 2**31 - 1)
|
|
427
|
+
schema.generate(
|
|
428
|
+
seed: nested_seed,
|
|
429
|
+
registry: registry,
|
|
430
|
+
mode: mode,
|
|
431
|
+
depth: depth + 1,
|
|
432
|
+
resolver: resolver,
|
|
433
|
+
parent_context: context,
|
|
434
|
+
shared_context: shared_context
|
|
435
|
+
)
|
|
436
|
+
end
|
|
437
|
+
|
|
438
|
+
def generate_random(rng, context, args)
|
|
439
|
+
generate(rng, context, args, :random)
|
|
440
|
+
end
|
|
441
|
+
|
|
442
|
+
def generate_edge(rng, context, args)
|
|
443
|
+
# In edge mode, return first schema's edge case
|
|
444
|
+
schemas = args[:schemas] || args["schemas"] || []
|
|
445
|
+
return nil if schemas.empty?
|
|
446
|
+
|
|
447
|
+
registry = context&.registry
|
|
448
|
+
return nil unless registry
|
|
449
|
+
|
|
450
|
+
first_schema = schemas.first
|
|
451
|
+
schema_name = first_schema.is_a?(Hash) ? (first_schema[:schema] || first_schema["schema"]) : first_schema.to_s
|
|
452
|
+
|
|
453
|
+
schema = registry.schema(schema_name) rescue nil
|
|
454
|
+
return {} unless schema
|
|
455
|
+
|
|
456
|
+
# :nocov: context is non-nil here (the `return nil unless registry` guard above
|
|
457
|
+
# exits when context — and thus registry — is nil), so this `&.` else is unreachable.
|
|
458
|
+
depth = context&.depth || 0
|
|
459
|
+
# :nocov:
|
|
460
|
+
nested_seed = rng.int(0, 2**31 - 1)
|
|
461
|
+
schema.generate(seed: nested_seed, registry: registry, mode: :edge, depth: depth + 1)
|
|
462
|
+
end
|
|
463
|
+
|
|
464
|
+
def generate_invalid(rng, context, args)
|
|
465
|
+
rng.sample([nil, "not-an-object", 123, [], {}])
|
|
466
|
+
end
|
|
467
|
+
|
|
468
|
+
private
|
|
469
|
+
|
|
470
|
+
# Select a schema based on weights
|
|
471
|
+
#
|
|
472
|
+
# @param rng [Generator::RNG] random number generator
|
|
473
|
+
# @param schemas [Array] list of schemas (strings or hashes with weights)
|
|
474
|
+
# @return [Object] selected schema entry
|
|
475
|
+
#
|
|
476
|
+
def select_schema(rng, schemas)
|
|
477
|
+
# Check if any schema has weights
|
|
478
|
+
has_weights = schemas.any? { |s| s.is_a?(Hash) && (s[:weight] || s["weight"]) }
|
|
479
|
+
|
|
480
|
+
if has_weights
|
|
481
|
+
# Weighted selection
|
|
482
|
+
total_weight = schemas.sum do |s|
|
|
483
|
+
if s.is_a?(Hash)
|
|
484
|
+
s[:weight] || s["weight"] || 1
|
|
485
|
+
else
|
|
486
|
+
1
|
|
487
|
+
end
|
|
488
|
+
end
|
|
489
|
+
|
|
490
|
+
roll = rng.int(1, total_weight)
|
|
491
|
+
cumulative = 0
|
|
492
|
+
|
|
493
|
+
schemas.each do |s|
|
|
494
|
+
weight = if s.is_a?(Hash)
|
|
495
|
+
s[:weight] || s["weight"] || 1
|
|
496
|
+
else
|
|
497
|
+
1
|
|
498
|
+
end
|
|
499
|
+
cumulative += weight
|
|
500
|
+
return s if roll <= cumulative
|
|
501
|
+
end
|
|
502
|
+
|
|
503
|
+
schemas.last # Fallback
|
|
504
|
+
else
|
|
505
|
+
# Equal probability
|
|
506
|
+
rng.sample(schemas)
|
|
507
|
+
end
|
|
508
|
+
end
|
|
509
|
+
end
|
|
510
|
+
end
|
|
511
|
+
end
|
|
512
|
+
end
|
|
513
|
+
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Synthra
|
|
4
|
+
module Types
|
|
5
|
+
module Core
|
|
6
|
+
# Default value type generator
|
|
7
|
+
#
|
|
8
|
+
# Returns a default value, optionally with a probability of being used.
|
|
9
|
+
# If probability is less than 1.0, returns nil some of the time.
|
|
10
|
+
#
|
|
11
|
+
# @example Generate default value
|
|
12
|
+
# default = DefaultValue.new
|
|
13
|
+
# default.generate_random(rng, context, { value: "Anonymous" }, :random)
|
|
14
|
+
# # => "Anonymous"
|
|
15
|
+
#
|
|
16
|
+
# @example Default value with probability
|
|
17
|
+
# default.generate_random(rng, context, { value: "Guest", probability: 0.8 }, :random)
|
|
18
|
+
# # => "Guest" (80% of the time) or nil (20% of the time)
|
|
19
|
+
#
|
|
20
|
+
class DefaultValue < Base
|
|
21
|
+
# Generate a default value
|
|
22
|
+
#
|
|
23
|
+
# @param rng [Generator::RNG] random number generator
|
|
24
|
+
# @param context [Generator::Context] generation context (not used)
|
|
25
|
+
# @param args [Hash] type arguments
|
|
26
|
+
# @option args [Object] :value default value to return
|
|
27
|
+
# @option args [Float] :probability probability of using default (0.0-1.0, default: 1.0)
|
|
28
|
+
# @return [Object, nil] default value or nil
|
|
29
|
+
#
|
|
30
|
+
def generate_random(rng, context, args)
|
|
31
|
+
value = args[:value] || args["value"]
|
|
32
|
+
probability = args[:probability] || args["probability"] || 1.0
|
|
33
|
+
|
|
34
|
+
# If probability is less than 1.0, randomly return nil
|
|
35
|
+
if probability < 1.0
|
|
36
|
+
rng.rand < probability ? value : nil
|
|
37
|
+
else
|
|
38
|
+
value
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Generate edge case default values
|
|
43
|
+
#
|
|
44
|
+
# @param rng [Generator::RNG] random number generator
|
|
45
|
+
# @param context [Generator::Context] generation context
|
|
46
|
+
# @param args [Hash] type arguments
|
|
47
|
+
# @return [Object, nil] edge case value
|
|
48
|
+
#
|
|
49
|
+
def generate_edge(rng, context, args)
|
|
50
|
+
value = args[:value] || args["value"]
|
|
51
|
+
rng.sample([value, nil])
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Generate invalid default values
|
|
55
|
+
#
|
|
56
|
+
# @param rng [Generator::RNG] random number generator
|
|
57
|
+
# @param context [Generator::Context] generation context
|
|
58
|
+
# @param args [Hash] type arguments
|
|
59
|
+
# @return [Object] invalid value
|
|
60
|
+
#
|
|
61
|
+
def generate_invalid(rng, context, args)
|
|
62
|
+
value = args[:value] || args["value"]
|
|
63
|
+
case value
|
|
64
|
+
when String
|
|
65
|
+
rng.sample([nil, 123, [], {}])
|
|
66
|
+
when Integer
|
|
67
|
+
rng.sample([nil, "not a number", [], {}])
|
|
68
|
+
else
|
|
69
|
+
nil
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Synthra
|
|
4
|
+
module Types
|
|
5
|
+
module Core
|
|
6
|
+
# Enum type generator
|
|
7
|
+
#
|
|
8
|
+
# Generates values from a predefined list, optionally with probability weights.
|
|
9
|
+
#
|
|
10
|
+
# @example Simple enum
|
|
11
|
+
# enum(active, inactive)
|
|
12
|
+
#
|
|
13
|
+
# @example With probabilities
|
|
14
|
+
# enum(active: 80%, inactive: 20%)
|
|
15
|
+
#
|
|
16
|
+
class Enum < Base
|
|
17
|
+
# Generate a random enum value
|
|
18
|
+
#
|
|
19
|
+
# @param rng [Generator::RNG] random number generator
|
|
20
|
+
# @param context [Generator::Context] generation context
|
|
21
|
+
# @param args [Hash] type arguments
|
|
22
|
+
# @option args [Array] :values array of enum values (with probabilities)
|
|
23
|
+
# @option args [Array<String>] :values simple string array
|
|
24
|
+
# @return [Object] selected enum value
|
|
25
|
+
#
|
|
26
|
+
def generate_random(rng, context, args)
|
|
27
|
+
values = args[:values] || args["values"] || []
|
|
28
|
+
|
|
29
|
+
if values.empty?
|
|
30
|
+
return nil
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Check if values are simple strings/objects or have probabilities
|
|
34
|
+
if values.first.respond_to?(:probability)
|
|
35
|
+
# Weighted selection
|
|
36
|
+
weighted_choice(rng, values)
|
|
37
|
+
else
|
|
38
|
+
# Simple random selection
|
|
39
|
+
rng.sample(values)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Generate edge case enum value
|
|
44
|
+
#
|
|
45
|
+
# @param rng [Generator::RNG] random number generator
|
|
46
|
+
# @param context [Generator::Context] generation context
|
|
47
|
+
# @param args [Hash] type arguments
|
|
48
|
+
# @return [Object] edge case enum value
|
|
49
|
+
#
|
|
50
|
+
def generate_edge(rng, context, args)
|
|
51
|
+
values = args[:values] || args["values"] || []
|
|
52
|
+
return nil if values.empty?
|
|
53
|
+
|
|
54
|
+
# Return first or last value
|
|
55
|
+
if values.first.respond_to?(:value)
|
|
56
|
+
candidates = [values.first.value, values.last.value].compact
|
|
57
|
+
else
|
|
58
|
+
candidates = [values.first, values.last].compact
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
rng.sample(candidates)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Generate invalid enum value
|
|
65
|
+
#
|
|
66
|
+
# @param rng [Generator::RNG] random number generator
|
|
67
|
+
# @param context [Generator::Context] generation context
|
|
68
|
+
# @param args [Hash] type arguments
|
|
69
|
+
# @return [Object] invalid value
|
|
70
|
+
#
|
|
71
|
+
def generate_invalid(rng, context, args)
|
|
72
|
+
rng.sample([nil, "invalid_enum", [], {}, 123])
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
private
|
|
76
|
+
|
|
77
|
+
# Select a value using weighted probabilities
|
|
78
|
+
#
|
|
79
|
+
# @param rng [Generator::RNG] random number generator
|
|
80
|
+
# @param values [Array] array of value objects with probabilities
|
|
81
|
+
# @return [Object] selected value
|
|
82
|
+
#
|
|
83
|
+
def weighted_choice(rng, values)
|
|
84
|
+
total = values.sum { |v| v.probability || 0 }
|
|
85
|
+
return values.first.value if total.zero?
|
|
86
|
+
|
|
87
|
+
random = rng.int(1, total)
|
|
88
|
+
cumulative = 0
|
|
89
|
+
|
|
90
|
+
values.each do |value|
|
|
91
|
+
cumulative += (value.probability || 0)
|
|
92
|
+
return value.value if random <= cumulative
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Fallback to first value
|
|
96
|
+
values.first.value
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|