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,372 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# =============================================================================
|
|
4
|
+
|
|
5
|
+
# Synthra Generator Context
|
|
6
|
+
# =============================================================================
|
|
7
|
+
#
|
|
8
|
+
# The Context holds generated field values during record generation.
|
|
9
|
+
# It provides access to previously generated values for computed fields
|
|
10
|
+
# and conditional logic.
|
|
11
|
+
#
|
|
12
|
+
# @example Using context in custom functions
|
|
13
|
+
# Synthra.register_function(:full_name) do |ctx|
|
|
14
|
+
# "#{ctx['first_name']} #{ctx['last_name']}"
|
|
15
|
+
# end
|
|
16
|
+
#
|
|
17
|
+
# @example Conditional field using context
|
|
18
|
+
# # admin_note: text if is_admin
|
|
19
|
+
# # The context contains { "is_admin" => true } when evaluating
|
|
20
|
+
#
|
|
21
|
+
# =============================================================================
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
module Synthra
|
|
25
|
+
module Generator
|
|
26
|
+
|
|
27
|
+
# Generation context for accessing generated field values
|
|
28
|
+
#
|
|
29
|
+
# Context acts as a hash-like container for field values during
|
|
30
|
+
# generation. It supports multiple access patterns (hash, dot notation)
|
|
31
|
+
# and nested path lookups.
|
|
32
|
+
#
|
|
33
|
+
# @example Create and use context
|
|
34
|
+
# ctx = Context.new
|
|
35
|
+
# ctx["name"] = "John"
|
|
36
|
+
# ctx["name"] # => "John"
|
|
37
|
+
# ctx.name # => "John" (dot notation)
|
|
38
|
+
#
|
|
39
|
+
# @example Nested access
|
|
40
|
+
# ctx["address"] = { "city" => "NYC" }
|
|
41
|
+
# ctx.get("address.city") # => "NYC"
|
|
42
|
+
#
|
|
43
|
+
|
|
44
|
+
class Context
|
|
45
|
+
|
|
46
|
+
# @!attribute [r] schema_name
|
|
47
|
+
# Name of the schema being generated (for reference resolution)
|
|
48
|
+
# @return [String, nil] the schema name
|
|
49
|
+
|
|
50
|
+
attr_reader :schema_name
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
# @!attribute [rw] registry
|
|
54
|
+
# Registry for resolving cross-schema references
|
|
55
|
+
# @return [Registry, nil] the schema registry
|
|
56
|
+
|
|
57
|
+
attr_accessor :registry
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
# @!attribute [rw] depth
|
|
61
|
+
# Current recursion depth for nested schema generation
|
|
62
|
+
# @return [Integer] the depth level
|
|
63
|
+
|
|
64
|
+
attr_accessor :depth
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
# @!attribute [rw] parent_context
|
|
68
|
+
# Parent context for nested schemas (enables copy() from parent)
|
|
69
|
+
# @return [Context, nil] the parent context
|
|
70
|
+
|
|
71
|
+
attr_accessor :parent_context
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
# @!attribute [rw] shared_context
|
|
75
|
+
# Shared context for batch generation (values persist across records)
|
|
76
|
+
# @return [Hash] the shared context hash
|
|
77
|
+
|
|
78
|
+
attr_accessor :shared_context
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
# @!attribute [rw] resolver
|
|
82
|
+
# Resolver for cross-schema reference resolution
|
|
83
|
+
# @return [Resolver, nil] the resolver instance
|
|
84
|
+
|
|
85
|
+
attr_accessor :resolver
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
# @!attribute [rw] faker_adapter
|
|
89
|
+
# Faker adapter for deterministic fake data generation
|
|
90
|
+
# @return [FakerAdapter, nil] the faker adapter
|
|
91
|
+
|
|
92
|
+
attr_accessor :faker_adapter
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
# Create a new Context
|
|
96
|
+
#
|
|
97
|
+
# @param data [Hash] initial data to populate the context
|
|
98
|
+
# @param registry [Registry, nil] registry for resolving references
|
|
99
|
+
# @param depth [Integer] current recursion depth
|
|
100
|
+
# @param parent_context [Context, nil] parent context for nested schemas
|
|
101
|
+
# @param shared_context [Hash, nil] shared context for batch generation
|
|
102
|
+
# @param resolver [Resolver, nil] resolver for cross-schema references
|
|
103
|
+
# @param faker_adapter [FakerAdapter, nil] adapter for Faker
|
|
104
|
+
#
|
|
105
|
+
# @example Empty context
|
|
106
|
+
# ctx = Context.new
|
|
107
|
+
#
|
|
108
|
+
# @example Pre-populated context
|
|
109
|
+
# ctx = Context.new({ name: "John", age: 30 })
|
|
110
|
+
#
|
|
111
|
+
# @example With all options
|
|
112
|
+
# ctx = Context.new(
|
|
113
|
+
# { name: "John" },
|
|
114
|
+
# registry: my_registry,
|
|
115
|
+
# depth: 1,
|
|
116
|
+
# parent_context: parent_ctx,
|
|
117
|
+
# shared_context: {}
|
|
118
|
+
# )
|
|
119
|
+
#
|
|
120
|
+
|
|
121
|
+
def initialize(data = {}, registry: nil, depth: 0, parent_context: nil, shared_context: nil, resolver: nil, faker_adapter: nil)
|
|
122
|
+
@data = data.transform_keys(&:to_s)
|
|
123
|
+
@schema_name = nil
|
|
124
|
+
@registry = registry
|
|
125
|
+
@depth = depth
|
|
126
|
+
@parent_context = parent_context
|
|
127
|
+
@shared_context = shared_context || {}
|
|
128
|
+
@resolver = resolver
|
|
129
|
+
@faker_adapter = faker_adapter
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
# Get value by key (hash-style access)
|
|
134
|
+
#
|
|
135
|
+
# @param key [String, Symbol] the key to look up
|
|
136
|
+
# @return [Object, nil] the value or nil if not found
|
|
137
|
+
#
|
|
138
|
+
# @example
|
|
139
|
+
# ctx["name"] # => "John"
|
|
140
|
+
# ctx[:name] # => "John" (symbols work too)
|
|
141
|
+
#
|
|
142
|
+
|
|
143
|
+
def [](key)
|
|
144
|
+
@data[key.to_s]
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
# Set value by key (hash-style access)
|
|
149
|
+
#
|
|
150
|
+
# @param key [String, Symbol] the key to set
|
|
151
|
+
# @param value [Object] the value to store
|
|
152
|
+
# @return [Object] the value
|
|
153
|
+
#
|
|
154
|
+
# @example
|
|
155
|
+
# ctx["name"] = "John"
|
|
156
|
+
#
|
|
157
|
+
|
|
158
|
+
def []=(key, value)
|
|
159
|
+
@data[key.to_s] = value
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
# Set value (chainable)
|
|
164
|
+
#
|
|
165
|
+
# @param key [String, Symbol] the key to set
|
|
166
|
+
# @param value [Object] the value to store
|
|
167
|
+
# @return [Context] self for chaining
|
|
168
|
+
#
|
|
169
|
+
# @example
|
|
170
|
+
# ctx.set(:name, "John").set(:age, 30)
|
|
171
|
+
#
|
|
172
|
+
|
|
173
|
+
def set(key, value)
|
|
174
|
+
self[key] = value
|
|
175
|
+
self
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
# Get value with dot notation path support
|
|
180
|
+
#
|
|
181
|
+
# Traverses nested hashes/contexts using dot-separated paths.
|
|
182
|
+
#
|
|
183
|
+
# @param path [String] dot-separated path (e.g., "address.city")
|
|
184
|
+
# @return [Object, nil] the value or nil if not found
|
|
185
|
+
#
|
|
186
|
+
# @example
|
|
187
|
+
# ctx["user"] = { "address" => { "city" => "NYC" } }
|
|
188
|
+
# ctx.get("user.address.city") # => "NYC"
|
|
189
|
+
#
|
|
190
|
+
|
|
191
|
+
def get(path)
|
|
192
|
+
parts = path.to_s.split(".")
|
|
193
|
+
result = @data
|
|
194
|
+
|
|
195
|
+
parts.each do |part|
|
|
196
|
+
case result
|
|
197
|
+
when Hash
|
|
198
|
+
result = result[part] || result[part.to_sym]
|
|
199
|
+
when Context
|
|
200
|
+
result = result[part]
|
|
201
|
+
else
|
|
202
|
+
return nil
|
|
203
|
+
end
|
|
204
|
+
return nil if result.nil?
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
result
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
# Check if a key exists
|
|
212
|
+
#
|
|
213
|
+
# @param key [String, Symbol] the key to check
|
|
214
|
+
# @return [Boolean] true if key exists
|
|
215
|
+
#
|
|
216
|
+
# @example
|
|
217
|
+
# ctx.has?("name") # => true
|
|
218
|
+
# ctx.has?("unknown") # => false
|
|
219
|
+
#
|
|
220
|
+
|
|
221
|
+
def has?(key)
|
|
222
|
+
@data.key?(key.to_s)
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
alias key? has?
|
|
226
|
+
alias include? has?
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
# Convert to a plain Hash
|
|
230
|
+
#
|
|
231
|
+
# @return [Hash] copy of internal data
|
|
232
|
+
#
|
|
233
|
+
# @example
|
|
234
|
+
# ctx.to_h # => { "name" => "John", "age" => 30 }
|
|
235
|
+
#
|
|
236
|
+
|
|
237
|
+
def to_h
|
|
238
|
+
@data.dup
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
alias to_hash to_h
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
# Merge with another hash or context
|
|
245
|
+
#
|
|
246
|
+
# Creates a new Context with merged data. Does not modify original.
|
|
247
|
+
#
|
|
248
|
+
# @param other [Hash, Context] values to merge in
|
|
249
|
+
# @return [Context] new context with merged data
|
|
250
|
+
#
|
|
251
|
+
# @example
|
|
252
|
+
# new_ctx = ctx.merge({ email: "john@example.com" })
|
|
253
|
+
#
|
|
254
|
+
|
|
255
|
+
def merge(other)
|
|
256
|
+
other_data = other.respond_to?(:to_h) ? other.to_h : other
|
|
257
|
+
Context.new(@data.merge(other_data.transform_keys(&:to_s)))
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
# Set the schema name for reference resolution
|
|
262
|
+
#
|
|
263
|
+
# @param name [String] the schema name
|
|
264
|
+
# @return [Context] self for chaining
|
|
265
|
+
#
|
|
266
|
+
|
|
267
|
+
def with_schema(name)
|
|
268
|
+
@schema_name = name
|
|
269
|
+
self
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
# Dynamic method access (dot notation for fields)
|
|
274
|
+
#
|
|
275
|
+
# Provides Ruby-style attribute access to context values.
|
|
276
|
+
# Nested hashes are automatically wrapped in Context objects.
|
|
277
|
+
#
|
|
278
|
+
# @example
|
|
279
|
+
# ctx.name # same as ctx["name"]
|
|
280
|
+
# ctx.name = "X" # same as ctx["name"] = "X"
|
|
281
|
+
#
|
|
282
|
+
|
|
283
|
+
def method_missing(method, *args, &block)
|
|
284
|
+
method_name = method.to_s
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
# Handle setters (method=)
|
|
288
|
+
if method_name.end_with?("=")
|
|
289
|
+
key = method_name.chomp("=")
|
|
290
|
+
return self[key] = args.first
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
# Handle getters
|
|
295
|
+
if @data.key?(method_name)
|
|
296
|
+
value = @data[method_name]
|
|
297
|
+
|
|
298
|
+
# Wrap nested hashes in Context for continued dot access
|
|
299
|
+
return value.is_a?(Hash) ? Context.new(value) : value
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
super
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
# Check if method responds (for method_missing)
|
|
307
|
+
#
|
|
308
|
+
# @param method [Symbol] method name
|
|
309
|
+
# @param include_private [Boolean] include private methods
|
|
310
|
+
# @return [Boolean] true if method would be handled
|
|
311
|
+
#
|
|
312
|
+
|
|
313
|
+
def respond_to_missing?(method, include_private = false)
|
|
314
|
+
method_name = method.to_s.chomp("=")
|
|
315
|
+
@data.key?(method_name) || super
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
# Get all keys
|
|
320
|
+
#
|
|
321
|
+
# @return [Array<String>] list of keys
|
|
322
|
+
#
|
|
323
|
+
|
|
324
|
+
def keys
|
|
325
|
+
@data.keys
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
# Get all values
|
|
330
|
+
#
|
|
331
|
+
# @return [Array] list of values
|
|
332
|
+
#
|
|
333
|
+
|
|
334
|
+
def values
|
|
335
|
+
@data.values
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
# Iterate over key-value pairs
|
|
340
|
+
#
|
|
341
|
+
# @yield [key, value] block for each pair
|
|
342
|
+
# @return [Enumerator] if no block given
|
|
343
|
+
#
|
|
344
|
+
|
|
345
|
+
def each(&block)
|
|
346
|
+
@data.each(&block)
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
# Check if context is empty
|
|
351
|
+
#
|
|
352
|
+
# @return [Boolean] true if no data
|
|
353
|
+
#
|
|
354
|
+
|
|
355
|
+
def empty?
|
|
356
|
+
@data.empty?
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
# Get number of entries
|
|
361
|
+
#
|
|
362
|
+
# @return [Integer] number of key-value pairs
|
|
363
|
+
#
|
|
364
|
+
|
|
365
|
+
def size
|
|
366
|
+
@data.size
|
|
367
|
+
end
|
|
368
|
+
|
|
369
|
+
alias length size
|
|
370
|
+
end
|
|
371
|
+
end
|
|
372
|
+
end
|
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# =============================================================================
|
|
4
|
+
|
|
5
|
+
# Synthra Generator Engine
|
|
6
|
+
# =============================================================================
|
|
7
|
+
#
|
|
8
|
+
# The Engine is the core component responsible for generating fake data
|
|
9
|
+
# from schema definitions. It coordinates type generation, field behaviors,
|
|
10
|
+
# uniqueness constraints, and deterministic seeding.
|
|
11
|
+
#
|
|
12
|
+
# @example Basic usage
|
|
13
|
+
# engine = Generator::Engine.new(schema)
|
|
14
|
+
# record = engine.generate
|
|
15
|
+
#
|
|
16
|
+
# @example Deterministic generation
|
|
17
|
+
# engine = Generator::Engine.new(schema)
|
|
18
|
+
# record1 = engine.generate(seed: 12345)
|
|
19
|
+
# record2 = engine.generate(seed: 12345)
|
|
20
|
+
# # record1 == record2 (same seed = same output)
|
|
21
|
+
#
|
|
22
|
+
# =============================================================================
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
module Synthra
|
|
26
|
+
module Generator
|
|
27
|
+
|
|
28
|
+
# Core generation engine
|
|
29
|
+
#
|
|
30
|
+
# The Engine orchestrates the data generation process, handling:
|
|
31
|
+
# - Field iteration and value generation
|
|
32
|
+
# - Type lookup and invocation
|
|
33
|
+
# - Uniqueness constraint enforcement
|
|
34
|
+
# - Optional and conditional field handling
|
|
35
|
+
# - Field-level behavior application
|
|
36
|
+
# - Deterministic seeding via RNG
|
|
37
|
+
#
|
|
38
|
+
# @example Generate a single record
|
|
39
|
+
# engine = Engine.new(schema)
|
|
40
|
+
# user = engine.generate(seed: 42, mode: :random)
|
|
41
|
+
#
|
|
42
|
+
# @example Generate multiple records
|
|
43
|
+
# users = engine.generate_many(100, seed: 42)
|
|
44
|
+
#
|
|
45
|
+
|
|
46
|
+
class Engine
|
|
47
|
+
|
|
48
|
+
# @!attribute [r] schema
|
|
49
|
+
# The schema being generated from
|
|
50
|
+
# @return [Schema] the schema definition
|
|
51
|
+
|
|
52
|
+
attr_reader :schema
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
# @!attribute [r] rng
|
|
56
|
+
# The random number generator for determinism
|
|
57
|
+
# @return [RNG, nil] the RNG instance
|
|
58
|
+
|
|
59
|
+
attr_reader :rng
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
# @!attribute [r] registry
|
|
63
|
+
# Registry for cross-schema references
|
|
64
|
+
# @return [Registry, nil] schema registry
|
|
65
|
+
|
|
66
|
+
attr_reader :registry
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
# Create a new Engine instance
|
|
70
|
+
#
|
|
71
|
+
# @param schema [Schema] the schema to generate from
|
|
72
|
+
# @param registry [Registry, nil] optional registry for Ref() lookups
|
|
73
|
+
# @param resolver [Resolver, nil] optional resolver instance to reuse (for cycle detection)
|
|
74
|
+
#
|
|
75
|
+
# @example
|
|
76
|
+
# engine = Engine.new(user_schema, registry: schema_registry)
|
|
77
|
+
#
|
|
78
|
+
|
|
79
|
+
def initialize(schema, registry: nil, resolver: nil)
|
|
80
|
+
@schema = schema
|
|
81
|
+
@registry = registry
|
|
82
|
+
@rng = nil
|
|
83
|
+
@faker_adapter = nil
|
|
84
|
+
@uniqueness = Uniqueness.new(schema_name: schema.name)
|
|
85
|
+
|
|
86
|
+
# Use provided resolver or create new one if registry is available
|
|
87
|
+
# Reusing resolver ensures @generating set is shared for cycle detection
|
|
88
|
+
@resolver = resolver || (registry ? Resolver.new(registry) : nil)
|
|
89
|
+
|
|
90
|
+
# Pre-compute observability flag at initialization to avoid repeated
|
|
91
|
+
# config lookups in the hot path (called once per Engine, not per record)
|
|
92
|
+
@observability_enabled = Synthra.configuration.observability_enabled?
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
# Generate a single record
|
|
97
|
+
#
|
|
98
|
+
# Creates one fake data record based on the schema definition.
|
|
99
|
+
# Use the seed parameter for deterministic/reproducible output.
|
|
100
|
+
#
|
|
101
|
+
# @param seed [Integer, nil] random seed for deterministic output
|
|
102
|
+
# @param mode [Symbol] generation mode (:random, :edge, :invalid, :mixed)
|
|
103
|
+
# @param overrides [Hash] field values to use instead of generating
|
|
104
|
+
# @return [Hash] the generated record
|
|
105
|
+
#
|
|
106
|
+
# @example Basic generation
|
|
107
|
+
# record = engine.generate
|
|
108
|
+
#
|
|
109
|
+
# @example Deterministic generation
|
|
110
|
+
# record = engine.generate(seed: 12345)
|
|
111
|
+
#
|
|
112
|
+
# @example With overrides
|
|
113
|
+
# record = engine.generate(overrides: { "name" => "Test User" })
|
|
114
|
+
#
|
|
115
|
+
|
|
116
|
+
def generate(seed: nil, mode: :random, overrides: {}, depth: 0, parent_context: nil, shared_context: nil)
|
|
117
|
+
|
|
118
|
+
# Reset RNG if seed is provided, or create one if none exists
|
|
119
|
+
setup_rng(seed) if seed || @rng.nil?
|
|
120
|
+
generate_single(mode: mode, overrides: overrides, depth: depth, parent_context: parent_context, shared_context: shared_context)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
# Generate multiple records
|
|
125
|
+
#
|
|
126
|
+
# Creates multiple fake data records efficiently. Uniqueness
|
|
127
|
+
# constraints are enforced across the entire batch.
|
|
128
|
+
#
|
|
129
|
+
# @param count [Integer] number of records to generate
|
|
130
|
+
# @param seed [Integer, nil] random seed for deterministic output
|
|
131
|
+
# @param mode [Symbol] generation mode
|
|
132
|
+
# @param overrides [Hash] field values to override in all records
|
|
133
|
+
# @return [Array<Hash>] array of generated records
|
|
134
|
+
#
|
|
135
|
+
# @example Generate 100 users
|
|
136
|
+
# users = engine.generate_many(100)
|
|
137
|
+
#
|
|
138
|
+
# @example Deterministic batch
|
|
139
|
+
# users = engine.generate_many(100, seed: 42)
|
|
140
|
+
#
|
|
141
|
+
# @note For large batches (10K+ records), consider using
|
|
142
|
+
# generate_many_stream for memory-efficient lazy generation.
|
|
143
|
+
#
|
|
144
|
+
|
|
145
|
+
def generate_many(count, seed: nil, mode: :random, overrides: {}, shared_context: nil)
|
|
146
|
+
setup_rng(seed)
|
|
147
|
+
@uniqueness.clear # Reset uniqueness tracking for new batch
|
|
148
|
+
|
|
149
|
+
# Create shared context for batch - values persist across all records
|
|
150
|
+
shared_context ||= {}
|
|
151
|
+
|
|
152
|
+
count.times.map do
|
|
153
|
+
generate_single(mode: mode, overrides: overrides, depth: 0, shared_context: shared_context)
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
# Generate multiple records as a lazy stream (Enumerator)
|
|
159
|
+
#
|
|
160
|
+
# Returns an Enumerator that generates records on-demand. This is
|
|
161
|
+
# memory-efficient for large batches as records are not all held
|
|
162
|
+
# in memory at once.
|
|
163
|
+
#
|
|
164
|
+
# @param count [Integer] number of records to generate
|
|
165
|
+
# @param seed [Integer, nil] random seed for deterministic output
|
|
166
|
+
# @param mode [Symbol] generation mode
|
|
167
|
+
# @param overrides [Hash] field values to override in all records
|
|
168
|
+
# @return [Enumerator] lazy enumerator of records
|
|
169
|
+
#
|
|
170
|
+
# @example Generate 10K users efficiently
|
|
171
|
+
# engine.generate_many_stream(10000, seed: 42).each do |user|
|
|
172
|
+
# process(user) # Records generated one at a time
|
|
173
|
+
# end
|
|
174
|
+
#
|
|
175
|
+
|
|
176
|
+
def generate_many_stream(count, seed: nil, mode: :random, overrides: {}, shared_context: nil)
|
|
177
|
+
setup_rng(seed)
|
|
178
|
+
@uniqueness.clear # Reset uniqueness tracking for new batch
|
|
179
|
+
|
|
180
|
+
# Create shared context for batch - values persist across all records
|
|
181
|
+
shared_context ||= {}
|
|
182
|
+
|
|
183
|
+
Enumerator.new(count) do |yielder|
|
|
184
|
+
count.times do
|
|
185
|
+
yielder << generate_single(mode: mode, overrides: overrides, depth: 0, shared_context: shared_context)
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
private
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
# Setup RNG and FakerAdapter for deterministic output
|
|
194
|
+
#
|
|
195
|
+
# @param seed [Integer, nil] seed value (nil generates random seed)
|
|
196
|
+
# @return [void]
|
|
197
|
+
#
|
|
198
|
+
|
|
199
|
+
def setup_rng(seed)
|
|
200
|
+
@rng = RNG.new(seed)
|
|
201
|
+
@faker_adapter = FakerAdapter.new(@rng)
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
# Generate a single record (internal)
|
|
206
|
+
#
|
|
207
|
+
# @param mode [Symbol] generation mode
|
|
208
|
+
# @param overrides [Hash] field overrides
|
|
209
|
+
# @param depth [Integer] current recursion depth
|
|
210
|
+
# @param shared_context [Hash] shared values that persist across batch
|
|
211
|
+
# @return [Hash] generated record
|
|
212
|
+
#
|
|
213
|
+
|
|
214
|
+
def generate_single(mode:, overrides:, depth: 0, parent_context: nil, shared_context: nil)
|
|
215
|
+
# Cache config locally to avoid repeated method calls
|
|
216
|
+
config = Synthra.configuration
|
|
217
|
+
presence_rate = config.optional_field_presence_rate || Synthra::OPTIONAL_FIELD_PRESENCE_RATE
|
|
218
|
+
null_rate = config.nullable_field_null_rate || Synthra::NULLABLE_FIELD_NULL_RATE
|
|
219
|
+
|
|
220
|
+
# Create context with proper initialization
|
|
221
|
+
context = Context.new(
|
|
222
|
+
{},
|
|
223
|
+
registry: @registry,
|
|
224
|
+
depth: depth,
|
|
225
|
+
parent_context: parent_context,
|
|
226
|
+
shared_context: shared_context || {},
|
|
227
|
+
resolver: @resolver,
|
|
228
|
+
faker_adapter: @faker_adapter
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
result = {}
|
|
232
|
+
applicator = Behaviors::Applicator.new(@schema, @rng)
|
|
233
|
+
|
|
234
|
+
# Iterate fields in definition order (important for copy() to work)
|
|
235
|
+
@schema.fields.each do |field|
|
|
236
|
+
# Skip if override provided
|
|
237
|
+
if overrides.key?(field.name) || overrides.key?(field.name.to_sym)
|
|
238
|
+
result[field.name] = overrides[field.name] || overrides[field.name.to_sym]
|
|
239
|
+
context[field.name] = result[field.name]
|
|
240
|
+
next
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
# Handle conditional fields
|
|
244
|
+
if field.conditional?
|
|
245
|
+
next unless context[field.condition]
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
# Handle optional fields
|
|
249
|
+
next if field.optional? && !@rng.boolean(presence_rate)
|
|
250
|
+
|
|
251
|
+
# Generate the field value with optional timing
|
|
252
|
+
start_time = @observability_enabled ? Process.clock_gettime(Process::CLOCK_MONOTONIC) : nil
|
|
253
|
+
value = generate_field(field, context, mode)
|
|
254
|
+
|
|
255
|
+
# Apply field-level behaviors
|
|
256
|
+
value = applicator.apply_field_behaviors(field, value)
|
|
257
|
+
next if value == :omit
|
|
258
|
+
|
|
259
|
+
# Handle nullable fields
|
|
260
|
+
value = nil if field.nullable? && @rng.boolean(null_rate)
|
|
261
|
+
|
|
262
|
+
# Observability hooks (only if enabled - checked once at init)
|
|
263
|
+
if @observability_enabled
|
|
264
|
+
duration_ms = ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time) * 1000).round
|
|
265
|
+
emit_observability(config, field, value, duration_ms)
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
result[field.name] = value
|
|
269
|
+
context[field.name] = value
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
result
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
def emit_observability(config, field, value, duration_ms)
|
|
276
|
+
if config.metrics_collector
|
|
277
|
+
config.metrics_collector.timing(
|
|
278
|
+
"synthra.field_generation",
|
|
279
|
+
duration_ms,
|
|
280
|
+
schema: @schema.name,
|
|
281
|
+
field: field.name,
|
|
282
|
+
type: field.type_name
|
|
283
|
+
)
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
config.logger&.debug(
|
|
287
|
+
"Generated field #{@schema.name}.#{field.name} (#{field.type_name}) in #{duration_ms}ms"
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
config.on_field_generated&.call(@schema.name, field.name, value, duration_ms)
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
# Generate a single field value
|
|
295
|
+
#
|
|
296
|
+
# @param field [Field] the field definition
|
|
297
|
+
# @param context [Context] current generation context
|
|
298
|
+
# @param mode [Symbol] generation mode
|
|
299
|
+
# @return [Object] generated value
|
|
300
|
+
#
|
|
301
|
+
|
|
302
|
+
def generate_field(field, context, mode)
|
|
303
|
+
type_name = field.type_name
|
|
304
|
+
type_args = field.type_args
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
# Look up the type generator - first try Types::Registry
|
|
308
|
+
type_gen = begin
|
|
309
|
+
Types::Registry.lookup(type_name)
|
|
310
|
+
rescue Errors::UnknownTypeError
|
|
311
|
+
# If not a registered type, check if it's a schema reference
|
|
312
|
+
if @registry && @registry.schema?(type_name)
|
|
313
|
+
# Create an ObjectType for the schema reference
|
|
314
|
+
Types::Core::ObjectType.new(type_name)
|
|
315
|
+
else
|
|
316
|
+
raise # Re-raise the UnknownTypeError if not a schema either
|
|
317
|
+
end
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
# Pass faker_adapter in context for type generators to use
|
|
322
|
+
context.faker_adapter = @faker_adapter if @faker_adapter
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
# Handle uniqueness constraint
|
|
326
|
+
if field.unique?
|
|
327
|
+
@uniqueness.generate_unique(field.name) do
|
|
328
|
+
type_gen.generate(@rng, context, type_args, mode)
|
|
329
|
+
end
|
|
330
|
+
else
|
|
331
|
+
type_gen.generate(@rng, context, type_args, mode)
|
|
332
|
+
end
|
|
333
|
+
end
|
|
334
|
+
end
|
|
335
|
+
end
|
|
336
|
+
end
|