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,466 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Synthra
|
|
4
|
+
module Types
|
|
5
|
+
module Core
|
|
6
|
+
# Copy type generator - copies value from another field in current or parent schema
|
|
7
|
+
#
|
|
8
|
+
# Allows referencing fields that were already generated in the same or parent schema.
|
|
9
|
+
# Useful for linking IDs across nested objects.
|
|
10
|
+
#
|
|
11
|
+
# @example Copy a nested field value
|
|
12
|
+
# sender_id: copy("users.sender.id")
|
|
13
|
+
#
|
|
14
|
+
class Copy < Base
|
|
15
|
+
# Generate by copying from another field
|
|
16
|
+
#
|
|
17
|
+
# @param rng [Generator::RNG] random number generator (not used)
|
|
18
|
+
# @param context [Generator::Context] generation context with field values
|
|
19
|
+
# @param args [Hash] type arguments
|
|
20
|
+
# @option args [String] :path dot-separated path to field (e.g., "users.sender.id")
|
|
21
|
+
# @return [Object, nil] copied value
|
|
22
|
+
#
|
|
23
|
+
def generate(rng, context, args, mode)
|
|
24
|
+
path = args[:path] || args["path"] || args.values.first
|
|
25
|
+
return nil unless path && context
|
|
26
|
+
|
|
27
|
+
# Try current context first
|
|
28
|
+
result = context.get(path.to_s)
|
|
29
|
+
return result if result
|
|
30
|
+
|
|
31
|
+
# Walk up parent contexts to find the value
|
|
32
|
+
parent = context.parent_context
|
|
33
|
+
while parent && result.nil?
|
|
34
|
+
result = parent.get(path.to_s)
|
|
35
|
+
parent = parent.parent_context
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
result
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def generate_random(rng, context, args)
|
|
42
|
+
generate(rng, context, args, :random)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def generate_edge(rng, context, args)
|
|
46
|
+
generate(rng, context, args, :edge)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def generate_invalid(rng, context, args)
|
|
50
|
+
nil
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Reference type generator (Ref(Schema.field))
|
|
55
|
+
#
|
|
56
|
+
# Generates values by resolving references to fields in other schemas.
|
|
57
|
+
# Uses the Resolver to look up previously generated values or generate
|
|
58
|
+
# new ones on-demand.
|
|
59
|
+
#
|
|
60
|
+
# @example Generate reference value
|
|
61
|
+
# ref_type = Reference.new
|
|
62
|
+
# ref_type.generate(rng, context, { ref: RefNode.new("User", "id") }, :random)
|
|
63
|
+
#
|
|
64
|
+
class Reference < Base
|
|
65
|
+
# Generate a reference value
|
|
66
|
+
#
|
|
67
|
+
# @param rng [Generator::RNG] random number generator
|
|
68
|
+
# @param context [Generator::Context] generation context
|
|
69
|
+
# @param args [Hash] type arguments
|
|
70
|
+
# @option args [ReferenceNode, Hash] :ref reference node or hash with :schema and :field
|
|
71
|
+
# @return [Object, nil] resolved reference value
|
|
72
|
+
#
|
|
73
|
+
def generate(rng, context, args, mode)
|
|
74
|
+
ref = args[:ref] || args["ref"]
|
|
75
|
+
return nil unless ref
|
|
76
|
+
|
|
77
|
+
# Extract schema name and field path
|
|
78
|
+
if ref.respond_to?(:schema_name) && ref.respond_to?(:field_path)
|
|
79
|
+
schema_name = ref.schema_name
|
|
80
|
+
field_path = ref.field_path
|
|
81
|
+
elsif ref.is_a?(Hash)
|
|
82
|
+
schema_name = ref[:schema] || ref["schema"]
|
|
83
|
+
field_path = ref[:field] || ref["field"]
|
|
84
|
+
else
|
|
85
|
+
return nil
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
return nil unless schema_name && field_path
|
|
89
|
+
|
|
90
|
+
# Get resolver from context
|
|
91
|
+
resolver = context&.resolver
|
|
92
|
+
return nil unless resolver
|
|
93
|
+
|
|
94
|
+
# :nocov: context is non-nil here (the `return nil unless resolver` guard above
|
|
95
|
+
# exits when context — and thus resolver — is nil), so this `&.` else is unreachable.
|
|
96
|
+
depth = context&.depth || 0
|
|
97
|
+
# :nocov:
|
|
98
|
+
# Derive a deterministic seed from the (seeded) generation RNG so the referenced record —
|
|
99
|
+
# and thus this FK value — is reproducible by seed, mirroring how Shared seeds its
|
|
100
|
+
# sub-generation. Without it the resolver generated the referenced schema with a random
|
|
101
|
+
# seed, so ref(User.id) varied across runs with the same parent seed.
|
|
102
|
+
resolver.resolve(schema_name, field_path, depth: depth, seed: rng.int(0, 2_147_483_647))
|
|
103
|
+
|
|
104
|
+
rescue StandardError
|
|
105
|
+
nil
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
# Generate a random reference value
|
|
110
|
+
#
|
|
111
|
+
# @param rng [Generator::RNG] random number generator
|
|
112
|
+
# @param context [Generator::Context] generation context
|
|
113
|
+
# @param args [Hash] type arguments
|
|
114
|
+
# @return [Object, nil] resolved reference value
|
|
115
|
+
#
|
|
116
|
+
def generate_random(rng, context, args)
|
|
117
|
+
generate(rng, context, args, :random)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Generate edge case reference values
|
|
121
|
+
#
|
|
122
|
+
# @param rng [Generator::RNG] random number generator
|
|
123
|
+
# @param context [Generator::Context] generation context
|
|
124
|
+
# @param args [Hash] type arguments
|
|
125
|
+
# @return [Object, nil] edge case value
|
|
126
|
+
#
|
|
127
|
+
def generate_edge(rng, context, args)
|
|
128
|
+
nil
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# Generate invalid reference values
|
|
132
|
+
#
|
|
133
|
+
# @param rng [Generator::RNG] random number generator
|
|
134
|
+
# @param context [Generator::Context] generation context
|
|
135
|
+
# @param args [Hash] type arguments
|
|
136
|
+
# @return [Object] invalid reference value
|
|
137
|
+
#
|
|
138
|
+
def generate_invalid(rng, context, args)
|
|
139
|
+
rng.sample([
|
|
140
|
+
nil,
|
|
141
|
+
"invalid-ref",
|
|
142
|
+
123,
|
|
143
|
+
[]
|
|
144
|
+
])
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# Shared type generator - generates once and reuses across batch
|
|
149
|
+
#
|
|
150
|
+
# When used with generate_many, generates the wrapped type once per batch
|
|
151
|
+
# and reuses the same value for all records. Useful for scenarios like
|
|
152
|
+
# "all DMs go to the same recipient" or "all orders from same customer".
|
|
153
|
+
#
|
|
154
|
+
# @example All records share the same recipient
|
|
155
|
+
# users: map_by_field("id", sender: TwitterUser, recipient: shared(TwitterUser))
|
|
156
|
+
#
|
|
157
|
+
class Shared < Base
|
|
158
|
+
# Track whether we've already warned about misuse (to avoid spam)
|
|
159
|
+
@warned_about_misuse = false
|
|
160
|
+
|
|
161
|
+
class << self
|
|
162
|
+
attr_accessor :warned_about_misuse
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# Generate a shared value (cached per batch)
|
|
166
|
+
#
|
|
167
|
+
# @param rng [Generator::RNG] random number generator
|
|
168
|
+
# @param context [Generator::Context] generation context with shared_context
|
|
169
|
+
# @param args [Hash] type arguments
|
|
170
|
+
# @option args [String] :type_name the wrapped type to generate
|
|
171
|
+
# @option args [Hash] :type_args arguments for the wrapped type
|
|
172
|
+
# @return [Object] generated or cached value
|
|
173
|
+
#
|
|
174
|
+
def generate(rng, context, args, mode)
|
|
175
|
+
type_name = args[:type_name] || args["type_name"]
|
|
176
|
+
type_args = args[:type_args] || args["type_args"] || {}
|
|
177
|
+
shared_key = args[:shared_key] || args["shared_key"] || "#{type_name}_#{type_args.hash}"
|
|
178
|
+
|
|
179
|
+
# Get or create shared_context from context
|
|
180
|
+
shared_context = context&.shared_context
|
|
181
|
+
|
|
182
|
+
# Warn if shared_context was not initialized by generate_many
|
|
183
|
+
if shared_context.nil? && should_warn_about_misuse?
|
|
184
|
+
emit_misuse_warning(type_name)
|
|
185
|
+
shared_context = {}
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
shared_context ||= {}
|
|
189
|
+
|
|
190
|
+
# Return cached value if exists
|
|
191
|
+
return shared_context[shared_key] if shared_context.key?(shared_key)
|
|
192
|
+
|
|
193
|
+
# Generate new value
|
|
194
|
+
registry = context&.registry
|
|
195
|
+
|
|
196
|
+
value = if registry&.schema?(type_name)
|
|
197
|
+
# It's a schema reference
|
|
198
|
+
schema = registry.schema(type_name)
|
|
199
|
+
new_seed = rng.int(0, 2_147_483_647)
|
|
200
|
+
schema.generate(seed: new_seed, registry: registry, mode: mode, parent_context: context)
|
|
201
|
+
else
|
|
202
|
+
# It's a regular type
|
|
203
|
+
type_gen = Types::Registry.lookup(type_name)
|
|
204
|
+
type_gen.generate(rng, context, type_args, mode)
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
# Cache the value
|
|
208
|
+
shared_context[shared_key] = value
|
|
209
|
+
context.shared_context = shared_context if context
|
|
210
|
+
|
|
211
|
+
value
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
def generate_random(rng, context, args)
|
|
215
|
+
generate(rng, context, args, :random)
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
def generate_edge(rng, context, args)
|
|
219
|
+
generate(rng, context, args, :edge)
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
def generate_invalid(rng, context, args)
|
|
223
|
+
nil
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
private
|
|
227
|
+
|
|
228
|
+
# Check if we should warn about shared() misuse
|
|
229
|
+
#
|
|
230
|
+
# @return [Boolean] true if warning should be emitted
|
|
231
|
+
#
|
|
232
|
+
def should_warn_about_misuse?
|
|
233
|
+
return false if self.class.warned_about_misuse
|
|
234
|
+
|
|
235
|
+
Synthra.configuration.warn_on_shared_misuse
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
# Emit warning about shared() being used outside generate_many()
|
|
239
|
+
#
|
|
240
|
+
# @param type_name [String] the type name being shared
|
|
241
|
+
# @return [void]
|
|
242
|
+
#
|
|
243
|
+
def emit_misuse_warning(type_name)
|
|
244
|
+
self.class.warned_about_misuse = true
|
|
245
|
+
|
|
246
|
+
message = "[Synthra] Warning: shared(#{type_name}) used outside generate_many() - " \
|
|
247
|
+
"value will not be shared across calls. " \
|
|
248
|
+
"Use generate_many() for batch generation with shared values."
|
|
249
|
+
|
|
250
|
+
logger = Synthra.configuration.logger
|
|
251
|
+
if logger
|
|
252
|
+
logger.warn(message)
|
|
253
|
+
else
|
|
254
|
+
warn message
|
|
255
|
+
end
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
# Schema reference type generator (schema(:Name))
|
|
260
|
+
#
|
|
261
|
+
# Generates values by instantiating another schema. This is the explicit
|
|
262
|
+
# way to reference schemas in the new Simulyra DSL syntax.
|
|
263
|
+
#
|
|
264
|
+
# @example Generate schema reference
|
|
265
|
+
# schema_ref = SchemaRef.new
|
|
266
|
+
# schema_ref.generate(rng, context, { schema_name: "User", array: false }, :random)
|
|
267
|
+
#
|
|
268
|
+
# @example Generate array of schemas
|
|
269
|
+
# schema_ref.generate(rng, context, { schema_name: "User", array: true, array_range: 1..10 }, :random)
|
|
270
|
+
#
|
|
271
|
+
class SchemaRef < Base
|
|
272
|
+
# Generate a schema reference value
|
|
273
|
+
#
|
|
274
|
+
# @param rng [Generator::RNG] random number generator
|
|
275
|
+
# @param context [Generator::Context] generation context
|
|
276
|
+
# @param args [Hash] type arguments
|
|
277
|
+
# @option args [String] :schema_name name of the schema to generate
|
|
278
|
+
# @option args [Boolean] :array whether to generate an array (default: false)
|
|
279
|
+
# @option args [Range, nil] :array_range size range for arrays
|
|
280
|
+
# @return [Object, Array<Object>, nil] generated schema instance(s)
|
|
281
|
+
#
|
|
282
|
+
def generate(rng, context, args, mode)
|
|
283
|
+
schema_name = args[:schema_name] || args["schema_name"]
|
|
284
|
+
return nil unless schema_name
|
|
285
|
+
|
|
286
|
+
registry = context&.registry
|
|
287
|
+
return nil unless registry
|
|
288
|
+
|
|
289
|
+
# Check if schema exists
|
|
290
|
+
unless registry.schema?(schema_name)
|
|
291
|
+
return nil
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
schema = registry.schema(schema_name)
|
|
295
|
+
is_array = args[:array] || args["array"] || false
|
|
296
|
+
array_range = args[:array_range] || args["array_range"]
|
|
297
|
+
|
|
298
|
+
if is_array
|
|
299
|
+
generate_array(rng, context, schema, registry, mode, array_range)
|
|
300
|
+
else
|
|
301
|
+
generate_single(rng, context, schema, registry, mode)
|
|
302
|
+
end
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
def generate_random(rng, context, args)
|
|
306
|
+
generate(rng, context, args, :random)
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
def generate_edge(rng, context, args)
|
|
310
|
+
generate(rng, context, args, :edge)
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
def generate_invalid(rng, context, args)
|
|
314
|
+
rng.sample([
|
|
315
|
+
nil,
|
|
316
|
+
"invalid-schema-ref",
|
|
317
|
+
123,
|
|
318
|
+
[]
|
|
319
|
+
])
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
private
|
|
323
|
+
|
|
324
|
+
# Generate a single schema instance
|
|
325
|
+
#
|
|
326
|
+
# @param rng [Generator::RNG] random number generator
|
|
327
|
+
# @param context [Generator::Context] generation context
|
|
328
|
+
# @param schema [Schema] the schema to generate
|
|
329
|
+
# @param registry [Registry] schema registry
|
|
330
|
+
# @param mode [Symbol] generation mode
|
|
331
|
+
# @return [Object] generated schema instance
|
|
332
|
+
#
|
|
333
|
+
def generate_single(rng, context, schema, registry, mode)
|
|
334
|
+
new_seed = rng.int(0, 2_147_483_647)
|
|
335
|
+
# :nocov: every caller reaches here only past `return nil unless registry`
|
|
336
|
+
# (registry derives from context), so context is non-nil — `&.` else unreachable.
|
|
337
|
+
depth = (context&.depth || 0) + 1
|
|
338
|
+
# :nocov:
|
|
339
|
+
|
|
340
|
+
# Prevent infinite recursion
|
|
341
|
+
# :nocov: Synthra.configuration and #limits are memoized (`||= …`) and never nil,
|
|
342
|
+
# so the `&.` nil-receiver else-arms on this chain are unreachable.
|
|
343
|
+
max_depth = Synthra.configuration&.limits&.max_recursion || 10
|
|
344
|
+
# :nocov:
|
|
345
|
+
return nil if depth > max_depth
|
|
346
|
+
|
|
347
|
+
schema.generate(
|
|
348
|
+
seed: new_seed,
|
|
349
|
+
registry: registry,
|
|
350
|
+
mode: mode,
|
|
351
|
+
parent_context: context,
|
|
352
|
+
depth: depth
|
|
353
|
+
)
|
|
354
|
+
end
|
|
355
|
+
|
|
356
|
+
# Generate an array of schema instances
|
|
357
|
+
#
|
|
358
|
+
# @param rng [Generator::RNG] random number generator
|
|
359
|
+
# @param context [Generator::Context] generation context
|
|
360
|
+
# @param schema [Schema] the schema to generate
|
|
361
|
+
# @param registry [Registry] schema registry
|
|
362
|
+
# @param mode [Symbol] generation mode
|
|
363
|
+
# @param size_range [Range, nil] size range for the array
|
|
364
|
+
# @return [Array<Object>] array of generated schema instances
|
|
365
|
+
#
|
|
366
|
+
def generate_array(rng, context, schema, registry, mode, size_range)
|
|
367
|
+
# Determine array size
|
|
368
|
+
size = if size_range.is_a?(Range)
|
|
369
|
+
rng.int(size_range.min, size_range.max)
|
|
370
|
+
elsif size_range.is_a?(Integer)
|
|
371
|
+
size_range
|
|
372
|
+
elsif size_range.is_a?(Hash)
|
|
373
|
+
min = size_range[:min] || 0
|
|
374
|
+
max = size_range[:max] || 10
|
|
375
|
+
rng.int(min, max)
|
|
376
|
+
else
|
|
377
|
+
rng.int(1, 10) # Default size
|
|
378
|
+
end
|
|
379
|
+
|
|
380
|
+
# Enforce array size limits
|
|
381
|
+
# :nocov: Synthra.configuration and #limits are memoized (`||= …`) and never nil,
|
|
382
|
+
# so the `&.` nil-receiver else-arms on this chain are unreachable.
|
|
383
|
+
max_size = Synthra.configuration&.limits&.max_array_size || 1000
|
|
384
|
+
# :nocov:
|
|
385
|
+
size = [size, max_size].min
|
|
386
|
+
|
|
387
|
+
Array.new(size) do
|
|
388
|
+
generate_single(rng, context, schema, registry, mode)
|
|
389
|
+
end.compact
|
|
390
|
+
end
|
|
391
|
+
end
|
|
392
|
+
|
|
393
|
+
# Custom type generator (calls registered functions)
|
|
394
|
+
#
|
|
395
|
+
# Generates values by invoking a registered function with the current
|
|
396
|
+
# generation context. Functions can access previously generated fields
|
|
397
|
+
# to compute derived values.
|
|
398
|
+
#
|
|
399
|
+
# @example Generate using custom function
|
|
400
|
+
# custom_type = Custom.new
|
|
401
|
+
# custom_type.generate(rng, context, { function: :full_name }, :random)
|
|
402
|
+
#
|
|
403
|
+
class Custom < Base
|
|
404
|
+
# Generate a value using a custom function
|
|
405
|
+
#
|
|
406
|
+
# @param rng [Generator::RNG] random number generator (not used)
|
|
407
|
+
# @param context [Generator::Context] generation context
|
|
408
|
+
# @param args [Hash] type arguments
|
|
409
|
+
# @option args [Symbol, String] :function function name to call
|
|
410
|
+
# @return [Object] function result
|
|
411
|
+
# @raise [MissingFunctionError] if function not found
|
|
412
|
+
#
|
|
413
|
+
def generate(rng, context, args, mode)
|
|
414
|
+
function_name = args[:function] || args["function"] || args.values.first
|
|
415
|
+
raise Errors::MissingFunctionError.new(function_name) unless function_name
|
|
416
|
+
|
|
417
|
+
Functions::Registry.call(function_name.to_sym, context)
|
|
418
|
+
end
|
|
419
|
+
|
|
420
|
+
# Generate a random value using a custom function
|
|
421
|
+
#
|
|
422
|
+
# @param rng [Generator::RNG] random number generator (not used)
|
|
423
|
+
# @param context [Generator::Context] generation context
|
|
424
|
+
# @param args [Hash] type arguments
|
|
425
|
+
# @return [Object] function result
|
|
426
|
+
# @raise [MissingFunctionError] if function not found
|
|
427
|
+
#
|
|
428
|
+
def generate_random(rng, context, args)
|
|
429
|
+
generate(rng, context, args, :random)
|
|
430
|
+
end
|
|
431
|
+
|
|
432
|
+
# Generate edge case custom values
|
|
433
|
+
#
|
|
434
|
+
# @param rng [Generator::RNG] random number generator
|
|
435
|
+
# @param context [Generator::Context] generation context
|
|
436
|
+
# @param args [Hash] type arguments
|
|
437
|
+
# @return [Object] edge case value
|
|
438
|
+
#
|
|
439
|
+
def generate_edge(rng, context, args)
|
|
440
|
+
generate(rng, context, args, :edge)
|
|
441
|
+
|
|
442
|
+
rescue StandardError
|
|
443
|
+
nil
|
|
444
|
+
end
|
|
445
|
+
|
|
446
|
+
|
|
447
|
+
# Generate invalid custom values
|
|
448
|
+
#
|
|
449
|
+
# @param rng [Generator::RNG] random number generator
|
|
450
|
+
# @param context [Generator::Context] generation context
|
|
451
|
+
# @param args [Hash] type arguments
|
|
452
|
+
# @return [Object] invalid value
|
|
453
|
+
#
|
|
454
|
+
def generate_invalid(rng, context, args)
|
|
455
|
+
rng.sample([
|
|
456
|
+
nil,
|
|
457
|
+
"invalid-custom",
|
|
458
|
+
123,
|
|
459
|
+
[]
|
|
460
|
+
])
|
|
461
|
+
end
|
|
462
|
+
end
|
|
463
|
+
end
|
|
464
|
+
end
|
|
465
|
+
end
|
|
466
|
+
|