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,83 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# =============================================================================
|
|
4
|
+
|
|
5
|
+
# Synthra Partial Data Behavior
|
|
6
|
+
# =============================================================================
|
|
7
|
+
#
|
|
8
|
+
# Simulates incomplete data by randomly removing fields from generated records.
|
|
9
|
+
# Useful for testing applications that handle partial data gracefully.
|
|
10
|
+
#
|
|
11
|
+
# @example DSL usage
|
|
12
|
+
# User:
|
|
13
|
+
# @partial_data 30% # 30% chance of removing fields
|
|
14
|
+
# name: name
|
|
15
|
+
# email: email
|
|
16
|
+
#
|
|
17
|
+
# @example Field-level usage
|
|
18
|
+
# User:
|
|
19
|
+
# profile: object(Profile) @partial_data 20% # 20% chance of omitting profile
|
|
20
|
+
#
|
|
21
|
+
# =============================================================================
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
module Synthra
|
|
25
|
+
module Behaviors
|
|
26
|
+
|
|
27
|
+
# Partial data behavior - removes some fields
|
|
28
|
+
#
|
|
29
|
+
# Randomly removes fields from generated records to simulate incomplete
|
|
30
|
+
# data. Prefers removing non-critical fields (excludes "id" and "uuid").
|
|
31
|
+
#
|
|
32
|
+
# @example Apply partial data behavior
|
|
33
|
+
# behavior = PartialData.new(30, rng) # 30% probability
|
|
34
|
+
# result = behavior.apply({ "id" => "1", "name" => "John", "email" => "..." })
|
|
35
|
+
# # => { "id" => "1", "name" => "John" } # email removed 30% of the time
|
|
36
|
+
#
|
|
37
|
+
|
|
38
|
+
class PartialData < Base
|
|
39
|
+
|
|
40
|
+
# Apply partial data behavior to result
|
|
41
|
+
#
|
|
42
|
+
# Removes a random subset of fields (approximately 30% of fields)
|
|
43
|
+
# from the result hash. Preserves critical fields like "id" and "uuid".
|
|
44
|
+
#
|
|
45
|
+
# @param result [Hash] generated record to modify
|
|
46
|
+
# @param context [Generator::Context, nil] generation context (not used)
|
|
47
|
+
# @return [Hash] result with some fields removed
|
|
48
|
+
#
|
|
49
|
+
# @example
|
|
50
|
+
# apply({ "id" => "1", "name" => "John", "email" => "..." }, nil)
|
|
51
|
+
# # => { "id" => "1", "name" => "John" } # email removed
|
|
52
|
+
#
|
|
53
|
+
|
|
54
|
+
def apply(result, context = nil)
|
|
55
|
+
return result unless should_apply?
|
|
56
|
+
return result unless result.is_a?(Hash)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
# Remove optional fields first
|
|
60
|
+
keys = result.keys.select do |k|
|
|
61
|
+
|
|
62
|
+
# Prefer removing fields that look optional
|
|
63
|
+
!%w[id uuid].include?(k.to_s)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
return result if keys.empty?
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
# Remove a random subset using RNG for determinism
|
|
70
|
+
count_to_remove = (keys.length * 0.3).ceil
|
|
71
|
+
if @rng
|
|
72
|
+
to_remove = keys.shuffle(random: @rng.instance_variable_get(:@random)).take(count_to_remove)
|
|
73
|
+
else
|
|
74
|
+
to_remove = keys.sample(count_to_remove)
|
|
75
|
+
end
|
|
76
|
+
to_remove.each { |k| result.delete(k) }
|
|
77
|
+
|
|
78
|
+
result
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# =============================================================================
|
|
4
|
+
|
|
5
|
+
# Synthra Randomize Order Behavior
|
|
6
|
+
# =============================================================================
|
|
7
|
+
#
|
|
8
|
+
# Shuffles array elements in generated records to simulate non-deterministic
|
|
9
|
+
# ordering. Useful for testing applications that handle unordered data.
|
|
10
|
+
#
|
|
11
|
+
# @example DSL usage
|
|
12
|
+
# User:
|
|
13
|
+
# @randomize_order 50% # 50% chance of shuffling arrays
|
|
14
|
+
# tags: array(text, 1..5)
|
|
15
|
+
#
|
|
16
|
+
# =============================================================================
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
module Synthra
|
|
20
|
+
module Behaviors
|
|
21
|
+
|
|
22
|
+
# Randomize order behavior - shuffles arrays in result
|
|
23
|
+
#
|
|
24
|
+
# Recursively shuffles all array elements in the generated result to
|
|
25
|
+
# simulate non-deterministic ordering.
|
|
26
|
+
#
|
|
27
|
+
# @example Apply randomize order behavior
|
|
28
|
+
# behavior = RandomizeOrder.new(50, rng) # 50% probability
|
|
29
|
+
# result = behavior.apply({ "tags" => ["a", "b", "c"] })
|
|
30
|
+
# # => { "tags" => ["c", "a", "b"] } # Shuffled 50% of the time
|
|
31
|
+
#
|
|
32
|
+
|
|
33
|
+
class RandomizeOrder < Base
|
|
34
|
+
|
|
35
|
+
# Apply randomize order behavior to result
|
|
36
|
+
#
|
|
37
|
+
# Recursively shuffles all arrays found in the result hash.
|
|
38
|
+
#
|
|
39
|
+
# @param result [Object] generated data to modify
|
|
40
|
+
# @param context [Generator::Context, nil] generation context (not used)
|
|
41
|
+
# @return [Object] result with arrays shuffled
|
|
42
|
+
#
|
|
43
|
+
|
|
44
|
+
def apply(result, context = nil)
|
|
45
|
+
return result unless should_apply?
|
|
46
|
+
|
|
47
|
+
shuffle_arrays(result)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
private
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
# Recursively shuffle arrays in value
|
|
54
|
+
#
|
|
55
|
+
# Traverses the value structure and shuffles all arrays found.
|
|
56
|
+
#
|
|
57
|
+
# @param value [Object] value to process (Hash, Array, or other)
|
|
58
|
+
# @return [Object] value with arrays shuffled
|
|
59
|
+
#
|
|
60
|
+
|
|
61
|
+
def shuffle_arrays(value)
|
|
62
|
+
case value
|
|
63
|
+
when Array
|
|
64
|
+
value.map { |v| shuffle_arrays(v) }.shuffle
|
|
65
|
+
when Hash
|
|
66
|
+
value.transform_values { |v| shuffle_arrays(v) }
|
|
67
|
+
else
|
|
68
|
+
value
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# =============================================================================
|
|
4
|
+
|
|
5
|
+
# Synthra Behaviors Registry
|
|
6
|
+
# =============================================================================
|
|
7
|
+
#
|
|
8
|
+
# The Behaviors::Registry manages registration and lookup of behavior classes.
|
|
9
|
+
# Built-in behaviors are registered automatically at gem load time.
|
|
10
|
+
#
|
|
11
|
+
# @example Register a custom behavior
|
|
12
|
+
# Synthra::Behaviors::Registry.register(:my_behavior, MyBehaviorClass)
|
|
13
|
+
#
|
|
14
|
+
# @example Register with a block
|
|
15
|
+
# Synthra::Behaviors::Registry.register(:log) do |context, result, value|
|
|
16
|
+
# puts "Generated: #{result.inspect}"
|
|
17
|
+
# result
|
|
18
|
+
# end
|
|
19
|
+
#
|
|
20
|
+
# =============================================================================
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
module Synthra
|
|
24
|
+
module Behaviors
|
|
25
|
+
|
|
26
|
+
# Registry for behavior classes
|
|
27
|
+
#
|
|
28
|
+
# Thread-safe registry for storing and retrieving behavior implementations.
|
|
29
|
+
# Supports both class-based and block-based behavior registration.
|
|
30
|
+
#
|
|
31
|
+
# @example Look up a behavior
|
|
32
|
+
# klass = Registry.lookup(:latency)
|
|
33
|
+
# behavior = klass.new(100..500, rng)
|
|
34
|
+
# behavior.apply(result)
|
|
35
|
+
#
|
|
36
|
+
|
|
37
|
+
class Registry
|
|
38
|
+
|
|
39
|
+
# Internal storage for registered behaviors
|
|
40
|
+
# @api private
|
|
41
|
+
@behaviors = {}
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
# Mutex for thread-safe access
|
|
45
|
+
# @api private
|
|
46
|
+
@mutex = Mutex.new
|
|
47
|
+
|
|
48
|
+
class << self
|
|
49
|
+
|
|
50
|
+
# Register a behavior
|
|
51
|
+
#
|
|
52
|
+
# @param name [Symbol, String] behavior name (used with @ prefix in DSL)
|
|
53
|
+
# @param klass [Class, nil] behavior class (must inherit from Base)
|
|
54
|
+
# @yield [context, result, value] block for applying behavior
|
|
55
|
+
# @yieldparam context [Generator::Context] generation context
|
|
56
|
+
# @yieldparam result [Object] generated data
|
|
57
|
+
# @yieldparam value [Object] behavior configuration value
|
|
58
|
+
# @yieldreturn [Object] modified result
|
|
59
|
+
# @return [void]
|
|
60
|
+
# @raise [ArgumentError] if neither class nor block provided
|
|
61
|
+
#
|
|
62
|
+
# @example Register with class
|
|
63
|
+
# Registry.register(:latency, Latency)
|
|
64
|
+
#
|
|
65
|
+
# @example Register with block
|
|
66
|
+
# Registry.register(:log) do |ctx, result, value|
|
|
67
|
+
# puts "Generated: #{result.inspect}"
|
|
68
|
+
# result
|
|
69
|
+
# end
|
|
70
|
+
#
|
|
71
|
+
|
|
72
|
+
def register(name, klass = nil, &block)
|
|
73
|
+
@mutex.synchronize do
|
|
74
|
+
if block_given?
|
|
75
|
+
@behaviors[name.to_sym] = BlockBehavior.new(&block)
|
|
76
|
+
elsif klass
|
|
77
|
+
@behaviors[name.to_sym] = klass
|
|
78
|
+
else
|
|
79
|
+
raise ArgumentError, "Must provide a class or block"
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
# Look up a behavior by name
|
|
86
|
+
#
|
|
87
|
+
# @param name [Symbol, String] behavior name to look up
|
|
88
|
+
# @return [Class] behavior class (can be instantiated with new(value, rng))
|
|
89
|
+
# @raise [ArgumentError] if behavior is not registered
|
|
90
|
+
#
|
|
91
|
+
# @example
|
|
92
|
+
# klass = Registry.lookup(:latency)
|
|
93
|
+
# behavior = klass.new(100..500, rng)
|
|
94
|
+
#
|
|
95
|
+
|
|
96
|
+
def lookup(name)
|
|
97
|
+
@mutex.synchronize do
|
|
98
|
+
@behaviors.fetch(name.to_sym) do
|
|
99
|
+
raise ArgumentError, "Unknown behavior: #{name}"
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
# Check if a behavior is registered
|
|
106
|
+
#
|
|
107
|
+
# @param name [Symbol, String] behavior name to check
|
|
108
|
+
# @return [Boolean] true if registered
|
|
109
|
+
#
|
|
110
|
+
# @example
|
|
111
|
+
# Registry.registered?(:latency) # => true
|
|
112
|
+
# Registry.registered?(:unknown) # => false
|
|
113
|
+
#
|
|
114
|
+
|
|
115
|
+
def registered?(name)
|
|
116
|
+
@mutex.synchronize do
|
|
117
|
+
@behaviors.key?(name.to_sym)
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
# List all registered behavior names
|
|
123
|
+
#
|
|
124
|
+
# @return [Array<Symbol>] list of behavior names
|
|
125
|
+
#
|
|
126
|
+
# @example
|
|
127
|
+
# Registry.names # => [:latency, :failure, :partial_data, ...]
|
|
128
|
+
#
|
|
129
|
+
|
|
130
|
+
def names
|
|
131
|
+
@mutex.synchronize { @behaviors.keys }
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
# Clear all registered behaviors
|
|
136
|
+
#
|
|
137
|
+
# Primarily used for testing. After calling this, you'll need
|
|
138
|
+
# to call register_builtins! to restore built-in behaviors.
|
|
139
|
+
#
|
|
140
|
+
# @return [void]
|
|
141
|
+
#
|
|
142
|
+
|
|
143
|
+
def clear
|
|
144
|
+
@mutex.synchronize { @behaviors.clear }
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
# Register all built-in behaviors
|
|
149
|
+
#
|
|
150
|
+
# Called automatically at gem initialization. Registers the
|
|
151
|
+
# standard behaviors like latency, failure, partial_data, etc.
|
|
152
|
+
#
|
|
153
|
+
# @return [void]
|
|
154
|
+
#
|
|
155
|
+
|
|
156
|
+
def register_builtins!
|
|
157
|
+
register(:latency, Latency)
|
|
158
|
+
register(:failure, Failure)
|
|
159
|
+
register(:partial_data, PartialData)
|
|
160
|
+
register(:close_connection, CloseConnection)
|
|
161
|
+
register(:simulate_error, SimulateError)
|
|
162
|
+
register(:randomize_order, RandomizeOrder)
|
|
163
|
+
register(:deprecated, Deprecated)
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
# Wrapper for block-based behaviors
|
|
169
|
+
#
|
|
170
|
+
# Allows blocks to be used as behaviors. The wrapper creates
|
|
171
|
+
# BlockBehaviorInstance objects when instantiated.
|
|
172
|
+
#
|
|
173
|
+
# @api private
|
|
174
|
+
#
|
|
175
|
+
|
|
176
|
+
class BlockBehavior
|
|
177
|
+
|
|
178
|
+
# Create a new block behavior wrapper
|
|
179
|
+
#
|
|
180
|
+
# @yield [context, result, value] block for applying behavior
|
|
181
|
+
#
|
|
182
|
+
|
|
183
|
+
def initialize(&block)
|
|
184
|
+
@block = block
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
# Create a new instance of this block behavior
|
|
189
|
+
#
|
|
190
|
+
# @param value [Object] behavior configuration
|
|
191
|
+
# @param rng [Generator::RNG, nil] random number generator
|
|
192
|
+
# @return [BlockBehaviorInstance] behavior instance
|
|
193
|
+
#
|
|
194
|
+
|
|
195
|
+
def new(value, rng = nil)
|
|
196
|
+
BlockBehaviorInstance.new(value, rng, @block)
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
# Instance of a block-based behavior
|
|
202
|
+
#
|
|
203
|
+
# Wraps a block in a Base-compatible interface.
|
|
204
|
+
#
|
|
205
|
+
# @api private
|
|
206
|
+
#
|
|
207
|
+
|
|
208
|
+
class BlockBehaviorInstance < Base
|
|
209
|
+
|
|
210
|
+
# Create a new block behavior instance
|
|
211
|
+
#
|
|
212
|
+
# @param value [Object] behavior configuration
|
|
213
|
+
# @param rng [Generator::RNG, nil] random number generator
|
|
214
|
+
# @param block [Proc] the block to call when applying
|
|
215
|
+
#
|
|
216
|
+
|
|
217
|
+
def initialize(value, rng, block)
|
|
218
|
+
super(value, rng)
|
|
219
|
+
@block = block
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
# Apply the block behavior
|
|
224
|
+
#
|
|
225
|
+
# @param result [Object] generated data
|
|
226
|
+
# @param context [Generator::Context, nil] generation context
|
|
227
|
+
# @return [Object] result from block
|
|
228
|
+
#
|
|
229
|
+
|
|
230
|
+
def apply(result, context = nil)
|
|
231
|
+
return result unless should_apply?
|
|
232
|
+
|
|
233
|
+
@block.call(context, result, value)
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
end
|
|
237
|
+
end
|
|
238
|
+
end
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# =============================================================================
|
|
4
|
+
|
|
5
|
+
# Synthra Simulate Error Behavior
|
|
6
|
+
# =============================================================================
|
|
7
|
+
#
|
|
8
|
+
# Simulates HTTP-like errors by raising SimulatedError exception with
|
|
9
|
+
# a specified error code. Useful for testing error handling logic.
|
|
10
|
+
#
|
|
11
|
+
# @example DSL usage
|
|
12
|
+
# User:
|
|
13
|
+
# @simulate_error 500 10% # 10% chance of 500 error
|
|
14
|
+
# @simulate_error 404 5% # 5% chance of 404 error
|
|
15
|
+
# name: name
|
|
16
|
+
#
|
|
17
|
+
# =============================================================================
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
module Synthra
|
|
21
|
+
module Behaviors
|
|
22
|
+
|
|
23
|
+
# Simulate error behavior - raises HTTP-like error
|
|
24
|
+
#
|
|
25
|
+
# Raises SimulatedError exception with a specified HTTP status code
|
|
26
|
+
# when activated, simulating API errors.
|
|
27
|
+
#
|
|
28
|
+
# @example Apply simulate error behavior
|
|
29
|
+
# behavior = SimulateError.new(500, rng) # 500 error
|
|
30
|
+
# begin
|
|
31
|
+
# behavior.apply({ "name" => "John" })
|
|
32
|
+
# rescue SimulatedError => e
|
|
33
|
+
# e.code # => 500
|
|
34
|
+
# end
|
|
35
|
+
#
|
|
36
|
+
|
|
37
|
+
class SimulateError < Base
|
|
38
|
+
|
|
39
|
+
# Apply simulate error behavior
|
|
40
|
+
#
|
|
41
|
+
# Raises SimulatedError if the behavior should be applied based
|
|
42
|
+
# 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 [SimulatedError] if behavior activates
|
|
48
|
+
#
|
|
49
|
+
|
|
50
|
+
def apply(result, context = nil)
|
|
51
|
+
if should_apply?
|
|
52
|
+
code = extract_code
|
|
53
|
+
probability = extract_probability
|
|
54
|
+
raise SimulatedError.new(code: code, probability: probability)
|
|
55
|
+
end
|
|
56
|
+
result
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
private
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
# Extract error code from value
|
|
63
|
+
#
|
|
64
|
+
# Extracts the HTTP status code from the behavior value configuration.
|
|
65
|
+
#
|
|
66
|
+
# @return [Integer] HTTP status code (default: 500)
|
|
67
|
+
#
|
|
68
|
+
|
|
69
|
+
def extract_code
|
|
70
|
+
case value
|
|
71
|
+
when Integer then value
|
|
72
|
+
when Hash then value[:code] || 500
|
|
73
|
+
else 500
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Synthra
|
|
4
|
+
class CLI
|
|
5
|
+
module Commands
|
|
6
|
+
# Base class for all CLI commands
|
|
7
|
+
#
|
|
8
|
+
# Provides common functionality for command execution including
|
|
9
|
+
# option parsing, error handling, and registry management.
|
|
10
|
+
#
|
|
11
|
+
class Base
|
|
12
|
+
# Exit codes
|
|
13
|
+
EXIT_SUCCESS = 0
|
|
14
|
+
EXIT_PARSE_ERROR = 1
|
|
15
|
+
EXIT_RUNTIME_ERROR = 2
|
|
16
|
+
|
|
17
|
+
attr_reader :args, :options
|
|
18
|
+
|
|
19
|
+
# Create a new command instance
|
|
20
|
+
#
|
|
21
|
+
# @param args [Array<String>] command arguments
|
|
22
|
+
# @param options [Hash] parsed options
|
|
23
|
+
#
|
|
24
|
+
def initialize(args, options)
|
|
25
|
+
@args = args
|
|
26
|
+
@options = options
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Execute the command
|
|
30
|
+
#
|
|
31
|
+
# @return [Integer] exit code
|
|
32
|
+
#
|
|
33
|
+
def call
|
|
34
|
+
raise NotImplementedError, "#{self.class} must implement #call"
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
protected
|
|
38
|
+
|
|
39
|
+
# Load schemas from a directory
|
|
40
|
+
#
|
|
41
|
+
# @param registry [Registry] registry to load schemas into
|
|
42
|
+
# @param dir [String] directory path
|
|
43
|
+
# @param validate [Boolean] whether to validate on load
|
|
44
|
+
# @return [void]
|
|
45
|
+
#
|
|
46
|
+
def load_schemas_from_dir(registry, dir, validate: true)
|
|
47
|
+
dir = File.expand_path(dir)
|
|
48
|
+
raise Errno::ENOENT, "Directory not found: #{dir}" unless Dir.exist?(dir)
|
|
49
|
+
|
|
50
|
+
pattern = File.join(dir, "*.dsl")
|
|
51
|
+
files = Dir.glob(pattern)
|
|
52
|
+
|
|
53
|
+
if files.empty?
|
|
54
|
+
$stderr.puts "No .dsl files found in #{dir}"
|
|
55
|
+
return
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
files.each do |f|
|
|
59
|
+
file_path = File.expand_path(f)
|
|
60
|
+
raise ArgumentError, "Invalid file path: #{f}" unless file_path.start_with?(dir)
|
|
61
|
+
registry.load_file(f, validate: validate)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Load from path (file or directory)
|
|
66
|
+
#
|
|
67
|
+
# @param registry [Registry] registry to load into
|
|
68
|
+
# @param path [String] file or directory path
|
|
69
|
+
# @param validate [Boolean] whether to validate
|
|
70
|
+
#
|
|
71
|
+
def load_path(registry, path, validate: false)
|
|
72
|
+
if File.directory?(path)
|
|
73
|
+
load_schemas_from_dir(registry, path, validate: validate)
|
|
74
|
+
else
|
|
75
|
+
registry.load_file(path, validate: validate)
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Print success message
|
|
80
|
+
#
|
|
81
|
+
# @param message [String] message to print
|
|
82
|
+
#
|
|
83
|
+
def success(message)
|
|
84
|
+
puts "✓ #{message}"
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Print error message
|
|
88
|
+
#
|
|
89
|
+
# @param message [String] message to print
|
|
90
|
+
#
|
|
91
|
+
def error(message)
|
|
92
|
+
$stderr.puts "✗ #{message}"
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Print info message
|
|
96
|
+
#
|
|
97
|
+
# @param message [String] message to print
|
|
98
|
+
#
|
|
99
|
+
def info(message)
|
|
100
|
+
puts message
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|