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,586 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Synthra
|
|
4
|
+
module Types
|
|
5
|
+
module Core
|
|
6
|
+
# Text type generator with locale support
|
|
7
|
+
#
|
|
8
|
+
# Generates random text strings with optional length constraints and locale support.
|
|
9
|
+
# Uses Faker for locale-specific text generation when available.
|
|
10
|
+
#
|
|
11
|
+
# @example Basic usage
|
|
12
|
+
# text(50..100) # 50-100 characters
|
|
13
|
+
# text(100) # Exactly 100 characters
|
|
14
|
+
# text(min: 50, max: 200) # Named arguments
|
|
15
|
+
#
|
|
16
|
+
# @example With locale
|
|
17
|
+
# text(ar) # Arabic text (default length)
|
|
18
|
+
# text(50, language: :ar) # Arabic text, ~50 chars
|
|
19
|
+
# text(locale: :es) # Spanish text
|
|
20
|
+
#
|
|
21
|
+
# @example Multiple args: locale + length (order-independent)
|
|
22
|
+
# text(ar, 1..10) # Arabic text, 1-10 characters
|
|
23
|
+
# text(1..10, ar) # Same: 1-10 chars, Arabic
|
|
24
|
+
# text(en, 5..50) # English text, 5-50 characters
|
|
25
|
+
#
|
|
26
|
+
class Text < Base
|
|
27
|
+
# Generate random text
|
|
28
|
+
#
|
|
29
|
+
# @param rng [Generator::RNG] random number generator
|
|
30
|
+
# @param context [Generator::Context] generation context
|
|
31
|
+
# @param args [Hash] type arguments
|
|
32
|
+
# @option args [Integer] :min minimum length
|
|
33
|
+
# @option args [Integer] :max maximum length
|
|
34
|
+
# @option args [Range] :range length range
|
|
35
|
+
# @option args [Hash] :range hash with :min and :max keys
|
|
36
|
+
# @option args [Symbol, String] :language locale alias (:en, :ar, :es, etc.)
|
|
37
|
+
# @option args [Symbol, String] :locale locale identifier
|
|
38
|
+
# @return [String] generated text
|
|
39
|
+
#
|
|
40
|
+
def generate_random(rng, context, args)
|
|
41
|
+
# Check for locale in various argument formats:
|
|
42
|
+
# 1. Explicit: text(locale: :ar), text(language: :ar), text(lang: :ar)
|
|
43
|
+
# 2. Shorthand: text(ar) - parser should store this as args[:locale] since Synthra 1.x
|
|
44
|
+
# 3. Fallback: text(ar) - older versions stored as args[:value]
|
|
45
|
+
# An Integer or Range :value is a LENGTH (e.g. text(50), text(50..200)), not a locale,
|
|
46
|
+
# so it must not be fed to normalize_locale (Integer#to_sym would raise NoMethodError).
|
|
47
|
+
locale_arg = args[:value]
|
|
48
|
+
locale_arg = nil if locale_arg.is_a?(Integer) || locale_arg.is_a?(Range)
|
|
49
|
+
locale_value = args[:locale] || args[:language] || args[:lang] || locale_arg
|
|
50
|
+
locale = normalize_locale(locale_value)
|
|
51
|
+
|
|
52
|
+
# Prefer readable sentences/words over random character strings
|
|
53
|
+
generate_readable_text(rng, context, args, locale)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Generate edge case text
|
|
57
|
+
#
|
|
58
|
+
# @param rng [Generator::RNG] random number generator
|
|
59
|
+
# @param context [Generator::Context] generation context
|
|
60
|
+
# @param args [Hash] type arguments
|
|
61
|
+
# @return [String] edge case text
|
|
62
|
+
#
|
|
63
|
+
def generate_edge(rng, context, args)
|
|
64
|
+
rng.sample(Generator::Modes::EDGE_STRINGS)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Generate invalid text values
|
|
68
|
+
#
|
|
69
|
+
# @param rng [Generator::RNG] random number generator
|
|
70
|
+
# @param context [Generator::Context] generation context
|
|
71
|
+
# @param args [Hash] type arguments
|
|
72
|
+
# @return [Object] invalid value (nil, number, etc.)
|
|
73
|
+
#
|
|
74
|
+
def generate_invalid(rng, context, args)
|
|
75
|
+
rng.sample([nil, 123, [], {}, true, false])
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Generate hostile/malicious text for security testing
|
|
79
|
+
#
|
|
80
|
+
# Returns attack vectors including SQLi, XSS, path traversal,
|
|
81
|
+
# buffer overflows, and unicode edge cases.
|
|
82
|
+
#
|
|
83
|
+
# @param rng [Generator::RNG] random number generator
|
|
84
|
+
# @param context [Generator::Context] generation context
|
|
85
|
+
# @param args [Hash] type arguments
|
|
86
|
+
# @return [String] hostile payload
|
|
87
|
+
#
|
|
88
|
+
def generate_hostile(rng, context, args)
|
|
89
|
+
require_relative "../hostile_payloads"
|
|
90
|
+
rng.sample(HostilePayloads::ALL)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
private
|
|
94
|
+
|
|
95
|
+
# Extract length constraints from arguments
|
|
96
|
+
#
|
|
97
|
+
# @param args [Hash] type arguments
|
|
98
|
+
# @return [Array<Integer>] [min_length, max_length]
|
|
99
|
+
#
|
|
100
|
+
def extract_length(args)
|
|
101
|
+
if args[:range].is_a?(Range)
|
|
102
|
+
args[:range].minmax
|
|
103
|
+
elsif args[:range].is_a?(Hash)
|
|
104
|
+
[args[:range][:min] || args[:range]["min"] || 10, args[:range][:max] || args[:range]["max"] || 100]
|
|
105
|
+
elsif args[:min] || args[:max]
|
|
106
|
+
[args[:min] || 10, args[:max] || 100]
|
|
107
|
+
elsif args[:value].is_a?(Integer)
|
|
108
|
+
[args[:value], args[:value]]
|
|
109
|
+
elsif args[:value].is_a?(Range)
|
|
110
|
+
args[:value].minmax
|
|
111
|
+
else
|
|
112
|
+
[10, 100] # Default range
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Normalize locale identifier
|
|
117
|
+
#
|
|
118
|
+
# @param locale [Symbol, String] locale identifier
|
|
119
|
+
# @return [Symbol] normalized locale
|
|
120
|
+
#
|
|
121
|
+
def normalize_locale(locale)
|
|
122
|
+
return :en if locale.nil?
|
|
123
|
+
|
|
124
|
+
locale_str = locale.to_s.downcase
|
|
125
|
+
case locale_str
|
|
126
|
+
when "en", "english" then :en
|
|
127
|
+
when "ar", "arabic" then :ar
|
|
128
|
+
when "es", "spanish" then :es
|
|
129
|
+
when "fr", "french" then :fr
|
|
130
|
+
when "de", "german" then :de
|
|
131
|
+
when "it", "italian" then :it
|
|
132
|
+
when "pt", "portuguese" then :pt
|
|
133
|
+
when "ru", "russian" then :ru
|
|
134
|
+
when "zh", "chinese" then :zh_CN
|
|
135
|
+
when "ja", "japanese" then :ja
|
|
136
|
+
when "ko", "korean" then :ko
|
|
137
|
+
when "hi", "hindi" then :hi
|
|
138
|
+
else
|
|
139
|
+
locale.to_sym
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# Generate readable text (words/sentences) instead of random character strings.
|
|
144
|
+
# Respects character length when args include :range, :min/:max, or :value.
|
|
145
|
+
#
|
|
146
|
+
# @param rng [Generator::RNG] random number generator
|
|
147
|
+
# @param context [Generator::Context] generation context
|
|
148
|
+
# @param args [Hash] type arguments
|
|
149
|
+
# @param locale [Symbol] locale identifier
|
|
150
|
+
# @return [String] generated readable text
|
|
151
|
+
#
|
|
152
|
+
def generate_readable_text(rng, context, args, locale)
|
|
153
|
+
min_len, max_len = extract_length(args)
|
|
154
|
+
word_count = rng.int([min_len / 8, 2].max, [max_len / 5, 25].max)
|
|
155
|
+
|
|
156
|
+
text = begin
|
|
157
|
+
with_seeded_faker(rng) do
|
|
158
|
+
with_faker_locale(locale) do
|
|
159
|
+
if english_locale?(locale)
|
|
160
|
+
faker_english_words(word_count)
|
|
161
|
+
else
|
|
162
|
+
faker_localized_words(word_count, locale)
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
rescue StandardError
|
|
167
|
+
with_seeded_faker(rng) do
|
|
168
|
+
with_faker_locale(locale) { Faker::Lorem.words(number: [word_count, 10].min).join(" ") }
|
|
169
|
+
end
|
|
170
|
+
# :nocov: sibling rescue cannot catch errors raised by the rescue above it (dead code)
|
|
171
|
+
rescue StandardError
|
|
172
|
+
"Sample text"
|
|
173
|
+
# :nocov:
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
truncate_to_length(text, rng, min_len, max_len, args)
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
# Seed Faker deterministically from rng so word/sentence CONTENT — not just the word
|
|
180
|
+
# COUNT — is reproducible by seed. Without this, Faker::Books::Lovecraft.words (and the
|
|
181
|
+
# other word providers below) use Faker's process-global RNG and vary across runs even
|
|
182
|
+
# for the same generation seed. Derives a fresh per-call seed from rng (which advances,
|
|
183
|
+
# so each record still differs) and restores the previous Faker::Config.random after.
|
|
184
|
+
def with_seeded_faker(rng)
|
|
185
|
+
return yield unless defined?(Faker::Config) && Faker::Config.respond_to?(:random)
|
|
186
|
+
|
|
187
|
+
previous = Faker::Config.random
|
|
188
|
+
Faker::Config.random = Random.new(rng.rand(2**31))
|
|
189
|
+
begin
|
|
190
|
+
yield
|
|
191
|
+
ensure
|
|
192
|
+
Faker::Config.random = previous
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def with_faker_locale(locale)
|
|
197
|
+
return yield unless Faker::Config.respond_to?(:locale=)
|
|
198
|
+
|
|
199
|
+
original = Faker::Config.locale
|
|
200
|
+
Faker::Config.locale = locale
|
|
201
|
+
yield
|
|
202
|
+
ensure
|
|
203
|
+
Faker::Config.locale = original
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
def english_locale?(locale)
|
|
207
|
+
locale.to_s.downcase.start_with?("en")
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
# Truncate or slice text to fit within [min_len, max_len] when length was explicitly specified.
|
|
211
|
+
# Preserves locale (e.g. Arabic text stays Arabic).
|
|
212
|
+
#
|
|
213
|
+
def truncate_to_length(text, rng, min_len, max_len, args)
|
|
214
|
+
has_explicit_length = args.key?(:range) || args.key?(:min) || args.key?(:max) ||
|
|
215
|
+
(args[:value].is_a?(Integer) || args[:value].is_a?(Range))
|
|
216
|
+
return text unless has_explicit_length
|
|
217
|
+
return "" if max_len <= 0 # zero-length requested → empty string (clamp(1, 0) would raise)
|
|
218
|
+
|
|
219
|
+
target_len = rng.int(min_len.clamp(1, max_len), max_len)
|
|
220
|
+
text.length > target_len ? text[0, target_len] : text
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
# English words for text/string — Faker::Hipster reads as English (e.g. "artisan kale tote
|
|
224
|
+
# bag"), matching the `words` type. (Was Faker::Books::Lovecraft, which leaked horror
|
|
225
|
+
# vocabulary "stygian eldritch cyclopean tentacles" into post content, excerpts and tags.)
|
|
226
|
+
def faker_english_words(word_count)
|
|
227
|
+
word_count = word_count.clamp(1, 25)
|
|
228
|
+
Faker::Hipster.words(number: word_count).join(" ")
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
# Localized words via Faker (for non-English locales)
|
|
232
|
+
# Uses locale-aware Faker providers that actually generate text in the target language
|
|
233
|
+
def faker_localized_words(word_count, locale)
|
|
234
|
+
word_count = word_count.clamp(1, 25)
|
|
235
|
+
|
|
236
|
+
# Build text from locale-aware providers
|
|
237
|
+
words = []
|
|
238
|
+
attempts = 0
|
|
239
|
+
max_attempts = word_count * 3
|
|
240
|
+
|
|
241
|
+
while words.length < word_count && attempts < max_attempts
|
|
242
|
+
attempts += 1
|
|
243
|
+
|
|
244
|
+
# Try various localized Faker providers
|
|
245
|
+
# :nocov: each `if defined?(Faker::X)` guard's false arm is unreachable (Faker is a hard
|
|
246
|
+
# dependency, always loaded); the case arm chosen by rand(6) is also non-deterministic.
|
|
247
|
+
text = case rand(6)
|
|
248
|
+
when 0
|
|
249
|
+
Faker::Name.name if defined?(Faker::Name)
|
|
250
|
+
when 1
|
|
251
|
+
Faker::Address.city if defined?(Faker::Address)
|
|
252
|
+
when 2
|
|
253
|
+
Faker::Company.name if defined?(Faker::Company)
|
|
254
|
+
when 3
|
|
255
|
+
Faker::Address.country if defined?(Faker::Address)
|
|
256
|
+
when 4
|
|
257
|
+
Faker::Address.street_name if defined?(Faker::Address)
|
|
258
|
+
else
|
|
259
|
+
Faker::Company.industry if defined?(Faker::Company) && Faker::Company.respond_to?(:industry)
|
|
260
|
+
end
|
|
261
|
+
# :nocov:
|
|
262
|
+
|
|
263
|
+
words += text.to_s.split(/\s+/) if text
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
words.first(word_count).join(" ")
|
|
267
|
+
rescue StandardError
|
|
268
|
+
# Fallback to Lorem if localized providers fail
|
|
269
|
+
Faker::Lorem.words(number: word_count).join(" ")
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
# Generate text with locale support (character-based; used when explicit length needed)
|
|
273
|
+
#
|
|
274
|
+
def generate_text_with_locale(rng, context, length, locale)
|
|
275
|
+
adapter = faker_adapter(context)
|
|
276
|
+
|
|
277
|
+
if adapter
|
|
278
|
+
adapter.characters(number: length, locale: locale)
|
|
279
|
+
|
|
280
|
+
else
|
|
281
|
+
original_locale = Faker::Config.locale
|
|
282
|
+
begin
|
|
283
|
+
Faker::Config.locale = locale if Faker::Config.respond_to?(:locale=)
|
|
284
|
+
Faker::Lorem.characters(number: length)
|
|
285
|
+
rescue StandardError
|
|
286
|
+
generate_random_alphanumeric(rng, length)
|
|
287
|
+
ensure
|
|
288
|
+
Faker::Config.locale = original_locale if Faker::Config.respond_to?(:locale=)
|
|
289
|
+
end
|
|
290
|
+
end
|
|
291
|
+
rescue StandardError
|
|
292
|
+
generate_random_alphanumeric(rng, length)
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
# Generate random alphanumeric text (fallback)
|
|
296
|
+
#
|
|
297
|
+
# @param rng [Generator::RNG] random number generator
|
|
298
|
+
# @param length [Integer] desired text length
|
|
299
|
+
# @return [String] generated alphanumeric text
|
|
300
|
+
#
|
|
301
|
+
def generate_random_alphanumeric(rng, length)
|
|
302
|
+
chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a
|
|
303
|
+
length.times.map { rng.sample(chars) }.join
|
|
304
|
+
end
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
# Number type generator (integer or float)
|
|
308
|
+
#
|
|
309
|
+
# Generates numeric values with optional range constraints. Automatically
|
|
310
|
+
# detects whether to generate integers or floats based on the range.
|
|
311
|
+
#
|
|
312
|
+
# @example Integer range
|
|
313
|
+
# number(1..100) # Integer between 1 and 100
|
|
314
|
+
#
|
|
315
|
+
# @example Float range
|
|
316
|
+
# number(0.0..1.0) # Float between 0.0 and 1.0
|
|
317
|
+
#
|
|
318
|
+
# @example Named arguments
|
|
319
|
+
# number(min: 10, max: 100)
|
|
320
|
+
#
|
|
321
|
+
class Number < Base
|
|
322
|
+
# Generate a random number
|
|
323
|
+
#
|
|
324
|
+
# @param rng [Generator::RNG] random number generator
|
|
325
|
+
# @param context [Generator::Context] generation context
|
|
326
|
+
# @param args [Hash] type arguments
|
|
327
|
+
# @option args [Range] :range numeric range
|
|
328
|
+
# @option args [Integer, Float] :min minimum value
|
|
329
|
+
# @option args [Integer, Float] :max maximum value
|
|
330
|
+
# @return [Integer, Float] generated number
|
|
331
|
+
#
|
|
332
|
+
def generate_random(rng, context, args)
|
|
333
|
+
range = extract_range(args)
|
|
334
|
+
if range
|
|
335
|
+
# Detect if we need floats based on range values
|
|
336
|
+
if range.first.is_a?(Float) || range.last.is_a?(Float)
|
|
337
|
+
(rng.rand * (range.last - range.first)) + range.first
|
|
338
|
+
else
|
|
339
|
+
rng.int(range.first, range.last)
|
|
340
|
+
end
|
|
341
|
+
else
|
|
342
|
+
# Default: integer from 0 to 1000
|
|
343
|
+
rng.int(0, 1000)
|
|
344
|
+
end
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
# Generate edge case numbers
|
|
348
|
+
#
|
|
349
|
+
# @param rng [Generator::RNG] random number generator
|
|
350
|
+
# @param context [Generator::Context] generation context
|
|
351
|
+
# @param args [Hash] type arguments
|
|
352
|
+
# @return [Integer, Float] edge case number
|
|
353
|
+
#
|
|
354
|
+
def generate_edge(rng, context, args)
|
|
355
|
+
range = extract_range(args)
|
|
356
|
+
if range
|
|
357
|
+
# Return boundary values
|
|
358
|
+
candidates = [range.first, range.last]
|
|
359
|
+
rng.sample(candidates)
|
|
360
|
+
else
|
|
361
|
+
rng.sample(Generator::Modes::EDGE_NUMBERS)
|
|
362
|
+
end
|
|
363
|
+
end
|
|
364
|
+
|
|
365
|
+
# Generate invalid number values
|
|
366
|
+
#
|
|
367
|
+
# @param rng [Generator::RNG] random number generator
|
|
368
|
+
# @param context [Generator::Context] generation context
|
|
369
|
+
# @param args [Hash] type arguments
|
|
370
|
+
# @return [Object] invalid value
|
|
371
|
+
#
|
|
372
|
+
def generate_invalid(rng, context, args)
|
|
373
|
+
rng.sample(["not a number", nil, "NaN", Float::INFINITY, "", []])
|
|
374
|
+
end
|
|
375
|
+
|
|
376
|
+
# Generate hostile numeric values for security testing
|
|
377
|
+
#
|
|
378
|
+
# Returns extreme values designed to cause overflows,
|
|
379
|
+
# underflows, or unexpected behavior.
|
|
380
|
+
#
|
|
381
|
+
# @param rng [Generator::RNG] random number generator
|
|
382
|
+
# @param context [Generator::Context] generation context
|
|
383
|
+
# @param args [Hash] type arguments
|
|
384
|
+
# @return [Numeric] hostile numeric value
|
|
385
|
+
#
|
|
386
|
+
def generate_hostile(rng, context, args)
|
|
387
|
+
hostile_numbers = [
|
|
388
|
+
-2**63, # MIN_INT64
|
|
389
|
+
(2**63) - 1, # MAX_INT64
|
|
390
|
+
-2**31, # MIN_INT32
|
|
391
|
+
(2**31) - 1, # MAX_INT32
|
|
392
|
+
Float::INFINITY,
|
|
393
|
+
-Float::INFINITY,
|
|
394
|
+
Float::NAN,
|
|
395
|
+
0.0 / 0.0,
|
|
396
|
+
1.0 / 0.0,
|
|
397
|
+
-1.0 / 0.0,
|
|
398
|
+
2**128, # Extreme large number
|
|
399
|
+
-2**128,
|
|
400
|
+
0.0000000000001, # Very small float
|
|
401
|
+
9_999_999_999_999_999_999_999_999_999_999_999_999,
|
|
402
|
+
-9_999_999_999_999_999_999_999_999_999_999_999_999
|
|
403
|
+
]
|
|
404
|
+
rng.sample(hostile_numbers)
|
|
405
|
+
end
|
|
406
|
+
|
|
407
|
+
private
|
|
408
|
+
|
|
409
|
+
# Extract range from arguments
|
|
410
|
+
#
|
|
411
|
+
# @param args [Hash] type arguments
|
|
412
|
+
# @return [Range, nil] extracted range
|
|
413
|
+
#
|
|
414
|
+
def extract_range(args)
|
|
415
|
+
if args[:range].is_a?(Range)
|
|
416
|
+
args[:range]
|
|
417
|
+
elsif args[:min] || args[:max]
|
|
418
|
+
min = args[:min] || 0
|
|
419
|
+
max = args[:max] || 100
|
|
420
|
+
min..max
|
|
421
|
+
elsif args[:value].is_a?(Range)
|
|
422
|
+
args[:value] # fallback
|
|
423
|
+
else
|
|
424
|
+
nil
|
|
425
|
+
end
|
|
426
|
+
end
|
|
427
|
+
end
|
|
428
|
+
|
|
429
|
+
# Boolean type generator
|
|
430
|
+
#
|
|
431
|
+
# Generates true/false values with optional probability weights.
|
|
432
|
+
#
|
|
433
|
+
# @example Default (50/50)
|
|
434
|
+
# boolean
|
|
435
|
+
#
|
|
436
|
+
# @example With probability
|
|
437
|
+
# boolean(true: 80%) # 80% chance of true
|
|
438
|
+
# boolean(false: 30%) # 30% chance of false
|
|
439
|
+
#
|
|
440
|
+
class Boolean < Base
|
|
441
|
+
# Generate a random boolean
|
|
442
|
+
#
|
|
443
|
+
# @param rng [Generator::RNG] random number generator
|
|
444
|
+
# @param context [Generator::Context] generation context
|
|
445
|
+
# @param args [Hash] type arguments
|
|
446
|
+
# @option args [Integer] :true_probability probability of true (0-100)
|
|
447
|
+
# @option args [Integer] :false_probability probability of false (0-100)
|
|
448
|
+
# @return [Boolean] generated boolean value
|
|
449
|
+
#
|
|
450
|
+
def generate_random(rng, context, args)
|
|
451
|
+
true_prob = extract_probability(args[:true_probability] || args["true_probability"])
|
|
452
|
+
false_prob = extract_probability(args[:false_probability] || args["false_probability"])
|
|
453
|
+
|
|
454
|
+
threshold = if true_prob && true_prob > 0
|
|
455
|
+
true_prob
|
|
456
|
+
elsif false_prob && false_prob > 0
|
|
457
|
+
100 - false_prob
|
|
458
|
+
else
|
|
459
|
+
50 # Default 50/50
|
|
460
|
+
end
|
|
461
|
+
|
|
462
|
+
rng.int(1, 100) <= threshold
|
|
463
|
+
end
|
|
464
|
+
|
|
465
|
+
# Generate edge case boolean
|
|
466
|
+
#
|
|
467
|
+
# @param rng [Generator::RNG] random number generator
|
|
468
|
+
# @param context [Generator::Context] generation context
|
|
469
|
+
# @param args [Hash] type arguments
|
|
470
|
+
# @return [Boolean] edge case boolean
|
|
471
|
+
#
|
|
472
|
+
def generate_edge(rng, context, args)
|
|
473
|
+
rng.sample([true, false])
|
|
474
|
+
end
|
|
475
|
+
|
|
476
|
+
# Generate invalid boolean values
|
|
477
|
+
#
|
|
478
|
+
# @param rng [Generator::RNG] random number generator
|
|
479
|
+
# @param context [Generator::Context] generation context
|
|
480
|
+
# @param args [Hash] type arguments
|
|
481
|
+
# @return [Object] invalid value
|
|
482
|
+
#
|
|
483
|
+
def generate_invalid(rng, context, args)
|
|
484
|
+
rng.sample([nil, "true", "false", 0, 1, "", []])
|
|
485
|
+
end
|
|
486
|
+
|
|
487
|
+
private
|
|
488
|
+
|
|
489
|
+
# Extract probability from argument
|
|
490
|
+
#
|
|
491
|
+
# @param prob [Integer, String, nil] probability value
|
|
492
|
+
# @return [Integer, nil] extracted probability (0-100)
|
|
493
|
+
#
|
|
494
|
+
def extract_probability(prob)
|
|
495
|
+
return nil if prob.nil?
|
|
496
|
+
|
|
497
|
+
case prob
|
|
498
|
+
when Integer
|
|
499
|
+
prob.clamp(0, 100)
|
|
500
|
+
when String
|
|
501
|
+
prob.to_i.clamp(0, 100) # fallback
|
|
502
|
+
else
|
|
503
|
+
nil
|
|
504
|
+
end
|
|
505
|
+
end
|
|
506
|
+
end
|
|
507
|
+
|
|
508
|
+
# Const type generator - returns a literal constant value
|
|
509
|
+
#
|
|
510
|
+
# Always returns the exact value provided, regardless of generation mode.
|
|
511
|
+
# Useful for fields that should always have the same value, like
|
|
512
|
+
# discriminator fields or fixed identifiers.
|
|
513
|
+
#
|
|
514
|
+
# @example Fixed string value
|
|
515
|
+
# type: const("message_create")
|
|
516
|
+
#
|
|
517
|
+
# @example Fixed number value
|
|
518
|
+
# version: const(1)
|
|
519
|
+
#
|
|
520
|
+
# @example As discriminator
|
|
521
|
+
# eventType: const("new_twitter_direct_message")
|
|
522
|
+
#
|
|
523
|
+
class Const < Base
|
|
524
|
+
# Generate the constant value
|
|
525
|
+
#
|
|
526
|
+
# @param rng [Generator::RNG] random number generator (not used)
|
|
527
|
+
# @param context [Generator::Context] generation context (not used)
|
|
528
|
+
# @param args [Hash] type arguments
|
|
529
|
+
# @option args [Object] :value the constant value to return
|
|
530
|
+
# @return [Object] the constant value
|
|
531
|
+
#
|
|
532
|
+
def generate(rng, context, args, mode)
|
|
533
|
+
extract_value(args)
|
|
534
|
+
end
|
|
535
|
+
|
|
536
|
+
# Generate random constant (same as generate)
|
|
537
|
+
#
|
|
538
|
+
# @param rng [Generator::RNG] random number generator
|
|
539
|
+
# @param context [Generator::Context] generation context
|
|
540
|
+
# @param args [Hash] type arguments
|
|
541
|
+
# @return [Object] the constant value
|
|
542
|
+
#
|
|
543
|
+
def generate_random(rng, context, args)
|
|
544
|
+
extract_value(args)
|
|
545
|
+
end
|
|
546
|
+
|
|
547
|
+
# Generate edge case constant (same as generate)
|
|
548
|
+
#
|
|
549
|
+
# @param rng [Generator::RNG] random number generator
|
|
550
|
+
# @param context [Generator::Context] generation context
|
|
551
|
+
# @param args [Hash] type arguments
|
|
552
|
+
# @return [Object] the constant value
|
|
553
|
+
#
|
|
554
|
+
def generate_edge(rng, context, args)
|
|
555
|
+
extract_value(args)
|
|
556
|
+
end
|
|
557
|
+
|
|
558
|
+
# Generate invalid constant (same as generate)
|
|
559
|
+
#
|
|
560
|
+
# For const types, even "invalid" mode returns the constant value,
|
|
561
|
+
# since the purpose is to always return the same value.
|
|
562
|
+
#
|
|
563
|
+
# @param rng [Generator::RNG] random number generator
|
|
564
|
+
# @param context [Generator::Context] generation context
|
|
565
|
+
# @param args [Hash] type arguments
|
|
566
|
+
# @return [Object] the constant value
|
|
567
|
+
#
|
|
568
|
+
def generate_invalid(rng, context, args)
|
|
569
|
+
extract_value(args)
|
|
570
|
+
end
|
|
571
|
+
|
|
572
|
+
private
|
|
573
|
+
|
|
574
|
+
# Extract the constant value from arguments
|
|
575
|
+
#
|
|
576
|
+
# @param args [Hash] type arguments
|
|
577
|
+
# @return [Object] the extracted value
|
|
578
|
+
#
|
|
579
|
+
def extract_value(args)
|
|
580
|
+
# Support multiple argument formats
|
|
581
|
+
args[:value] || args["value"] || args[:path] || args["path"] || args.values.first
|
|
582
|
+
end
|
|
583
|
+
end
|
|
584
|
+
end
|
|
585
|
+
end
|
|
586
|
+
end
|