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,424 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Synthra
|
|
4
|
+
module Types
|
|
5
|
+
module FinanceBanking
|
|
6
|
+
# Money type
|
|
7
|
+
class Money < Base
|
|
8
|
+
# Generate a random money value
|
|
9
|
+
#
|
|
10
|
+
# Creates a hash with amount (2 decimal places) and currency code.
|
|
11
|
+
# Uses Faker::Currency.code if no currency specified.
|
|
12
|
+
#
|
|
13
|
+
# @param rng [Generator::RNG] random number generator
|
|
14
|
+
# @param args [Hash] type arguments
|
|
15
|
+
# @option args [Range, Hash] :range amount range (default: 0..10000)
|
|
16
|
+
# @option args [String] :currency currency code (default: random from Faker)
|
|
17
|
+
# @return [Hash] { "amount" => Float, "currency" => String }
|
|
18
|
+
#
|
|
19
|
+
# @example
|
|
20
|
+
# generate_random(rng, { range: 0..100, currency: "EUR" })
|
|
21
|
+
# # => { "amount" => 45.67, "currency" => "EUR" }
|
|
22
|
+
#
|
|
23
|
+
def generate_random(rng, context, args)
|
|
24
|
+
range = args[:range] || (0..10_000)
|
|
25
|
+
min = range.is_a?(Hash) ? (range[:min] || 0) : range.min
|
|
26
|
+
max = range.is_a?(Hash) ? (range[:max] || 10_000) : range.max
|
|
27
|
+
|
|
28
|
+
# Use provided currency or generate one using Faker
|
|
29
|
+
currency = args[:currency] || generate_currency(context)
|
|
30
|
+
|
|
31
|
+
# Generate amount and round to 2 decimal places (cents)
|
|
32
|
+
amount = (rng.rand * (max.to_f - min.to_f) + min.to_f).round(2)
|
|
33
|
+
|
|
34
|
+
{
|
|
35
|
+
"amount" => amount,
|
|
36
|
+
"currency" => currency
|
|
37
|
+
}
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def generate_edge(rng, context, args)
|
|
41
|
+
{
|
|
42
|
+
"amount" => 0.0,
|
|
43
|
+
"currency" => "USD"
|
|
44
|
+
}
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def generate_invalid(rng, context, args)
|
|
48
|
+
rng.sample([nil, "not money", [], {}, 123])
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
private
|
|
52
|
+
|
|
53
|
+
def generate_currency(context)
|
|
54
|
+
adapter = faker_adapter(context)
|
|
55
|
+
if adapter
|
|
56
|
+
adapter.currency_code
|
|
57
|
+
|
|
58
|
+
else
|
|
59
|
+
Faker::Currency.code
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
rescue StandardError
|
|
63
|
+
"USD"
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Currency type
|
|
69
|
+
class Currency < Base
|
|
70
|
+
# Fallback currency codes if Faker is unavailable
|
|
71
|
+
#
|
|
72
|
+
# ISO 4217 currency codes used when Faker cannot generate codes.
|
|
73
|
+
#
|
|
74
|
+
# @return [Array<String>] list of 3-letter currency codes
|
|
75
|
+
#
|
|
76
|
+
FALLBACK_CODES = %w[
|
|
77
|
+
USD EUR GBP JPY CNY INR BRL CAD AUD CHF
|
|
78
|
+
SEK NOK DKK NZD SGD HKD KRW MXN ZAR RUB
|
|
79
|
+
TRY PLN CZK HUF RON BGN HRK ILS AED SAR
|
|
80
|
+
THB MYR IDR PHP VND EGP NGN KES MAD PKR
|
|
81
|
+
].freeze
|
|
82
|
+
|
|
83
|
+
# Generate a random currency code
|
|
84
|
+
#
|
|
85
|
+
# Uses Faker::Currency.code for realistic ISO 4217 codes.
|
|
86
|
+
#
|
|
87
|
+
# @param rng [Generator::RNG] random number generator
|
|
88
|
+
# @param args [Hash] type arguments (not used)
|
|
89
|
+
# @return [String] three-letter currency code
|
|
90
|
+
#
|
|
91
|
+
# @example
|
|
92
|
+
# generate_random(rng, {})
|
|
93
|
+
# # => "EUR"
|
|
94
|
+
#
|
|
95
|
+
def generate_random(rng, context, args)
|
|
96
|
+
adapter = faker_adapter(context)
|
|
97
|
+
if adapter
|
|
98
|
+
adapter.currency_code
|
|
99
|
+
|
|
100
|
+
else
|
|
101
|
+
Faker::Currency.code
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
rescue StandardError
|
|
105
|
+
rng.sample(FALLBACK_CODES)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def generate_edge(rng, context, args)
|
|
110
|
+
["USD", "EUR", "GBP"].sample(random: rng.instance_variable_get(:@random))
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def generate_invalid(rng, context, args)
|
|
114
|
+
[nil, "XX", "123", [], {}].sample(random: rng.instance_variable_get(:@random))
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# CreditCard type
|
|
119
|
+
class CreditCard < Base
|
|
120
|
+
# Credit card type definitions
|
|
121
|
+
#
|
|
122
|
+
# Maps card type symbols to their specifications including
|
|
123
|
+
# valid prefixes and card number length.
|
|
124
|
+
#
|
|
125
|
+
# @return [Hash<Symbol, Hash>] card type to specification mapping
|
|
126
|
+
# @example
|
|
127
|
+
# CARD_TYPES[:visa] # => { prefix: [4], length: 16 }
|
|
128
|
+
# CARD_TYPES[:mastercard] # => { prefix: [51, 52, 53, 54, 55], length: 16 }
|
|
129
|
+
#
|
|
130
|
+
CARD_TYPES = {
|
|
131
|
+
visa: { prefix: [4], length: 16 },
|
|
132
|
+
mastercard: { prefix: [51, 52, 53, 54, 55], length: 16 },
|
|
133
|
+
amex: { prefix: [34, 37], length: 15 },
|
|
134
|
+
discover: { prefix: [6011, 65], length: 16 },
|
|
135
|
+
diners: { prefix: [30, 36, 38], length: 14 },
|
|
136
|
+
jcb: { prefix: [35], length: 16 }
|
|
137
|
+
}.freeze
|
|
138
|
+
|
|
139
|
+
# Generate random credit card number
|
|
140
|
+
#
|
|
141
|
+
# @param rng [Generator::RNG] random number generator
|
|
142
|
+
# @param context [Generator::Context] generation context
|
|
143
|
+
# @param args [Hash] type arguments
|
|
144
|
+
# @option args [Symbol, String] :type card type (:visa, :mastercard, :amex, etc.)
|
|
145
|
+
# @return [String] generated credit card number
|
|
146
|
+
#
|
|
147
|
+
def generate_random(rng, context, args)
|
|
148
|
+
type = (args[:type] || rng.sample(CARD_TYPES.keys)).to_sym
|
|
149
|
+
card_spec = CARD_TYPES[type] || CARD_TYPES[:visa]
|
|
150
|
+
|
|
151
|
+
# :nocov:
|
|
152
|
+
# The non-Array arm is unreachable: every CARD_TYPES entry (and the
|
|
153
|
+
# :visa default) declares an Array prefix, so card_spec[:prefix] is
|
|
154
|
+
# always an Array here.
|
|
155
|
+
prefix = card_spec[:prefix].is_a?(Array) ? rng.sample(card_spec[:prefix]) : card_spec[:prefix]
|
|
156
|
+
# :nocov:
|
|
157
|
+
length = card_spec[:length]
|
|
158
|
+
|
|
159
|
+
# Generate base number with prefix
|
|
160
|
+
base = prefix.to_s
|
|
161
|
+
remaining = length - base.length - 1 # -1 for check digit
|
|
162
|
+
|
|
163
|
+
# Fill with random digits
|
|
164
|
+
remaining.times { base += rng.int(0, 9).to_s }
|
|
165
|
+
|
|
166
|
+
# Calculate and append Luhn check digit
|
|
167
|
+
check_digit = calculate_luhn_check_digit(base)
|
|
168
|
+
"#{base}#{check_digit}"
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def generate_edge(rng, context, args)
|
|
172
|
+
["4111111111111111", "4000000000000002"].sample(random: rng.instance_variable_get(:@random))
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def generate_invalid(rng, context, args)
|
|
176
|
+
rng.sample([nil, "1234", "invalid", [], {}])
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
private
|
|
180
|
+
|
|
181
|
+
def calculate_luhn_check_digit(number)
|
|
182
|
+
sum = 0
|
|
183
|
+
number.reverse.chars.each_with_index do |digit, index|
|
|
184
|
+
n = digit.to_i
|
|
185
|
+
n *= 2 if index.odd?
|
|
186
|
+
n -= 9 if n > 9
|
|
187
|
+
sum += n
|
|
188
|
+
end
|
|
189
|
+
(10 - (sum % 10)) % 10
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
# BankName type
|
|
194
|
+
class BankName < Base
|
|
195
|
+
# Generate random bank name
|
|
196
|
+
#
|
|
197
|
+
# @param rng [Generator::RNG] random number generator (not directly used)
|
|
198
|
+
# @param context [Generator::Context] generation context
|
|
199
|
+
# @param args [Hash] type arguments (not used)
|
|
200
|
+
# @return [String] generated bank name
|
|
201
|
+
#
|
|
202
|
+
def generate_random(rng, context, args)
|
|
203
|
+
adapter = faker_adapter(context)
|
|
204
|
+
if adapter
|
|
205
|
+
adapter.bank
|
|
206
|
+
|
|
207
|
+
else
|
|
208
|
+
Faker::Bank.name
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
rescue StandardError
|
|
212
|
+
rng.sample(["Chase Bank", "Bank of America", "Wells Fargo"])
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def generate_edge(rng, context, args)
|
|
217
|
+
["", "A"].sample(random: rng.instance_variable_get(:@random))
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
def generate_invalid(rng, context, args)
|
|
221
|
+
[nil, 123, [], {}].sample(random: rng.instance_variable_get(:@random))
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
# AccountNumber type
|
|
226
|
+
class AccountNumber < Base
|
|
227
|
+
# Generate random account number
|
|
228
|
+
#
|
|
229
|
+
# @param rng [Generator::RNG] random number generator
|
|
230
|
+
# @param context [Generator::Context] generation context
|
|
231
|
+
# @param args [Hash] type arguments
|
|
232
|
+
# @option args [Integer] :length account number length (default: 8..12)
|
|
233
|
+
# @option args [String] :format format string (e.g., "%010d" for zero-padding)
|
|
234
|
+
# @return [String] generated account number
|
|
235
|
+
#
|
|
236
|
+
def generate_random(rng, context, args)
|
|
237
|
+
length = args[:length] || rng.int(8, 12)
|
|
238
|
+
format_str = args[:format]
|
|
239
|
+
|
|
240
|
+
# Generate number
|
|
241
|
+
max_value = 10**length - 1
|
|
242
|
+
value = rng.int(0, max_value)
|
|
243
|
+
|
|
244
|
+
# Format if specified
|
|
245
|
+
if format_str
|
|
246
|
+
format(format_str, value)
|
|
247
|
+
else
|
|
248
|
+
value.to_s.rjust(length, "0")
|
|
249
|
+
end
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
def generate_edge(rng, context, args)
|
|
253
|
+
["00000000", "999999999999"].sample(random: rng.instance_variable_get(:@random))
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
def generate_invalid(rng, context, args)
|
|
257
|
+
[nil, "not a number", [], {}].sample(random: rng.instance_variable_get(:@random))
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
# Iban type
|
|
262
|
+
class Iban < Base
|
|
263
|
+
# IBAN format specifications by country
|
|
264
|
+
#
|
|
265
|
+
# Maps country codes to their IBAN format specifications including
|
|
266
|
+
# country code and total length.
|
|
267
|
+
#
|
|
268
|
+
# @return [Hash<Symbol, Hash>] country code to format mapping
|
|
269
|
+
# @example
|
|
270
|
+
# IBAN_FORMATS[:gb] # => { code: "GB", length: 22 }
|
|
271
|
+
# IBAN_FORMATS[:de] # => { code: "DE", length: 22 }
|
|
272
|
+
#
|
|
273
|
+
IBAN_FORMATS = {
|
|
274
|
+
gb: { code: "GB", length: 22 },
|
|
275
|
+
de: { code: "DE", length: 22 },
|
|
276
|
+
fr: { code: "FR", length: 27 },
|
|
277
|
+
it: { code: "IT", length: 27 },
|
|
278
|
+
es: { code: "ES", length: 24 },
|
|
279
|
+
nl: { code: "NL", length: 18 },
|
|
280
|
+
be: { code: "BE", length: 16 },
|
|
281
|
+
us: { code: "US", length: 22 } # Not standard, but sometimes used
|
|
282
|
+
}.freeze
|
|
283
|
+
|
|
284
|
+
# Generate random IBAN
|
|
285
|
+
#
|
|
286
|
+
# @param rng [Generator::RNG] random number generator
|
|
287
|
+
# @param context [Generator::Context] generation context
|
|
288
|
+
# @param args [Hash] type arguments
|
|
289
|
+
# @option args [Symbol, String] :country country code (:gb, :de, :fr, etc.)
|
|
290
|
+
# @return [String] generated IBAN
|
|
291
|
+
#
|
|
292
|
+
def generate_random(rng, context, args)
|
|
293
|
+
country = (args[:country] || :gb).to_sym
|
|
294
|
+
format = IBAN_FORMATS[country] || IBAN_FORMATS[:gb]
|
|
295
|
+
|
|
296
|
+
country_code = format[:code]
|
|
297
|
+
length = format[:length]
|
|
298
|
+
|
|
299
|
+
# Generate check digits (2 digits)
|
|
300
|
+
check_digits = format("%02d", rng.int(0, 99))
|
|
301
|
+
|
|
302
|
+
# Generate BBAN (Basic Bank Account Number) - alphanumeric
|
|
303
|
+
bban_length = length - 4 # -4 for country code and check digits
|
|
304
|
+
chars = ("A".."Z").to_a + ("0".."9").to_a
|
|
305
|
+
bban = bban_length.times.map { rng.sample(chars) }.join
|
|
306
|
+
|
|
307
|
+
"#{country_code}#{check_digits}#{bban}"
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
def generate_edge(rng, context, args)
|
|
311
|
+
["GB00000000000000000000", "DE00000000000000000000"].sample(random: rng.instance_variable_get(:@random))
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
def generate_invalid(rng, context, args)
|
|
315
|
+
[nil, "invalid", "123", [], {}].sample(random: rng.instance_variable_get(:@random))
|
|
316
|
+
end
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
# BankRoutingNumber type
|
|
320
|
+
class BankRoutingNumber < Base
|
|
321
|
+
def generate_random(rng, context, args)
|
|
322
|
+
format("%09d", rng.int(100000000, 999999999))
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
def generate_edge(rng, context, args)
|
|
326
|
+
["000000000", "999999999"].sample(random: rng.instance_variable_get(:@random))
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
def generate_invalid(rng, context, args)
|
|
330
|
+
[nil, "not a number", [], {}].sample(random: rng.instance_variable_get(:@random))
|
|
331
|
+
end
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
# BankSwiftBic type
|
|
335
|
+
class BankSwiftBic < Base
|
|
336
|
+
def generate_random(rng, context, args)
|
|
337
|
+
# Format: 4 letters (bank) + 2 letters (country) + 2 alphanumeric (location) + 3 alphanumeric (branch, optional)
|
|
338
|
+
bank = (0...4).map { rng.sample(('A'..'Z').to_a) }.join
|
|
339
|
+
country = rng.sample(["US", "GB", "FR", "DE", "JP", "CA", "AU"])
|
|
340
|
+
location = (0...2).map { rng.sample(('A'..'Z').to_a + ('0'..'9').to_a) }.join
|
|
341
|
+
branch = (0...3).map { rng.sample(('A'..'Z').to_a + ('0'..'9').to_a) }.join
|
|
342
|
+
"#{bank}#{country}#{location}#{branch}"
|
|
343
|
+
end
|
|
344
|
+
|
|
345
|
+
def generate_edge(rng, context, args)
|
|
346
|
+
["AAAAUS00", "ZZZZZZ99"].sample(random: rng.instance_variable_get(:@random))
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
def generate_invalid(rng, context, args)
|
|
350
|
+
[nil, "invalid", "123", [], {}].sample(random: rng.instance_variable_get(:@random))
|
|
351
|
+
end
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
# BankLei type
|
|
355
|
+
class BankLei < Base
|
|
356
|
+
def generate_random(rng, context, args)
|
|
357
|
+
# Format: 20 alphanumeric characters
|
|
358
|
+
chars = ('A'..'Z').to_a + ('0'..'9').to_a
|
|
359
|
+
20.times.map { rng.sample(chars) }.join
|
|
360
|
+
end
|
|
361
|
+
|
|
362
|
+
def generate_edge(rng, context, args)
|
|
363
|
+
["00000000000000000000", "ZZZZZZZZZZZZZZZZZZZZ"].sample(random: rng.instance_variable_get(:@random))
|
|
364
|
+
end
|
|
365
|
+
|
|
366
|
+
def generate_invalid(rng, context, args)
|
|
367
|
+
[nil, "invalid", "123", [], {}].sample(random: rng.instance_variable_get(:@random))
|
|
368
|
+
end
|
|
369
|
+
end
|
|
370
|
+
|
|
371
|
+
# BankRiadCode type
|
|
372
|
+
class BankRiadCode < Base
|
|
373
|
+
def generate_random(rng, context, args)
|
|
374
|
+
# RIAD codes are typically 9 digits
|
|
375
|
+
format("%09d", rng.int(100000000, 999999999))
|
|
376
|
+
end
|
|
377
|
+
|
|
378
|
+
def generate_edge(rng, context, args)
|
|
379
|
+
["000000000", "999999999"].sample(random: rng.instance_variable_get(:@random))
|
|
380
|
+
end
|
|
381
|
+
|
|
382
|
+
def generate_invalid(rng, context, args)
|
|
383
|
+
[nil, "not a number", [], {}].sample(random: rng.instance_variable_get(:@random))
|
|
384
|
+
end
|
|
385
|
+
end
|
|
386
|
+
|
|
387
|
+
# TransactionAmount type
|
|
388
|
+
class TransactionAmount < Base
|
|
389
|
+
# Generate random transaction amount
|
|
390
|
+
#
|
|
391
|
+
# @param rng [Generator::RNG] random number generator
|
|
392
|
+
# @param context [Generator::Context] generation context
|
|
393
|
+
# @param args [Hash] type arguments
|
|
394
|
+
# @option args [Range] :range amount range (e.g., 1.00..1000.00)
|
|
395
|
+
# @option args [Float] :min minimum amount (default: 0.01)
|
|
396
|
+
# @option args [Float] :max maximum amount (default: 10000.00)
|
|
397
|
+
# @option args [Integer] :precision decimal precision (default: 2)
|
|
398
|
+
# @return [Float] generated transaction amount
|
|
399
|
+
#
|
|
400
|
+
def generate_random(rng, context, args)
|
|
401
|
+
if args[:range].is_a?(Range)
|
|
402
|
+
min = args[:range].min
|
|
403
|
+
max = args[:range].max
|
|
404
|
+
else
|
|
405
|
+
min = args[:min] || 0.01
|
|
406
|
+
max = args[:max] || 10_000.00
|
|
407
|
+
end
|
|
408
|
+
|
|
409
|
+
precision = args[:precision] || 2
|
|
410
|
+
value = rng.rand * (max - min) + min
|
|
411
|
+
value.round(precision)
|
|
412
|
+
end
|
|
413
|
+
|
|
414
|
+
def generate_edge(rng, context, args)
|
|
415
|
+
[0.01, 0.0, 999999.99].sample(random: rng.instance_variable_get(:@random))
|
|
416
|
+
end
|
|
417
|
+
|
|
418
|
+
def generate_invalid(rng, context, args)
|
|
419
|
+
[nil, "not a number", -1.0, [], {}].sample(random: rng.instance_variable_get(:@random))
|
|
420
|
+
end
|
|
421
|
+
end
|
|
422
|
+
end
|
|
423
|
+
end
|
|
424
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Synthra
|
|
4
|
+
module Types
|
|
5
|
+
module FinanceBanking
|
|
6
|
+
# CreditCardType type
|
|
7
|
+
class CreditCardType < Base
|
|
8
|
+
CARD_TYPES = ["Visa", "Mastercard", "American Express", "Discover", "JCB", "Diners Club"].freeze
|
|
9
|
+
|
|
10
|
+
def generate_random(rng, context, args)
|
|
11
|
+
rng.sample(CARD_TYPES)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def generate_edge(rng, context, args)
|
|
15
|
+
["", "Visa", "Mastercard"].sample(random: rng.instance_variable_get(:@random))
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def generate_invalid(rng, context, args)
|
|
19
|
+
[nil, 123, [], {}].sample(random: rng.instance_variable_get(:@random))
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Synthra
|
|
4
|
+
module Types
|
|
5
|
+
module FinanceBanking
|
|
6
|
+
# Ssn type
|
|
7
|
+
class Ssn < Base
|
|
8
|
+
def generate_random(rng, context, args)
|
|
9
|
+
# Format: XXX-XX-XXXX
|
|
10
|
+
part1 = format("%03d", rng.int(100, 999))
|
|
11
|
+
part2 = format("%02d", rng.int(10, 99))
|
|
12
|
+
part3 = format("%04d", rng.int(1000, 9999))
|
|
13
|
+
"#{part1}-#{part2}-#{part3}"
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def generate_edge(rng, context, args)
|
|
17
|
+
["000-00-0000", "999-99-9999", "123-45-6789"].sample(random: rng.instance_variable_get(:@random))
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def generate_invalid(rng, context, args)
|
|
21
|
+
[nil, "not a ssn", [], {}, "123456789"].sample(random: rng.instance_variable_get(:@random))
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Ein type
|
|
26
|
+
class Ein < Base
|
|
27
|
+
def generate_random(rng, context, args)
|
|
28
|
+
# Format: XX-XXXXXXX
|
|
29
|
+
part1 = format("%02d", rng.int(10, 99))
|
|
30
|
+
part2 = format("%07d", rng.int(1000000, 9999999))
|
|
31
|
+
"#{part1}-#{part2}"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def generate_edge(rng, context, args)
|
|
35
|
+
["00-0000000", "99-9999999", "12-3456789"].sample(random: rng.instance_variable_get(:@random))
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def generate_invalid(rng, context, args)
|
|
39
|
+
[nil, "not an ein", [], {}, "123456789"].sample(random: rng.instance_variable_get(:@random))
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# NhsNumber type
|
|
44
|
+
class NhsNumber < Base
|
|
45
|
+
def generate_random(rng, context, args)
|
|
46
|
+
# NHS numbers are 10 digits
|
|
47
|
+
format("%010d", rng.int(1000000000, 9999999999))
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def generate_edge(rng, context, args)
|
|
51
|
+
["0000000000", "9999999999", "1234567890"].sample(random: rng.instance_variable_get(:@random))
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def generate_invalid(rng, context, args)
|
|
55
|
+
[nil, "not a nhs", [], {}, "123456789"].sample(random: rng.instance_variable_get(:@random))
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# MedicareBeneficiaryId type
|
|
60
|
+
class MedicareBeneficiaryId < Base
|
|
61
|
+
def generate_random(rng, context, args)
|
|
62
|
+
# Format: 1-2-3-4-5-6-7-8-9-10-11 (11 characters, alphanumeric)
|
|
63
|
+
chars = ('A'..'Z').to_a + ('0'..'9').to_a
|
|
64
|
+
11.times.map { rng.sample(chars) }.join
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def generate_edge(rng, context, args)
|
|
68
|
+
["A" * 11, "0" * 11, "1A2B3C4D5E6"].sample(random: rng.instance_variable_get(:@random))
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def generate_invalid(rng, context, args)
|
|
72
|
+
[nil, "too short", [], {}, "too long string here"].sample(random: rng.instance_variable_get(:@random))
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# DunsNumber type
|
|
77
|
+
class DunsNumber < Base
|
|
78
|
+
def generate_random(rng, context, args)
|
|
79
|
+
# DUNS numbers are 9 digits, often formatted with dashes
|
|
80
|
+
format("%09d", rng.int(100000000, 999999999))
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def generate_edge(rng, context, args)
|
|
84
|
+
["000000000", "999999999", "123456789"].sample(random: rng.instance_variable_get(:@random))
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def generate_invalid(rng, context, args)
|
|
88
|
+
[nil, "not a duns", [], {}, "12345678"].sample(random: rng.instance_variable_get(:@random))
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|