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,605 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# =============================================================================
|
|
4
|
+
|
|
5
|
+
# Synthra Schema Class
|
|
6
|
+
# =============================================================================
|
|
7
|
+
#
|
|
8
|
+
# This file defines the Schema class which represents a complete schema
|
|
9
|
+
# definition parsed from DSL. A Schema contains fields, behaviors, and
|
|
10
|
+
# provides methods for generating fake data.
|
|
11
|
+
#
|
|
12
|
+
# @example DSL definition
|
|
13
|
+
# User:
|
|
14
|
+
# @seed 12345
|
|
15
|
+
# @latency 50..100ms
|
|
16
|
+
# id: uuid
|
|
17
|
+
# name: name
|
|
18
|
+
# email: email
|
|
19
|
+
# age: number(18..80)
|
|
20
|
+
#
|
|
21
|
+
# @example Using a schema
|
|
22
|
+
# schema = Synthra.load("user.dsl")
|
|
23
|
+
# user = schema.generate
|
|
24
|
+
# users = schema.generate_many(100)
|
|
25
|
+
#
|
|
26
|
+
# =============================================================================
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
module Synthra
|
|
30
|
+
|
|
31
|
+
# Represents a complete schema definition
|
|
32
|
+
#
|
|
33
|
+
# A Schema is the parsed representation of a DSL schema definition.
|
|
34
|
+
# It contains all the fields, behaviors, and metadata needed to generate
|
|
35
|
+
# fake data records.
|
|
36
|
+
#
|
|
37
|
+
# Schemas are typically created by loading DSL files through the Registry
|
|
38
|
+
# or Synthra.load, but can also be created programmatically.
|
|
39
|
+
#
|
|
40
|
+
# @example Load from file
|
|
41
|
+
# schema = Synthra.load("schemas/user.dsl")
|
|
42
|
+
#
|
|
43
|
+
# @example Generate single record
|
|
44
|
+
# user = schema.generate
|
|
45
|
+
# # => { "id" => "550e8400-...", "name" => "John Doe", ... }
|
|
46
|
+
#
|
|
47
|
+
# @example Generate with options
|
|
48
|
+
# user = schema.generate(
|
|
49
|
+
# seed: 12345, # Deterministic output
|
|
50
|
+
# mode: :edge, # Edge case values
|
|
51
|
+
# overrides: { "name" => "Custom Name" }
|
|
52
|
+
# )
|
|
53
|
+
#
|
|
54
|
+
# @example Generate multiple records
|
|
55
|
+
# users = schema.generate_many(100, seed: 42)
|
|
56
|
+
# # => [{ "id" => "...", ... }, { "id" => "...", ... }, ...]
|
|
57
|
+
#
|
|
58
|
+
# @example With runtime behaviors
|
|
59
|
+
# slow_schema = schema.with_behavior(:latency, 500..1000)
|
|
60
|
+
# user = slow_schema.generate # Will have 500-1000ms delay
|
|
61
|
+
#
|
|
62
|
+
|
|
63
|
+
class Schema
|
|
64
|
+
|
|
65
|
+
# @!attribute [r] name
|
|
66
|
+
# The schema name (from DSL: "User:")
|
|
67
|
+
# @return [String] schema name
|
|
68
|
+
|
|
69
|
+
attr_reader :name
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
# @!attribute [r] fields
|
|
73
|
+
# All fields defined in this schema
|
|
74
|
+
# @return [Array<Field>] list of Field objects
|
|
75
|
+
|
|
76
|
+
attr_reader :fields
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
# @!attribute [r] behaviors
|
|
80
|
+
# Schema-level behaviors defined in DSL
|
|
81
|
+
# @return [Array<Hash>] list of behavior hashes ({ name:, value: })
|
|
82
|
+
|
|
83
|
+
attr_reader :behaviors
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
# @!attribute [r] metadata
|
|
87
|
+
# Additional metadata (seed, etc.)
|
|
88
|
+
# @return [Hash] metadata key-value pairs
|
|
89
|
+
|
|
90
|
+
attr_reader :metadata
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
# @!attribute [r] version
|
|
94
|
+
# Schema version from @version annotation
|
|
95
|
+
# @return [String, nil] version string or nil if not specified
|
|
96
|
+
|
|
97
|
+
attr_reader :version
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
# @!attribute [r] deprecated
|
|
101
|
+
# Whether this schema is deprecated
|
|
102
|
+
# @return [Boolean] true if schema has @deprecated annotation
|
|
103
|
+
|
|
104
|
+
attr_reader :deprecated
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
# @!attribute [r] deprecation_message
|
|
108
|
+
# Deprecation message if schema is deprecated
|
|
109
|
+
# @return [String, nil] deprecation message or nil
|
|
110
|
+
|
|
111
|
+
attr_reader :deprecation_message
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
# Create a new Schema instance
|
|
115
|
+
#
|
|
116
|
+
# @param name [String] the schema name
|
|
117
|
+
# @param fields [Array<Field>] field definitions
|
|
118
|
+
# @param behaviors [Array<Hash>] schema-level behaviors
|
|
119
|
+
# @param metadata [Hash] additional metadata
|
|
120
|
+
# @param version [String, nil] schema version
|
|
121
|
+
# @param deprecated [Boolean] whether schema is deprecated
|
|
122
|
+
# @param deprecation_message [String, nil] deprecation message
|
|
123
|
+
#
|
|
124
|
+
# @example
|
|
125
|
+
# schema = Schema.new(
|
|
126
|
+
# name: "User",
|
|
127
|
+
# fields: [
|
|
128
|
+
# Field.new(name: "id", type_name: "uuid"),
|
|
129
|
+
# Field.new(name: "name", type_name: "name")
|
|
130
|
+
# ],
|
|
131
|
+
# behaviors: [{ name: :latency, value: 100 }],
|
|
132
|
+
# version: "1.0"
|
|
133
|
+
# )
|
|
134
|
+
#
|
|
135
|
+
|
|
136
|
+
def initialize(name:, fields: [], behaviors: [], metadata: {}, version: nil, deprecated: false, deprecation_message: nil)
|
|
137
|
+
@name = name
|
|
138
|
+
@fields = fields
|
|
139
|
+
@behaviors = behaviors
|
|
140
|
+
@metadata = metadata
|
|
141
|
+
@version = version
|
|
142
|
+
@deprecated = deprecated
|
|
143
|
+
@deprecation_message = deprecation_message
|
|
144
|
+
@runtime_behaviors = []
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
# Check if schema is deprecated
|
|
149
|
+
#
|
|
150
|
+
# @return [Boolean] true if deprecated
|
|
151
|
+
#
|
|
152
|
+
|
|
153
|
+
def deprecated?
|
|
154
|
+
@deprecated
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
# Get schema info hash for display
|
|
159
|
+
#
|
|
160
|
+
# @return [Hash] schema information
|
|
161
|
+
#
|
|
162
|
+
|
|
163
|
+
def info
|
|
164
|
+
{
|
|
165
|
+
name: @name,
|
|
166
|
+
version: @version,
|
|
167
|
+
deprecated: @deprecated,
|
|
168
|
+
deprecation_message: @deprecation_message,
|
|
169
|
+
field_count: @fields.length,
|
|
170
|
+
behavior_count: @behaviors.length,
|
|
171
|
+
fields: @fields.map(&:name)
|
|
172
|
+
}
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
# Get a field by name
|
|
177
|
+
#
|
|
178
|
+
# @param field_name [String, Symbol] the field name to find
|
|
179
|
+
# @return [Field, nil] the field or nil if not found
|
|
180
|
+
#
|
|
181
|
+
# @example
|
|
182
|
+
# email_field = schema.field("email")
|
|
183
|
+
# email_field.type_name # => "email"
|
|
184
|
+
#
|
|
185
|
+
|
|
186
|
+
def field(field_name)
|
|
187
|
+
fields.find { |f| f.name == field_name.to_s }
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
# Get all required (non-optional) fields
|
|
192
|
+
#
|
|
193
|
+
# @return [Array<Field>] fields that are not marked as optional
|
|
194
|
+
#
|
|
195
|
+
# @example
|
|
196
|
+
# schema.required_fields.each do |field|
|
|
197
|
+
# puts "#{field.name} is required"
|
|
198
|
+
# end
|
|
199
|
+
#
|
|
200
|
+
|
|
201
|
+
def required_fields
|
|
202
|
+
fields.reject(&:optional?)
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
# Get all optional fields
|
|
207
|
+
#
|
|
208
|
+
# @return [Array<Field>] fields marked with ? suffix
|
|
209
|
+
#
|
|
210
|
+
# @example
|
|
211
|
+
# schema.optional_fields.each do |field|
|
|
212
|
+
# puts "#{field.name} is optional"
|
|
213
|
+
# end
|
|
214
|
+
#
|
|
215
|
+
|
|
216
|
+
def optional_fields
|
|
217
|
+
fields.select(&:optional?)
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
# Generate a single record
|
|
222
|
+
#
|
|
223
|
+
# Creates one fake data record based on the schema definition.
|
|
224
|
+
# The record is a Hash with field names as keys.
|
|
225
|
+
#
|
|
226
|
+
# @param seed [Integer, nil] random seed for deterministic output
|
|
227
|
+
# @param mode [Symbol, nil] generation mode (:random, :edge, :invalid, :mixed)
|
|
228
|
+
# Defaults to Synthra.configuration.default_mode
|
|
229
|
+
# @param overrides [Hash] field values to use instead of generating
|
|
230
|
+
# @param registry [Registry, nil] registry for resolving cross-schema references
|
|
231
|
+
# @return [Hash] the generated record
|
|
232
|
+
#
|
|
233
|
+
# @example Basic generation
|
|
234
|
+
# user = schema.generate
|
|
235
|
+
# # => { "id" => "550e8400-...", "name" => "John Doe", "email" => "john@..." }
|
|
236
|
+
#
|
|
237
|
+
# @example Deterministic generation
|
|
238
|
+
# user1 = schema.generate(seed: 12345)
|
|
239
|
+
# user2 = schema.generate(seed: 12345)
|
|
240
|
+
# user1 == user2 # => true
|
|
241
|
+
#
|
|
242
|
+
# @example With overrides
|
|
243
|
+
# user = schema.generate(overrides: { "name" => "Test User" })
|
|
244
|
+
# user["name"] # => "Test User"
|
|
245
|
+
#
|
|
246
|
+
# @example Edge case mode
|
|
247
|
+
# user = schema.generate(mode: :edge)
|
|
248
|
+
# # May contain empty strings, nil values, boundary values
|
|
249
|
+
#
|
|
250
|
+
|
|
251
|
+
def generate(seed: nil, mode: nil, overrides: {}, registry: nil, depth: 0, resolver: nil, parent_context: nil, shared_context: nil)
|
|
252
|
+
mode ||= Synthra.configuration.default_mode
|
|
253
|
+
# Inherit shared_context from parent_context if not explicitly provided
|
|
254
|
+
shared_context ||= parent_context&.instance_variable_get(:@shared_context)
|
|
255
|
+
engine = Generator::Engine.new(self, registry: registry, resolver: resolver)
|
|
256
|
+
result = engine.generate(seed: seed, mode: mode, overrides: overrides, depth: depth, parent_context: parent_context, shared_context: shared_context)
|
|
257
|
+
apply_behaviors(result, engine.rng)
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
# Generate multiple records
|
|
262
|
+
#
|
|
263
|
+
# Creates multiple fake data records in a batch. More efficient
|
|
264
|
+
# than calling generate() multiple times.
|
|
265
|
+
#
|
|
266
|
+
# @param count [Integer] number of records to generate
|
|
267
|
+
# @param seed [Integer, nil] random seed for deterministic output
|
|
268
|
+
# @param mode [Symbol, nil] generation mode
|
|
269
|
+
# @param overrides [Hash] field values to override for all records
|
|
270
|
+
# @param registry [Registry, nil] registry for cross-schema references
|
|
271
|
+
# @return [Array<Hash>] array of generated records
|
|
272
|
+
#
|
|
273
|
+
# @example Generate 100 users
|
|
274
|
+
# users = schema.generate_many(100)
|
|
275
|
+
# users.length # => 100
|
|
276
|
+
#
|
|
277
|
+
# @example Deterministic batch
|
|
278
|
+
# users1 = schema.generate_many(10, seed: 42)
|
|
279
|
+
# users2 = schema.generate_many(10, seed: 42)
|
|
280
|
+
# users1 == users2 # => true
|
|
281
|
+
#
|
|
282
|
+
|
|
283
|
+
def generate_many(count, seed: nil, mode: nil, overrides: {}, registry: nil, shared_context: nil, engine: nil, locale: "en", threads: nil)
|
|
284
|
+
mode ||= Synthra.configuration.default_mode
|
|
285
|
+
|
|
286
|
+
# Use native engine if requested and available
|
|
287
|
+
if engine == :native
|
|
288
|
+
return generate_many_native(count, seed: seed, locale: locale, threads: threads)
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
# ponytail: fast_mode once selected a Generator::FastEngine that was never implemented (it
|
|
292
|
+
# crashed). The real high-throughput path is the native Rust engine (engine: :native); the
|
|
293
|
+
# standard engine handles everything else.
|
|
294
|
+
gen = Generator::Engine.new(self, registry: registry)
|
|
295
|
+
|
|
296
|
+
gen.generate_many(count, seed: seed, mode: mode, overrides: overrides, shared_context: shared_context)
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
# Generate multiple records using the Native Rust engine (maximum performance)
|
|
301
|
+
#
|
|
302
|
+
# Uses the Rust native extension with fake-rs for 100-250x faster generation.
|
|
303
|
+
# Requires the native extension to be compiled.
|
|
304
|
+
#
|
|
305
|
+
# @param count [Integer] number of records to generate
|
|
306
|
+
# @param seed [Integer, nil] random seed for deterministic output
|
|
307
|
+
# @param locale [String] locale for data generation (en, fr_fr, de_de, ja_jp, etc.)
|
|
308
|
+
# @param threads [Integer, nil] number of threads (nil/0 = use all cores)
|
|
309
|
+
# @return [Array<Hash>] array of generated records
|
|
310
|
+
# @raise [LoadError] if native extension is not available
|
|
311
|
+
#
|
|
312
|
+
# @example Native engine generation
|
|
313
|
+
# users = schema.generate_many_native(1_000_000, seed: 42)
|
|
314
|
+
#
|
|
315
|
+
# @example With locale
|
|
316
|
+
# users = schema.generate_many_native(100_000, locale: "ja_jp")
|
|
317
|
+
#
|
|
318
|
+
# @example With limited threads
|
|
319
|
+
# users = schema.generate_many_native(100_000, threads: 4)
|
|
320
|
+
#
|
|
321
|
+
|
|
322
|
+
def generate_many_native(count, seed: nil, locale: "en", threads: nil)
|
|
323
|
+
native_engine = NativeEngine.new(self, locale: locale, threads: threads)
|
|
324
|
+
native_engine.generate_many(count, seed: seed)
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
# Generate records directly to a file using the Native Rust engine
|
|
329
|
+
#
|
|
330
|
+
# This is the most efficient method for generating large amounts of data.
|
|
331
|
+
# Uses the Rust native extension for maximum performance.
|
|
332
|
+
#
|
|
333
|
+
# @param count [Integer] number of records to generate
|
|
334
|
+
# @param path [String] output file path
|
|
335
|
+
# @param seed [Integer, nil] random seed for deterministic output
|
|
336
|
+
# @param locale [String] locale for data generation
|
|
337
|
+
# @param format [String] output format: "jsonl", "json", "csv"
|
|
338
|
+
# @param threads [Integer, nil] number of threads (nil/0 = use all cores)
|
|
339
|
+
# @return [Integer] number of records written
|
|
340
|
+
# @raise [LoadError] if native extension is not available
|
|
341
|
+
#
|
|
342
|
+
# @example Generate 10 million records to JSONL
|
|
343
|
+
# schema.generate_to_file(10_000_000, "users.jsonl", format: "jsonl")
|
|
344
|
+
#
|
|
345
|
+
# @example Generate CSV with seed
|
|
346
|
+
# schema.generate_to_file(1_000_000, "users.csv", seed: 42, format: "csv")
|
|
347
|
+
#
|
|
348
|
+
# @example Generate with limited threads (leaves other cores free)
|
|
349
|
+
# schema.generate_to_file(10_000_000, "users.jsonl", threads: 4)
|
|
350
|
+
#
|
|
351
|
+
|
|
352
|
+
def generate_to_file(count, path, seed: nil, locale: "en", format: "jsonl", threads: nil)
|
|
353
|
+
native_engine = NativeEngine.new(self, locale: locale, threads: threads)
|
|
354
|
+
native_engine.generate_to_file(count, path, seed: seed, format: format)
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
# Generate multiple records using FastEngine (high performance)
|
|
359
|
+
#
|
|
360
|
+
# Uses the optimized FastEngine for 2-3x faster generation.
|
|
361
|
+
# NOT thread-safe - use only in single-threaded contexts.
|
|
362
|
+
#
|
|
363
|
+
# @param count [Integer] number of records to generate
|
|
364
|
+
# @param seed [Integer, nil] random seed for deterministic output
|
|
365
|
+
# @param mode [Symbol, nil] generation mode
|
|
366
|
+
# @param overrides [Hash] field values to override for all records
|
|
367
|
+
# @param registry [Registry, nil] registry for cross-schema references
|
|
368
|
+
# @return [Array<Hash>] array of generated records
|
|
369
|
+
#
|
|
370
|
+
# @example Fast batch generation
|
|
371
|
+
# users = schema.generate_many_fast(100_000, seed: 42)
|
|
372
|
+
#
|
|
373
|
+
|
|
374
|
+
def generate_many_fast(count, seed: nil, mode: nil, overrides: {}, registry: nil)
|
|
375
|
+
mode ||= Synthra.configuration.default_mode
|
|
376
|
+
# ponytail: kept as an alias for the standard engine — FastEngine was never implemented.
|
|
377
|
+
engine = Generator::Engine.new(self, registry: registry)
|
|
378
|
+
engine.generate_many(count, seed: seed, mode: mode, overrides: overrides)
|
|
379
|
+
end
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
# Generate multiple records as a lazy stream (Enumerator)
|
|
383
|
+
#
|
|
384
|
+
# Returns an Enumerator that generates records on-demand. This is
|
|
385
|
+
# memory-efficient for large batches as records are not all held
|
|
386
|
+
# in memory at once.
|
|
387
|
+
#
|
|
388
|
+
# @param count [Integer] total number of records to generate
|
|
389
|
+
# @param seed [Integer, nil] random seed for deterministic output
|
|
390
|
+
# @param mode [Symbol, nil] generation mode
|
|
391
|
+
# @param overrides [Hash] field values to override for all records
|
|
392
|
+
# @param registry [Registry, nil] registry for cross-schema references
|
|
393
|
+
# @return [Enumerator] lazy enumerator of records
|
|
394
|
+
#
|
|
395
|
+
# @example Stream processing for large batches
|
|
396
|
+
# schema.generate_many_stream(10000, seed: 42).each do |record|
|
|
397
|
+
# process(record) # Records generated one at a time
|
|
398
|
+
# end
|
|
399
|
+
#
|
|
400
|
+
# @note For small batches (< 1000 records), generate_many is more efficient.
|
|
401
|
+
# Use generate_many_stream for large batches to reduce memory usage.
|
|
402
|
+
#
|
|
403
|
+
|
|
404
|
+
def generate_many_stream(count, seed: nil, mode: nil, overrides: {}, registry: nil, shared_context: nil)
|
|
405
|
+
mode ||= Synthra.configuration.default_mode
|
|
406
|
+
|
|
407
|
+
# ponytail: fast_mode's FastEngine was never implemented; use the standard engine.
|
|
408
|
+
engine = Generator::Engine.new(self, registry: registry)
|
|
409
|
+
|
|
410
|
+
engine.generate_many_stream(count, seed: seed, mode: mode, overrides: overrides, shared_context: shared_context)
|
|
411
|
+
end
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
# Generate stream using FastEngine (high performance)
|
|
415
|
+
#
|
|
416
|
+
# Uses the optimized FastEngine for 2-3x faster streaming.
|
|
417
|
+
# NOT thread-safe - use only in single-threaded contexts.
|
|
418
|
+
#
|
|
419
|
+
# @param count [Integer] number of records to generate
|
|
420
|
+
# @param seed [Integer, nil] random seed for deterministic output
|
|
421
|
+
# @param mode [Symbol, nil] generation mode
|
|
422
|
+
# @param overrides [Hash] field values to override for all records
|
|
423
|
+
# @param registry [Registry, nil] registry for cross-schema references
|
|
424
|
+
# @return [Enumerator] lazy enumerator of records
|
|
425
|
+
#
|
|
426
|
+
# @example Fast streaming
|
|
427
|
+
# schema.generate_stream_fast(1_000_000, seed: 42).each do |record|
|
|
428
|
+
# process(record)
|
|
429
|
+
# end
|
|
430
|
+
#
|
|
431
|
+
|
|
432
|
+
def generate_stream_fast(count, seed: nil, mode: nil, overrides: {}, registry: nil)
|
|
433
|
+
mode ||= Synthra.configuration.default_mode
|
|
434
|
+
# ponytail: kept as an alias for the standard engine — FastEngine was never implemented.
|
|
435
|
+
engine = Generator::Engine.new(self, registry: registry)
|
|
436
|
+
engine.generate_many_stream(count, seed: seed, mode: mode, overrides: overrides)
|
|
437
|
+
end
|
|
438
|
+
|
|
439
|
+
|
|
440
|
+
# Generate a record as a JSON string
|
|
441
|
+
#
|
|
442
|
+
# Convenience method that generates a record and serializes it to JSON.
|
|
443
|
+
#
|
|
444
|
+
# @param pretty [Boolean] if true, format with indentation
|
|
445
|
+
# @param options [Hash] additional options passed to generate()
|
|
446
|
+
# @return [String] JSON string
|
|
447
|
+
#
|
|
448
|
+
# @example Minified JSON
|
|
449
|
+
# json = schema.generate_json
|
|
450
|
+
# # => '{"id":"550e8400-...","name":"John Doe"}'
|
|
451
|
+
#
|
|
452
|
+
# @example Pretty-printed JSON
|
|
453
|
+
# json = schema.generate_json(pretty: true)
|
|
454
|
+
# # => '{\n "id": "550e8400-...",\n "name": "John Doe"\n}'
|
|
455
|
+
#
|
|
456
|
+
|
|
457
|
+
def generate_json(pretty: false, **options)
|
|
458
|
+
result = generate(**options)
|
|
459
|
+
if pretty
|
|
460
|
+
JSON.pretty_generate(result)
|
|
461
|
+
else
|
|
462
|
+
JSON.generate(result)
|
|
463
|
+
end
|
|
464
|
+
end
|
|
465
|
+
|
|
466
|
+
|
|
467
|
+
# Generate records as a lazy stream (Enumerator)
|
|
468
|
+
#
|
|
469
|
+
# Returns an Enumerator that generates records on-demand. This is
|
|
470
|
+
# memory-efficient for large batches as records are not all held
|
|
471
|
+
# in memory at once.
|
|
472
|
+
#
|
|
473
|
+
# @param count [Integer] total number of records to generate
|
|
474
|
+
# @param options [Hash] generation options (seed, mode, etc.)
|
|
475
|
+
# @return [Enumerator] lazy enumerator of records
|
|
476
|
+
#
|
|
477
|
+
# @example Stream processing
|
|
478
|
+
# schema.generate_stream(count: 10000).each do |record|
|
|
479
|
+
# process(record) # Records generated one at a time
|
|
480
|
+
# end
|
|
481
|
+
#
|
|
482
|
+
# @example Convert to NDJSON
|
|
483
|
+
# schema.generate_stream(count: 1000).each do |record|
|
|
484
|
+
# puts record.to_json
|
|
485
|
+
# end
|
|
486
|
+
#
|
|
487
|
+
|
|
488
|
+
def generate_stream(count:, **options)
|
|
489
|
+
Generator::Streamer.new(self, **options).generate_stream(count: count)
|
|
490
|
+
end
|
|
491
|
+
|
|
492
|
+
|
|
493
|
+
# Create a new schema with an additional runtime behavior
|
|
494
|
+
#
|
|
495
|
+
# Returns a new Schema instance with the behavior added. The original
|
|
496
|
+
# schema is not modified.
|
|
497
|
+
#
|
|
498
|
+
# @param name [Symbol, String] behavior name (:latency, :failure, etc.)
|
|
499
|
+
# @param value [Object] behavior configuration value
|
|
500
|
+
# @return [Schema] new schema with the behavior added
|
|
501
|
+
#
|
|
502
|
+
# @example Add latency behavior
|
|
503
|
+
# slow_schema = schema.with_behavior(:latency, 500..1000)
|
|
504
|
+
# slow_schema.generate # Will have 500-1000ms delay
|
|
505
|
+
#
|
|
506
|
+
# @example Chain multiple behaviors
|
|
507
|
+
# test_schema = schema
|
|
508
|
+
# .with_behavior(:latency, 100)
|
|
509
|
+
# .with_behavior(:failure, 10)
|
|
510
|
+
#
|
|
511
|
+
|
|
512
|
+
def with_behavior(name, value)
|
|
513
|
+
new_schema = dup
|
|
514
|
+
new_schema.instance_variable_set(
|
|
515
|
+
:@runtime_behaviors,
|
|
516
|
+
@runtime_behaviors + [{ name: name.to_sym, value: value }]
|
|
517
|
+
)
|
|
518
|
+
new_schema
|
|
519
|
+
end
|
|
520
|
+
|
|
521
|
+
|
|
522
|
+
# Get all behaviors (defined in DSL + added at runtime)
|
|
523
|
+
#
|
|
524
|
+
# @return [Array<Hash>] all behavior configurations
|
|
525
|
+
#
|
|
526
|
+
# @example
|
|
527
|
+
# schema.all_behaviors
|
|
528
|
+
# # => [{ name: :latency, value: 100 }, { name: :seed, value: 12345 }]
|
|
529
|
+
#
|
|
530
|
+
|
|
531
|
+
def all_behaviors
|
|
532
|
+
behaviors + @runtime_behaviors
|
|
533
|
+
end
|
|
534
|
+
|
|
535
|
+
|
|
536
|
+
# Check if schema has a specific behavior
|
|
537
|
+
#
|
|
538
|
+
# @param name [Symbol, String] behavior name to check
|
|
539
|
+
# @return [Boolean] true if the behavior is defined
|
|
540
|
+
#
|
|
541
|
+
# @example
|
|
542
|
+
# schema.has_behavior?(:latency) # => true
|
|
543
|
+
# schema.has_behavior?(:failure) # => false
|
|
544
|
+
#
|
|
545
|
+
|
|
546
|
+
def has_behavior?(name)
|
|
547
|
+
all_behaviors.any? { |b| b[:name] == name.to_sym }
|
|
548
|
+
end
|
|
549
|
+
|
|
550
|
+
|
|
551
|
+
# Get a specific behavior configuration
|
|
552
|
+
#
|
|
553
|
+
# @param name [Symbol, String] behavior name
|
|
554
|
+
# @return [Hash, nil] the behavior hash or nil if not found
|
|
555
|
+
#
|
|
556
|
+
# @example
|
|
557
|
+
# latency = schema.behavior(:latency)
|
|
558
|
+
# latency[:value] # => 100..500
|
|
559
|
+
#
|
|
560
|
+
|
|
561
|
+
def behavior(name)
|
|
562
|
+
all_behaviors.find { |b| b[:name] == name.to_sym }
|
|
563
|
+
end
|
|
564
|
+
|
|
565
|
+
|
|
566
|
+
# Get the seed from metadata if specified
|
|
567
|
+
#
|
|
568
|
+
# @return [Integer, nil] the seed value or nil
|
|
569
|
+
#
|
|
570
|
+
# @example
|
|
571
|
+
# # DSL: @seed 12345
|
|
572
|
+
# schema.seed # => 12345
|
|
573
|
+
#
|
|
574
|
+
|
|
575
|
+
def seed
|
|
576
|
+
metadata[:seed]
|
|
577
|
+
end
|
|
578
|
+
|
|
579
|
+
|
|
580
|
+
# String representation
|
|
581
|
+
#
|
|
582
|
+
# @return [String] summary of the schema
|
|
583
|
+
#
|
|
584
|
+
|
|
585
|
+
def to_s
|
|
586
|
+
"#<Schema #{name} fields=#{fields.length} behaviors=#{all_behaviors.length}>"
|
|
587
|
+
end
|
|
588
|
+
|
|
589
|
+
alias inspect to_s
|
|
590
|
+
|
|
591
|
+
private
|
|
592
|
+
|
|
593
|
+
|
|
594
|
+
# Apply schema-level behaviors to generated result
|
|
595
|
+
#
|
|
596
|
+
# @param result [Hash] the generated record
|
|
597
|
+
# @param rng [Generator::RNG] random number generator
|
|
598
|
+
# @return [Hash] the result after applying behaviors
|
|
599
|
+
#
|
|
600
|
+
|
|
601
|
+
def apply_behaviors(result, rng)
|
|
602
|
+
Behaviors::Applicator.new(self, rng).apply_schema_behaviors(result)
|
|
603
|
+
end
|
|
604
|
+
end
|
|
605
|
+
end
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Synthra
|
|
4
|
+
# Schema Inheritance Support
|
|
5
|
+
#
|
|
6
|
+
# Allows schemas to inherit fields from parent schemas using the < operator.
|
|
7
|
+
#
|
|
8
|
+
# @example DSL syntax
|
|
9
|
+
# BaseUser:
|
|
10
|
+
# id: uuid
|
|
11
|
+
# created_at: timestamp
|
|
12
|
+
#
|
|
13
|
+
# AdminUser < BaseUser:
|
|
14
|
+
# role: const("admin")
|
|
15
|
+
# permissions: array(text, 1..5)
|
|
16
|
+
#
|
|
17
|
+
module SchemaInheritance
|
|
18
|
+
# Resolve inheritance for a schema node
|
|
19
|
+
#
|
|
20
|
+
# @param schema_node [AST::SchemaNode] the schema node to process
|
|
21
|
+
# @param registry [Registry] the registry containing parent schemas
|
|
22
|
+
# @return [AST::SchemaNode] schema node with inherited fields
|
|
23
|
+
#
|
|
24
|
+
def self.resolve(schema_node, registry)
|
|
25
|
+
return schema_node unless schema_node.parent_name
|
|
26
|
+
|
|
27
|
+
parent_schema = registry.schema(schema_node.parent_name)
|
|
28
|
+
|
|
29
|
+
# Merge parent fields with child fields (child fields override parent)
|
|
30
|
+
parent_field_names = parent_schema.fields.map(&:name)
|
|
31
|
+
child_field_names = schema_node.fields.map { |f| f.respond_to?(:name) ? f.name : f[:name] }
|
|
32
|
+
|
|
33
|
+
# Get parent fields that aren't overridden
|
|
34
|
+
inherited_fields = parent_schema.fields.reject do |f|
|
|
35
|
+
child_field_names.include?(f.name)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Create new schema node with merged fields
|
|
39
|
+
# Parent fields come first, then child fields
|
|
40
|
+
merged_fields = []
|
|
41
|
+
|
|
42
|
+
# Add inherited fields
|
|
43
|
+
inherited_fields.each do |field|
|
|
44
|
+
merged_fields << convert_field_to_node(field)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Add child's own fields
|
|
48
|
+
schema_node.fields.each do |field|
|
|
49
|
+
merged_fields << field
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Create new schema node with merged fields
|
|
53
|
+
Parser::AST::SchemaNode.new(
|
|
54
|
+
name: schema_node.name,
|
|
55
|
+
fields: merged_fields,
|
|
56
|
+
behaviors: schema_node.behaviors,
|
|
57
|
+
parent_name: schema_node.parent_name,
|
|
58
|
+
line: schema_node.line
|
|
59
|
+
)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Convert a Field object to a field node (for inheritance)
|
|
63
|
+
#
|
|
64
|
+
# @param field [Field] the field to convert
|
|
65
|
+
# @return [Parser::AST::FieldNode] the field node
|
|
66
|
+
#
|
|
67
|
+
def self.convert_field_to_node(field)
|
|
68
|
+
Parser::AST::FieldNode.new(
|
|
69
|
+
name: field.name,
|
|
70
|
+
type_node: Parser::AST::TypeNode.new(
|
|
71
|
+
name: field.type_name,
|
|
72
|
+
arguments: field.type_args,
|
|
73
|
+
nullable: field.nullable?,
|
|
74
|
+
line: 0
|
|
75
|
+
),
|
|
76
|
+
optional: field.optional?,
|
|
77
|
+
condition: nil,
|
|
78
|
+
behaviors: [],
|
|
79
|
+
line: 0
|
|
80
|
+
)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Parse inheritance syntax from schema name
|
|
84
|
+
#
|
|
85
|
+
# @param name [String] the schema name potentially with inheritance
|
|
86
|
+
# @return [Array<String, String>] [schema_name, parent_name or nil]
|
|
87
|
+
#
|
|
88
|
+
# @example
|
|
89
|
+
# parse_inheritance("AdminUser < BaseUser")
|
|
90
|
+
# # => ["AdminUser", "BaseUser"]
|
|
91
|
+
#
|
|
92
|
+
# parse_inheritance("User")
|
|
93
|
+
# # => ["User", nil]
|
|
94
|
+
#
|
|
95
|
+
def self.parse_inheritance(name)
|
|
96
|
+
if name.include?("<")
|
|
97
|
+
parts = name.split("<").map(&:strip)
|
|
98
|
+
[parts[0], parts[1]]
|
|
99
|
+
else
|
|
100
|
+
[name, nil]
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|