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,165 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# =============================================================================
|
|
4
|
+
|
|
5
|
+
# Synthra Behaviors Applicator
|
|
6
|
+
# =============================================================================
|
|
7
|
+
#
|
|
8
|
+
# The Applicator is responsible for applying behaviors to generated data.
|
|
9
|
+
# It handles both schema-level behaviors (applied to entire records) and
|
|
10
|
+
# field-level behaviors (applied to individual field values).
|
|
11
|
+
#
|
|
12
|
+
# @example Usage (internal)
|
|
13
|
+
# applicator = Applicator.new(schema, rng)
|
|
14
|
+
# result = applicator.apply_schema_behaviors(generated_record)
|
|
15
|
+
#
|
|
16
|
+
# =============================================================================
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
module Synthra
|
|
20
|
+
module Behaviors
|
|
21
|
+
|
|
22
|
+
# Applies behaviors to generated data
|
|
23
|
+
#
|
|
24
|
+
# The Applicator coordinates behavior application, looking up behavior
|
|
25
|
+
# classes from the registry and applying them in order. It handles
|
|
26
|
+
# both schema-level and field-level behaviors.
|
|
27
|
+
#
|
|
28
|
+
# @example Apply schema behaviors
|
|
29
|
+
# applicator = Applicator.new(schema, rng)
|
|
30
|
+
# result = applicator.apply_schema_behaviors({ "name" => "John" })
|
|
31
|
+
# # May add latency, raise errors, etc.
|
|
32
|
+
#
|
|
33
|
+
# @example Apply field behaviors
|
|
34
|
+
# value = applicator.apply_field_behaviors(field, "original_value")
|
|
35
|
+
# # => modified value or :omit to remove field
|
|
36
|
+
#
|
|
37
|
+
|
|
38
|
+
class Applicator
|
|
39
|
+
|
|
40
|
+
# Create a new Applicator
|
|
41
|
+
#
|
|
42
|
+
# @param schema [Schema] the schema containing behavior definitions
|
|
43
|
+
# @param rng [Generator::RNG] random number generator for deterministic behavior
|
|
44
|
+
#
|
|
45
|
+
# @example
|
|
46
|
+
# applicator = Applicator.new(schema, rng)
|
|
47
|
+
#
|
|
48
|
+
|
|
49
|
+
def initialize(schema, rng)
|
|
50
|
+
@schema = schema
|
|
51
|
+
@rng = rng
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
# Apply schema-level behaviors to a generated record
|
|
56
|
+
#
|
|
57
|
+
# Applies all behaviors defined at the schema level (e.g., @latency,
|
|
58
|
+
# @failure) to the complete generated record. Behaviors are applied
|
|
59
|
+
# in the order they were defined.
|
|
60
|
+
#
|
|
61
|
+
# @param result [Hash] the generated record
|
|
62
|
+
# @return [Hash] the result after all behaviors have been applied
|
|
63
|
+
# @raise [SimulatedFailure] if failure behavior triggers
|
|
64
|
+
# @raise [SimulatedConnectionDrop] if close_connection behavior triggers
|
|
65
|
+
# @raise [SimulatedError] if simulate_error behavior triggers
|
|
66
|
+
#
|
|
67
|
+
# @example
|
|
68
|
+
# # Schema with @latency 100ms @failure 10%
|
|
69
|
+
# result = applicator.apply_schema_behaviors({ "id" => "123" })
|
|
70
|
+
# # May add 100ms delay, or raise SimulatedFailure 10% of the time
|
|
71
|
+
#
|
|
72
|
+
|
|
73
|
+
def apply_schema_behaviors(result)
|
|
74
|
+
@schema.all_behaviors.each do |behavior_config|
|
|
75
|
+
name = behavior_config[:name]
|
|
76
|
+
value = behavior_config[:value]
|
|
77
|
+
|
|
78
|
+
begin
|
|
79
|
+
|
|
80
|
+
# Look up the behavior class from registry
|
|
81
|
+
behavior_class = Registry.lookup(name)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
# Create behavior instance
|
|
85
|
+
# Handle both Class and BlockBehavior patterns
|
|
86
|
+
behavior = behavior_class.respond_to?(:new) ?
|
|
87
|
+
(behavior_class.is_a?(Class) ? behavior_class.new(value, @rng) : behavior_class.new(value, @rng)) :
|
|
88
|
+
behavior_class
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
# Apply the behavior, which may modify result or raise errors
|
|
92
|
+
result = behavior.apply(result)
|
|
93
|
+
rescue ArgumentError => e
|
|
94
|
+
|
|
95
|
+
# Only skip if it's an "Unknown behavior" error
|
|
96
|
+
# Re-raise other ArgumentErrors (e.g., wrong number of arguments)
|
|
97
|
+
if e.message.include?("Unknown behavior")
|
|
98
|
+
|
|
99
|
+
# Unknown behavior - skip silently
|
|
100
|
+
# This allows forward compatibility with unknown behaviors
|
|
101
|
+
else
|
|
102
|
+
|
|
103
|
+
# Re-raise real argument errors
|
|
104
|
+
raise
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
result
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
# Apply field-level behaviors to a field value
|
|
113
|
+
#
|
|
114
|
+
# Applies behaviors defined on individual fields (e.g., @partial_data,
|
|
115
|
+
# @latency). Field behaviors can modify the value or indicate that
|
|
116
|
+
# the field should be omitted entirely.
|
|
117
|
+
#
|
|
118
|
+
# @param field [Field] the field definition with behaviors
|
|
119
|
+
# @param value [Object] the generated field value
|
|
120
|
+
# @return [Object, Symbol] the modified value, or :omit to remove the field
|
|
121
|
+
#
|
|
122
|
+
# @example Apply partial_data behavior
|
|
123
|
+
# # Field with @partial_data 20%
|
|
124
|
+
# result = applicator.apply_field_behaviors(field, "value")
|
|
125
|
+
# # => "value" (80% of the time) or :omit (20% of the time)
|
|
126
|
+
#
|
|
127
|
+
|
|
128
|
+
def apply_field_behaviors(field, value)
|
|
129
|
+
field.behaviors.each do |fb|
|
|
130
|
+
begin
|
|
131
|
+
|
|
132
|
+
# Look up and instantiate the behavior
|
|
133
|
+
behavior_class = Registry.lookup(fb.type)
|
|
134
|
+
behavior = behavior_class.respond_to?(:new) ?
|
|
135
|
+
(behavior_class.is_a?(Class) ? behavior_class.new(fb.value, @rng) : behavior_class.new(fb.value, @rng)) :
|
|
136
|
+
behavior_class
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
# Special handling for partial_data - return :omit symbol
|
|
140
|
+
if fb.type == :partial_data && behavior.should_apply?
|
|
141
|
+
return :omit
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
# Apply other behaviors normally
|
|
146
|
+
value = behavior.apply(value)
|
|
147
|
+
rescue ArgumentError => e
|
|
148
|
+
|
|
149
|
+
# Only skip if it's an "Unknown behavior" error
|
|
150
|
+
# Re-raise other ArgumentErrors (e.g., wrong number of arguments)
|
|
151
|
+
if e.message.include?("Unknown behavior")
|
|
152
|
+
|
|
153
|
+
# Unknown behavior - skip silently
|
|
154
|
+
else
|
|
155
|
+
|
|
156
|
+
# Re-raise real argument errors
|
|
157
|
+
raise
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
value
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# =============================================================================
|
|
4
|
+
|
|
5
|
+
# Synthra Behaviors Base Class
|
|
6
|
+
# =============================================================================
|
|
7
|
+
#
|
|
8
|
+
# The Base class is the foundation for all behavior implementations.
|
|
9
|
+
# Behaviors modify how data is generated or returned, enabling simulation
|
|
10
|
+
# of real-world conditions like network latency, failures, and partial data.
|
|
11
|
+
#
|
|
12
|
+
# Built-in behaviors:
|
|
13
|
+
# - latency: Adds delays to simulate slow responses
|
|
14
|
+
# - failure: Raises errors to simulate service failures
|
|
15
|
+
# - partial_data: Omits fields to simulate incomplete data
|
|
16
|
+
# - close_connection: Simulates connection drops
|
|
17
|
+
# - simulate_error: Simulates specific HTTP errors
|
|
18
|
+
# - randomize_order: Shuffles array elements
|
|
19
|
+
#
|
|
20
|
+
# @example Creating a custom behavior
|
|
21
|
+
# class MyBehavior < Synthra::Behaviors::Base
|
|
22
|
+
# def apply(result, context = nil)
|
|
23
|
+
# return result unless should_apply?
|
|
24
|
+
# # Modify result here
|
|
25
|
+
# result.merge("modified" => true)
|
|
26
|
+
# end
|
|
27
|
+
# end
|
|
28
|
+
#
|
|
29
|
+
# =============================================================================
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
module Synthra
|
|
33
|
+
module Behaviors
|
|
34
|
+
|
|
35
|
+
# Base class for all behavior implementations
|
|
36
|
+
#
|
|
37
|
+
# All behaviors inherit from Base and implement the #apply method.
|
|
38
|
+
# The base class provides probability-based activation through
|
|
39
|
+
# #should_apply? method.
|
|
40
|
+
#
|
|
41
|
+
# @abstract Subclass and implement {#apply}
|
|
42
|
+
#
|
|
43
|
+
# @example Implement a simple behavior
|
|
44
|
+
# class Logging < Base
|
|
45
|
+
# def apply(result, context = nil)
|
|
46
|
+
# return result unless should_apply?
|
|
47
|
+
# puts "Generated: #{result.inspect}"
|
|
48
|
+
# result
|
|
49
|
+
# end
|
|
50
|
+
# end
|
|
51
|
+
#
|
|
52
|
+
|
|
53
|
+
class Base
|
|
54
|
+
|
|
55
|
+
# @!attribute [r] value
|
|
56
|
+
# The behavior configuration value
|
|
57
|
+
# @return [Object] configuration (probability, range, etc.)
|
|
58
|
+
|
|
59
|
+
attr_reader :value
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
# @!attribute [r] rng
|
|
63
|
+
# Random number generator for deterministic probability checks
|
|
64
|
+
# @return [Generator::RNG, nil] RNG instance
|
|
65
|
+
|
|
66
|
+
attr_reader :rng
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
# Create a new behavior instance
|
|
70
|
+
#
|
|
71
|
+
# @param value [Object] behavior configuration (probability, range, etc.)
|
|
72
|
+
# @param rng [Generator::RNG, nil] random number generator for determinism
|
|
73
|
+
#
|
|
74
|
+
# @example Create with probability
|
|
75
|
+
# Latency.new(100..500, rng)
|
|
76
|
+
# Failure.new(10, rng) # 10% probability
|
|
77
|
+
#
|
|
78
|
+
|
|
79
|
+
def initialize(value, rng = nil)
|
|
80
|
+
@value = value
|
|
81
|
+
@rng = rng
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
# Apply the behavior to generated data
|
|
86
|
+
#
|
|
87
|
+
# This is the main method that behaviors implement to modify
|
|
88
|
+
# generated data or produce side effects.
|
|
89
|
+
#
|
|
90
|
+
# @abstract Must be implemented by subclasses
|
|
91
|
+
#
|
|
92
|
+
# @param result [Object] the generated data to modify
|
|
93
|
+
# @param context [Generator::Context, nil] generation context
|
|
94
|
+
# @return [Object] the modified result
|
|
95
|
+
# @raise [NotImplementedError] if not overridden
|
|
96
|
+
#
|
|
97
|
+
# @example Implementation
|
|
98
|
+
# def apply(result, context = nil)
|
|
99
|
+
# return result unless should_apply?
|
|
100
|
+
# # ... modify result ...
|
|
101
|
+
# result
|
|
102
|
+
# end
|
|
103
|
+
#
|
|
104
|
+
|
|
105
|
+
def apply(result, context = nil)
|
|
106
|
+
raise NotImplementedError, "#{self.class} must implement #apply"
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
# Check if this behavior should be applied
|
|
111
|
+
#
|
|
112
|
+
# Uses probability-based activation. If value is an Integer,
|
|
113
|
+
# it's treated as a percentage probability. If nil or >= 100,
|
|
114
|
+
# always applies.
|
|
115
|
+
#
|
|
116
|
+
# @return [Boolean] true if the behavior should be applied
|
|
117
|
+
#
|
|
118
|
+
# @example Probability check
|
|
119
|
+
# behavior = Failure.new(10, rng) # 10% probability
|
|
120
|
+
# behavior.should_apply? # => true (10% of the time)
|
|
121
|
+
#
|
|
122
|
+
|
|
123
|
+
def should_apply?
|
|
124
|
+
probability = extract_probability
|
|
125
|
+
|
|
126
|
+
# Always apply if no probability specified or 100%+
|
|
127
|
+
return true if probability.nil? || probability >= 100
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
# Use RNG for deterministic tests, or rand() otherwise
|
|
131
|
+
if rng
|
|
132
|
+
rng.rand(100) < probability
|
|
133
|
+
else
|
|
134
|
+
rand(100) < probability
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
private
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
# Extract probability from the value configuration
|
|
142
|
+
#
|
|
143
|
+
# @return [Integer, nil] probability percentage (0-100) or nil
|
|
144
|
+
#
|
|
145
|
+
|
|
146
|
+
def extract_probability
|
|
147
|
+
case value
|
|
148
|
+
when Integer then value
|
|
149
|
+
when Hash then value[:probability] || value[:percent]
|
|
150
|
+
else nil
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# =============================================================================
|
|
4
|
+
|
|
5
|
+
# Synthra Close Connection Behavior
|
|
6
|
+
# =============================================================================
|
|
7
|
+
#
|
|
8
|
+
# Simulates connection drops by raising SimulatedConnectionDrop exception.
|
|
9
|
+
# Useful for testing error handling and retry logic.
|
|
10
|
+
#
|
|
11
|
+
# @example DSL usage
|
|
12
|
+
# User:
|
|
13
|
+
# @close_connection 5% # 5% chance of connection drop
|
|
14
|
+
# name: name
|
|
15
|
+
# email: email
|
|
16
|
+
#
|
|
17
|
+
# =============================================================================
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
module Synthra
|
|
21
|
+
module Behaviors
|
|
22
|
+
|
|
23
|
+
# Close connection behavior - simulates connection drop
|
|
24
|
+
#
|
|
25
|
+
# Raises SimulatedConnectionDrop exception when activated, simulating
|
|
26
|
+
# a network connection failure.
|
|
27
|
+
#
|
|
28
|
+
# @example Apply close connection behavior
|
|
29
|
+
# behavior = CloseConnection.new(5, rng) # 5% probability
|
|
30
|
+
# begin
|
|
31
|
+
# behavior.apply({ "name" => "John" })
|
|
32
|
+
# rescue SimulatedConnectionDrop => e
|
|
33
|
+
# # Handle connection drop
|
|
34
|
+
# end
|
|
35
|
+
#
|
|
36
|
+
|
|
37
|
+
class CloseConnection < Base
|
|
38
|
+
|
|
39
|
+
# Apply close connection behavior
|
|
40
|
+
#
|
|
41
|
+
# Raises SimulatedConnectionDrop if the behavior should be applied
|
|
42
|
+
# based on probability.
|
|
43
|
+
#
|
|
44
|
+
# @param result [Object] generated data (not modified)
|
|
45
|
+
# @param context [Generator::Context, nil] generation context (not used)
|
|
46
|
+
# @return [Object] result (if behavior not applied)
|
|
47
|
+
# @raise [SimulatedConnectionDrop] if behavior activates
|
|
48
|
+
#
|
|
49
|
+
|
|
50
|
+
def apply(result, context = nil)
|
|
51
|
+
if should_apply?
|
|
52
|
+
probability = extract_probability
|
|
53
|
+
raise SimulatedConnectionDrop.new(probability: probability)
|
|
54
|
+
end
|
|
55
|
+
result
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# =============================================================================
|
|
4
|
+
|
|
5
|
+
# Synthra Deprecated Behavior
|
|
6
|
+
# =============================================================================
|
|
7
|
+
#
|
|
8
|
+
# The Deprecated behavior emits warnings when a schema or field is generated.
|
|
9
|
+
# This helps teams track usage of deprecated schemas and plan migrations.
|
|
10
|
+
#
|
|
11
|
+
# @example Schema-level deprecation
|
|
12
|
+
# LegacyUser:
|
|
13
|
+
# @deprecated "Use User schema instead (v2.0)"
|
|
14
|
+
# id: uuid
|
|
15
|
+
# name: text
|
|
16
|
+
#
|
|
17
|
+
# @example Field-level deprecation (in behaviors)
|
|
18
|
+
# User:
|
|
19
|
+
# @deprecated "This schema will be removed in v3.0"
|
|
20
|
+
# old_id: id_sequence
|
|
21
|
+
# id: uuid
|
|
22
|
+
#
|
|
23
|
+
# =============================================================================
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
# :nocov:
|
|
27
|
+
module Synthra
|
|
28
|
+
module Behaviors
|
|
29
|
+
|
|
30
|
+
# Emits deprecation warnings during data generation
|
|
31
|
+
#
|
|
32
|
+
# The Deprecated behavior is used to mark schemas as deprecated.
|
|
33
|
+
# When data is generated from a deprecated schema, a warning is
|
|
34
|
+
# logged/printed to alert developers.
|
|
35
|
+
#
|
|
36
|
+
# @example DSL usage
|
|
37
|
+
# OldSchema:
|
|
38
|
+
# @deprecated "Use NewSchema instead"
|
|
39
|
+
# field: text
|
|
40
|
+
#
|
|
41
|
+
# @example Programmatic check
|
|
42
|
+
# behavior = Deprecated.new("Use NewSchema instead")
|
|
43
|
+
# behavior.apply(result, context) # Prints warning
|
|
44
|
+
#
|
|
45
|
+
|
|
46
|
+
class Deprecated < Base
|
|
47
|
+
|
|
48
|
+
# Apply the deprecation warning
|
|
49
|
+
#
|
|
50
|
+
# Emits a warning message about the deprecated schema.
|
|
51
|
+
# The warning is sent to Synthra.logger if available,
|
|
52
|
+
# otherwise to $stderr.
|
|
53
|
+
#
|
|
54
|
+
# @param result [Object] the generated data (passed through unchanged)
|
|
55
|
+
# @param context [Generator::Context, nil] generation context
|
|
56
|
+
# @return [Object] the unchanged result
|
|
57
|
+
#
|
|
58
|
+
|
|
59
|
+
def apply(result, context = nil)
|
|
60
|
+
emit_warning(context)
|
|
61
|
+
result
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
private
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
# Emit the deprecation warning
|
|
68
|
+
#
|
|
69
|
+
# @param context [Generator::Context, nil] generation context
|
|
70
|
+
# @return [void]
|
|
71
|
+
#
|
|
72
|
+
|
|
73
|
+
def emit_warning(context)
|
|
74
|
+
schema_name = context&.instance_variable_get(:@schema_name) || "Unknown"
|
|
75
|
+
message = build_warning_message(schema_name)
|
|
76
|
+
|
|
77
|
+
if Synthra.configuration.logger
|
|
78
|
+
Synthra.configuration.logger.warn(message)
|
|
79
|
+
else
|
|
80
|
+
warn "[Synthra] #{message}"
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
# Build the warning message
|
|
86
|
+
#
|
|
87
|
+
# @param schema_name [String] name of the deprecated schema
|
|
88
|
+
# @return [String] formatted warning message
|
|
89
|
+
#
|
|
90
|
+
|
|
91
|
+
def build_warning_message(schema_name)
|
|
92
|
+
deprecation_note = value.is_a?(String) ? value : "This schema is deprecated"
|
|
93
|
+
|
|
94
|
+
"DEPRECATED: Schema '#{schema_name}' - #{deprecation_note}"
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
# :nocov:
|
|
100
|
+
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# =============================================================================
|
|
4
|
+
|
|
5
|
+
# Synthra Failure Behavior
|
|
6
|
+
# =============================================================================
|
|
7
|
+
#
|
|
8
|
+
# Simulates service failures by raising SimulatedFailure errors.
|
|
9
|
+
# Useful for testing error handling and retry logic in applications.
|
|
10
|
+
#
|
|
11
|
+
# @example DSL usage
|
|
12
|
+
# API:
|
|
13
|
+
# @failure 10% # 10% chance of failure
|
|
14
|
+
# @failure 5 # Same as 5%
|
|
15
|
+
# id: uuid
|
|
16
|
+
#
|
|
17
|
+
# @example Testing error handling
|
|
18
|
+
# begin
|
|
19
|
+
# api_schema.generate
|
|
20
|
+
# rescue Synthra::SimulatedFailure => e
|
|
21
|
+
# # Test that your app handles failures correctly
|
|
22
|
+
# end
|
|
23
|
+
#
|
|
24
|
+
# =============================================================================
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
module Synthra
|
|
28
|
+
module Behaviors
|
|
29
|
+
|
|
30
|
+
# Failure behavior - simulates service failure
|
|
31
|
+
#
|
|
32
|
+
# Raises a SimulatedFailure error based on configured probability.
|
|
33
|
+
# This is useful for testing how applications handle service
|
|
34
|
+
# unavailability, timeouts, and other failure scenarios.
|
|
35
|
+
#
|
|
36
|
+
# @example Create with probability
|
|
37
|
+
# failure = Failure.new(10, rng) # 10% failure rate
|
|
38
|
+
# failure.apply(result) # May raise SimulatedFailure
|
|
39
|
+
#
|
|
40
|
+
|
|
41
|
+
class Failure < Base
|
|
42
|
+
|
|
43
|
+
# Apply the failure behavior
|
|
44
|
+
#
|
|
45
|
+
# Based on probability, either raises SimulatedFailure or
|
|
46
|
+
# returns the result unchanged.
|
|
47
|
+
#
|
|
48
|
+
# @param result [Object] the generated data
|
|
49
|
+
# @param context [Generator::Context, nil] generation context (not used)
|
|
50
|
+
# @return [Object] the result if no failure
|
|
51
|
+
# @raise [SimulatedFailure] if failure is triggered
|
|
52
|
+
#
|
|
53
|
+
# @example
|
|
54
|
+
# failure = Failure.new(10, rng) # 10% failure rate
|
|
55
|
+
# failure.apply({ "id" => "123" })
|
|
56
|
+
# # Returns result 90% of the time
|
|
57
|
+
# # Raises SimulatedFailure 10% of the time
|
|
58
|
+
#
|
|
59
|
+
|
|
60
|
+
def apply(result, context = nil)
|
|
61
|
+
if should_apply?
|
|
62
|
+
probability = extract_probability
|
|
63
|
+
raise SimulatedFailure.new(probability: probability)
|
|
64
|
+
end
|
|
65
|
+
result
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# =============================================================================
|
|
4
|
+
|
|
5
|
+
# Synthra Latency Behavior
|
|
6
|
+
# =============================================================================
|
|
7
|
+
#
|
|
8
|
+
# Simulates network latency by adding delays to data generation.
|
|
9
|
+
# Useful for testing how applications handle slow responses.
|
|
10
|
+
#
|
|
11
|
+
# @example DSL usage
|
|
12
|
+
# User:
|
|
13
|
+
# @latency 500 # Fixed 500ms delay
|
|
14
|
+
# @latency 100..500ms # Random delay between 100-500ms
|
|
15
|
+
# @latency min: 100, max: 500 # Same as above with named args
|
|
16
|
+
#
|
|
17
|
+
# =============================================================================
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
module Synthra
|
|
21
|
+
module Behaviors
|
|
22
|
+
|
|
23
|
+
# Latency behavior - adds configurable delay to data generation
|
|
24
|
+
#
|
|
25
|
+
# Simulates network latency by sleeping for a specified duration.
|
|
26
|
+
# The delay can be fixed or randomized within a range.
|
|
27
|
+
#
|
|
28
|
+
# @example Fixed delay
|
|
29
|
+
# latency = Latency.new(500, rng)
|
|
30
|
+
# latency.apply(result) # Sleeps for 500ms
|
|
31
|
+
#
|
|
32
|
+
# @example Random delay
|
|
33
|
+
# latency = Latency.new(100..500, rng)
|
|
34
|
+
# latency.apply(result) # Sleeps for random 100-500ms
|
|
35
|
+
#
|
|
36
|
+
|
|
37
|
+
class Latency < Base
|
|
38
|
+
|
|
39
|
+
# Apply latency to the result
|
|
40
|
+
#
|
|
41
|
+
# Calculates the delay and sleeps for that duration.
|
|
42
|
+
# Validates against configured maximum latency limit.
|
|
43
|
+
#
|
|
44
|
+
# @param result [Object] the generated data (passed through unchanged)
|
|
45
|
+
# @param context [Generator::Context, nil] generation context (not used)
|
|
46
|
+
# @return [Object] the result unchanged
|
|
47
|
+
# @raise [LatencyLimitError] if delay exceeds max_latency_ms limit
|
|
48
|
+
#
|
|
49
|
+
# @example
|
|
50
|
+
# latency = Latency.new(500, rng)
|
|
51
|
+
# result = latency.apply({ "id" => "123" })
|
|
52
|
+
# # Sleeps for 500ms, then returns result
|
|
53
|
+
#
|
|
54
|
+
|
|
55
|
+
def apply(result, context = nil)
|
|
56
|
+
return result unless should_apply?
|
|
57
|
+
|
|
58
|
+
delay = calculate_delay
|
|
59
|
+
|
|
60
|
+
# Validate against configured limits
|
|
61
|
+
Synthra.configuration.limits.validate_latency!(delay)
|
|
62
|
+
|
|
63
|
+
# Sleep for delay (convert ms to seconds)
|
|
64
|
+
sleep(delay / 1000.0)
|
|
65
|
+
result
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
private
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
# Calculate the delay in milliseconds
|
|
72
|
+
#
|
|
73
|
+
# Supports multiple value formats:
|
|
74
|
+
# - Range: random value within range
|
|
75
|
+
# - Hash: { min: X, max: Y } for range
|
|
76
|
+
# - Integer: fixed delay
|
|
77
|
+
#
|
|
78
|
+
# @return [Integer] delay in milliseconds
|
|
79
|
+
#
|
|
80
|
+
|
|
81
|
+
def calculate_delay
|
|
82
|
+
case value
|
|
83
|
+
when Range
|
|
84
|
+
|
|
85
|
+
# Random delay within range
|
|
86
|
+
rng ? rng.rand(value) : rand(value)
|
|
87
|
+
when Hash
|
|
88
|
+
|
|
89
|
+
# Named min/max arguments
|
|
90
|
+
min = value[:min] || 0
|
|
91
|
+
max = value[:max] || min
|
|
92
|
+
rng ? rng.int(min, max) : rand(min..max)
|
|
93
|
+
when Integer
|
|
94
|
+
|
|
95
|
+
# Fixed delay
|
|
96
|
+
value
|
|
97
|
+
else
|
|
98
|
+
0
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|