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,790 @@
|
|
|
1
|
+
# Complex Types Reference
|
|
2
|
+
|
|
3
|
+
Arrays, objects, references, cross-schema linking, and advanced data generation.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
- [Array Type](#array-type)
|
|
8
|
+
- [Object Type](#object-type)
|
|
9
|
+
- [Reference Type](#reference-type)
|
|
10
|
+
- [Copy Type](#copy-type)
|
|
11
|
+
- [Shared Type](#shared-type)
|
|
12
|
+
- [Map By Field Type](#map-by-field-type)
|
|
13
|
+
- [One Of Type](#one-of-type)
|
|
14
|
+
- [Const Type](#const-type)
|
|
15
|
+
- [Custom Type](#custom-type)
|
|
16
|
+
- [Sequence Types](#sequence-types)
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Array Type
|
|
21
|
+
|
|
22
|
+
### Basic Arrays
|
|
23
|
+
|
|
24
|
+
```dsl
|
|
25
|
+
Order:
|
|
26
|
+
# Array of strings
|
|
27
|
+
tags: array(text, 1..5)
|
|
28
|
+
|
|
29
|
+
# Array of numbers
|
|
30
|
+
scores: array(number(1..100), 5)
|
|
31
|
+
|
|
32
|
+
# Array of schema references
|
|
33
|
+
items: array(OrderItem, 1..10)
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Syntax Options
|
|
37
|
+
|
|
38
|
+
```dsl
|
|
39
|
+
# Positional arguments: element type, size
|
|
40
|
+
tags: array(text, 3) # Exactly 3 elements
|
|
41
|
+
tags: array(text, 1..5) # 1 to 5 elements
|
|
42
|
+
|
|
43
|
+
# Named arguments
|
|
44
|
+
tags: array(element: text, size: 3)
|
|
45
|
+
tags: array(element: text, size: 1..5)
|
|
46
|
+
|
|
47
|
+
# Complex element types
|
|
48
|
+
addresses: array(Address, 1..3)
|
|
49
|
+
emails: array(email, 0..5)
|
|
50
|
+
prices: array(number(0.99..99.99), 1..10)
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Array Arguments
|
|
54
|
+
|
|
55
|
+
| Argument | Type | Description |
|
|
56
|
+
|----------|------|-------------|
|
|
57
|
+
| `element` | Type | Element type or schema reference |
|
|
58
|
+
| `size` | Integer or Range | Number of elements |
|
|
59
|
+
| `unique` | Boolean | Ensure unique elements |
|
|
60
|
+
| `min` | Integer | Minimum size (alternative to range) |
|
|
61
|
+
| `max` | Integer | Maximum size (alternative to range) |
|
|
62
|
+
|
|
63
|
+
### Array Examples
|
|
64
|
+
|
|
65
|
+
```dsl
|
|
66
|
+
Product:
|
|
67
|
+
# 1-5 tags, each 3-20 characters
|
|
68
|
+
tags: array(text(3..20), 1..5)
|
|
69
|
+
|
|
70
|
+
# Exactly 3 images
|
|
71
|
+
images: array(url, 3)
|
|
72
|
+
|
|
73
|
+
# 0 or more reviews (optional array)
|
|
74
|
+
reviews: array(Review, 0..10)
|
|
75
|
+
|
|
76
|
+
# Unique categories
|
|
77
|
+
categories: array(enum(tech, food, fashion), size: 1..3, unique: true)
|
|
78
|
+
|
|
79
|
+
Review:
|
|
80
|
+
rating: number(1..5)
|
|
81
|
+
comment: text(10..500)
|
|
82
|
+
author: name
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Empty Arrays
|
|
86
|
+
|
|
87
|
+
```dsl
|
|
88
|
+
# May be empty (0 elements)
|
|
89
|
+
optional_items: array(Item, 0..5)
|
|
90
|
+
|
|
91
|
+
# Always has at least one
|
|
92
|
+
required_items: array(Item, 1..5)
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## Object Type
|
|
98
|
+
|
|
99
|
+
### Inline Objects
|
|
100
|
+
|
|
101
|
+
For simple, one-off nested structures:
|
|
102
|
+
|
|
103
|
+
```dsl
|
|
104
|
+
User:
|
|
105
|
+
metadata: object
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Generates a generic object with random keys/values.
|
|
109
|
+
|
|
110
|
+
### Schema References
|
|
111
|
+
|
|
112
|
+
For structured nested objects, reference another schema:
|
|
113
|
+
|
|
114
|
+
```dsl
|
|
115
|
+
User:
|
|
116
|
+
id: uuid
|
|
117
|
+
name: name
|
|
118
|
+
address: Address # Embeds full Address schema
|
|
119
|
+
|
|
120
|
+
Address:
|
|
121
|
+
street: text(10..100)
|
|
122
|
+
city: name
|
|
123
|
+
country: country_code
|
|
124
|
+
postal_code: postal_code
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Nested Schema Example
|
|
128
|
+
|
|
129
|
+
```ruby
|
|
130
|
+
schema = FakeDataDSL.load('user.dsl')
|
|
131
|
+
user = schema.generate
|
|
132
|
+
|
|
133
|
+
# Result:
|
|
134
|
+
# {
|
|
135
|
+
# "id" => "550e8400-...",
|
|
136
|
+
# "name" => "John Smith",
|
|
137
|
+
# "address" => {
|
|
138
|
+
# "street" => "123 Main Street",
|
|
139
|
+
# "city" => "Springfield",
|
|
140
|
+
# "country" => "US",
|
|
141
|
+
# "postal_code" => "12345"
|
|
142
|
+
# }
|
|
143
|
+
# }
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
## Reference Type
|
|
149
|
+
|
|
150
|
+
### What is a Reference?
|
|
151
|
+
|
|
152
|
+
References let you link schemas together without embedding the full object.
|
|
153
|
+
|
|
154
|
+
### Ref() Syntax
|
|
155
|
+
|
|
156
|
+
```dsl
|
|
157
|
+
# Reference a specific field from another schema
|
|
158
|
+
Order:
|
|
159
|
+
id: uuid
|
|
160
|
+
user_id: Ref(User.id) # Gets a User id
|
|
161
|
+
shipping_address_id: Ref(Address.id) # Gets an Address id
|
|
162
|
+
|
|
163
|
+
User:
|
|
164
|
+
id: uuid
|
|
165
|
+
name: name
|
|
166
|
+
|
|
167
|
+
Address:
|
|
168
|
+
id: uuid
|
|
169
|
+
street: text
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### How References Work
|
|
173
|
+
|
|
174
|
+
1. **Same Batch**: References resolve to values generated in the same batch
|
|
175
|
+
2. **Registry Required**: Cross-schema references need a Registry
|
|
176
|
+
|
|
177
|
+
```ruby
|
|
178
|
+
registry = FakeDataDSL::Registry.new
|
|
179
|
+
registry.load_file('schemas/user.dsl')
|
|
180
|
+
registry.load_file('schemas/order.dsl')
|
|
181
|
+
|
|
182
|
+
# Generate with registry for reference resolution
|
|
183
|
+
order = registry.schema('Order').generate(registry: registry)
|
|
184
|
+
# order["user_id"] will be a valid UUID
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### Reference Examples
|
|
188
|
+
|
|
189
|
+
```dsl
|
|
190
|
+
# E-commerce system
|
|
191
|
+
Customer:
|
|
192
|
+
id: uuid
|
|
193
|
+
email: email
|
|
194
|
+
|
|
195
|
+
Order:
|
|
196
|
+
id: uuid
|
|
197
|
+
customer_id: Ref(Customer.id)
|
|
198
|
+
items: array(OrderItem, 1..10)
|
|
199
|
+
|
|
200
|
+
OrderItem:
|
|
201
|
+
id: uuid
|
|
202
|
+
order_id: Ref(Order.id)
|
|
203
|
+
product_id: Ref(Product.id)
|
|
204
|
+
quantity: number(1..5)
|
|
205
|
+
|
|
206
|
+
Product:
|
|
207
|
+
id: uuid
|
|
208
|
+
name: text(10..50)
|
|
209
|
+
price: money
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
### Reference vs Embed
|
|
213
|
+
|
|
214
|
+
| Approach | Use Case | Example |
|
|
215
|
+
|----------|----------|---------|
|
|
216
|
+
| **Embed** | Full object needed | `address: Address` |
|
|
217
|
+
| **Ref** | Just ID/foreign key | `user_id: Ref(User.id)` |
|
|
218
|
+
|
|
219
|
+
```dsl
|
|
220
|
+
# Embed - includes full address
|
|
221
|
+
User:
|
|
222
|
+
address: Address
|
|
223
|
+
|
|
224
|
+
# Generates:
|
|
225
|
+
# { "address": { "street": "...", "city": "...", ... } }
|
|
226
|
+
|
|
227
|
+
# Reference - just the ID
|
|
228
|
+
Order:
|
|
229
|
+
shipping_address_id: Ref(Address.id)
|
|
230
|
+
|
|
231
|
+
# Generates:
|
|
232
|
+
# { "shipping_address_id": "550e8400-..." }
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### ⚠️ Important: Ref() vs copy() - Know the Difference!
|
|
236
|
+
|
|
237
|
+
`Ref()` and `copy()` look similar but behave **very differently**:
|
|
238
|
+
|
|
239
|
+
| Feature | `Ref(Schema.field)` | `copy("path.to.field")` |
|
|
240
|
+
|---------|---------------------|-------------------------|
|
|
241
|
+
| **What it does** | Generates a NEW value | Copies an EXISTING value |
|
|
242
|
+
| **Source** | Generates from another schema's field type | References already-generated field in current context |
|
|
243
|
+
| **Use case** | Foreign key that doesn't need to match existing data | Link IDs across nested objects in same record |
|
|
244
|
+
| **Requires** | Registry for cross-schema resolution | Field must be defined BEFORE the copy |
|
|
245
|
+
|
|
246
|
+
#### Example: The Wrong Way vs The Right Way
|
|
247
|
+
|
|
248
|
+
```dsl
|
|
249
|
+
# ❌ WRONG - Using Ref() when you need copy()
|
|
250
|
+
TwitterDM:
|
|
251
|
+
sender: TwitterUser
|
|
252
|
+
sender_id: Ref(TwitterUser.id) # Generates a NEW ID, different from sender.id!
|
|
253
|
+
|
|
254
|
+
# ✅ CORRECT - Using copy() to link IDs
|
|
255
|
+
TwitterDM:
|
|
256
|
+
sender: TwitterUser
|
|
257
|
+
sender_id: copy("sender.id") # Uses the SAME ID as sender.id
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
#### When to Use Each
|
|
261
|
+
|
|
262
|
+
| Scenario | Use | Example |
|
|
263
|
+
|----------|-----|---------|
|
|
264
|
+
| Foreign key to another table | `Ref()` | `customer_id: Ref(Customer.id)` |
|
|
265
|
+
| Copying ID from nested object | `copy()` | `sender_id: copy("sender.id")` |
|
|
266
|
+
| Dynamic key linking with map_by_field | `copy()` | `for_user_id: copy("users.recipient.id")` |
|
|
267
|
+
| Independent ID generation | `Ref()` | `correlation_id: Ref(Request.id)` |
|
|
268
|
+
|
|
269
|
+
---
|
|
270
|
+
|
|
271
|
+
## Copy Type
|
|
272
|
+
|
|
273
|
+
### What is Copy?
|
|
274
|
+
|
|
275
|
+
`copy()` references a value from another field in the same generation context. This enables linking related IDs across nested schemas.
|
|
276
|
+
|
|
277
|
+
### Basic Usage
|
|
278
|
+
|
|
279
|
+
```dsl
|
|
280
|
+
Order:
|
|
281
|
+
user: User
|
|
282
|
+
order_user_id: copy("user.id") # Copies the generated user's ID
|
|
283
|
+
|
|
284
|
+
User:
|
|
285
|
+
id: uuid
|
|
286
|
+
name: full_name
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
### Path Syntax
|
|
290
|
+
|
|
291
|
+
Paths use dot notation to traverse nested structures:
|
|
292
|
+
|
|
293
|
+
```dsl
|
|
294
|
+
TwitterDM:
|
|
295
|
+
users: map_by_field("id", sender: TwitterUser, recipient: TwitterUser)
|
|
296
|
+
sender_id: copy("users.sender.id") # Gets sender's ID
|
|
297
|
+
recipient_id: copy("users.recipient.id") # Gets recipient's ID
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
### Important: Field Order
|
|
301
|
+
|
|
302
|
+
`copy()` can only reference fields that are **defined before** the copy field:
|
|
303
|
+
|
|
304
|
+
```dsl
|
|
305
|
+
# ✅ CORRECT - user defined before copy
|
|
306
|
+
Order:
|
|
307
|
+
user: User
|
|
308
|
+
user_id: copy("user.id")
|
|
309
|
+
|
|
310
|
+
# ❌ WRONG - user_id defined before user
|
|
311
|
+
Order:
|
|
312
|
+
user_id: copy("user.id") # Will return nil!
|
|
313
|
+
user: User
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
### Path Validation
|
|
317
|
+
|
|
318
|
+
FakeDataDSL validates `copy()` paths at load time by default:
|
|
319
|
+
|
|
320
|
+
```ruby
|
|
321
|
+
registry = FakeDataDSL::Registry.new
|
|
322
|
+
registry.load_string(<<~DSL)
|
|
323
|
+
Order:
|
|
324
|
+
user: User
|
|
325
|
+
user_id: copy("users.id") # Typo: "users" instead of "user"
|
|
326
|
+
DSL
|
|
327
|
+
# Raises: FakeDataDSL::Errors::PathError
|
|
328
|
+
# Path 'users.id' not found in schema
|
|
329
|
+
# Did you mean: user.id?
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
To disable validation (not recommended):
|
|
333
|
+
|
|
334
|
+
```ruby
|
|
335
|
+
registry.load_string(dsl, validate: false)
|
|
336
|
+
# OR globally:
|
|
337
|
+
FakeDataDSL.configure { |c| c.validate_paths_on_load = false }
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
---
|
|
341
|
+
|
|
342
|
+
## Shared Type
|
|
343
|
+
|
|
344
|
+
### What is Shared?
|
|
345
|
+
|
|
346
|
+
`shared()` generates a value once and reuses it across all records in a batch when using `generate_many()`. This is essential for simulating realistic data where certain values should be consistent.
|
|
347
|
+
|
|
348
|
+
### ⚠️ Important: Only Works with `generate_many()`
|
|
349
|
+
|
|
350
|
+
**`shared()` requires `generate_many()` to function correctly.**
|
|
351
|
+
|
|
352
|
+
```ruby
|
|
353
|
+
# ✅ CORRECT - shared values work with generate_many()
|
|
354
|
+
webhooks = schema.generate_many(10, registry: registry)
|
|
355
|
+
# All 10 webhooks have the SAME recipient
|
|
356
|
+
|
|
357
|
+
# ⚠️ WARNING - shared values don't work with generate()
|
|
358
|
+
webhook1 = schema.generate(registry: registry)
|
|
359
|
+
webhook2 = schema.generate(registry: registry)
|
|
360
|
+
# webhook1 and webhook2 have DIFFERENT recipients!
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
When `shared()` is used with `generate()`, FakeDataDSL emits a warning:
|
|
364
|
+
|
|
365
|
+
```
|
|
366
|
+
WARNING: shared() type used outside generate_many() - value will not be shared across calls.
|
|
367
|
+
Use generate_many() for batch generation with shared values.
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
### Basic Usage
|
|
371
|
+
|
|
372
|
+
```dsl
|
|
373
|
+
# All webhooks in a batch share the same recipient
|
|
374
|
+
TwitterDMWebhook:
|
|
375
|
+
users: map_by_field("id", sender: TwitterUser, recipient: shared(TwitterUser))
|
|
376
|
+
for_user_id: copy("users.recipient.id") # Same for all records
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
### How Shared Works
|
|
380
|
+
|
|
381
|
+
1. First generation: Creates the value and caches it in `shared_context`
|
|
382
|
+
2. Subsequent generations: Returns the cached value
|
|
383
|
+
|
|
384
|
+
```ruby
|
|
385
|
+
schema = registry.schema("TwitterDMWebhook")
|
|
386
|
+
webhooks = schema.generate_many(10, registry: registry)
|
|
387
|
+
|
|
388
|
+
# Verify sharing
|
|
389
|
+
recipient_ids = webhooks.map { |w| w.dig("users", "recipient", "id") }.uniq
|
|
390
|
+
# => 1 (all have same recipient)
|
|
391
|
+
|
|
392
|
+
sender_ids = webhooks.map { |w| w.dig("users", "sender", "id") }.uniq
|
|
393
|
+
# => 10 (all have different senders)
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
### Shared with Custom Keys
|
|
397
|
+
|
|
398
|
+
Use the `key:` argument for multiple shared values of the same type:
|
|
399
|
+
|
|
400
|
+
```dsl
|
|
401
|
+
Conversation:
|
|
402
|
+
user1: shared(User, key: "participant_1")
|
|
403
|
+
user2: shared(User, key: "participant_2")
|
|
404
|
+
messages: array(Message, 5..10)
|
|
405
|
+
|
|
406
|
+
Message:
|
|
407
|
+
sender: one_of(shared(User, key: "participant_1"), shared(User, key: "participant_2"))
|
|
408
|
+
text: message_text
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
### When to Use Shared
|
|
412
|
+
|
|
413
|
+
| Scenario | Use Shared? |
|
|
414
|
+
|----------|-------------|
|
|
415
|
+
| Inbox webhooks (same recipient) | ✅ Yes |
|
|
416
|
+
| Random users per record | ❌ No |
|
|
417
|
+
| Consistent config across batch | ✅ Yes |
|
|
418
|
+
| Unique IDs per record | ❌ No |
|
|
419
|
+
|
|
420
|
+
---
|
|
421
|
+
|
|
422
|
+
## Map By Field Type
|
|
423
|
+
|
|
424
|
+
### What is Map By Field?
|
|
425
|
+
|
|
426
|
+
`map_by_field()` creates dynamic object keys from generated field values. This is essential for APIs that return objects keyed by ID.
|
|
427
|
+
|
|
428
|
+
### Basic Usage
|
|
429
|
+
|
|
430
|
+
```dsl
|
|
431
|
+
EventData:
|
|
432
|
+
users: map_by_field("id", sender: TwitterUser, recipient: TwitterUser)
|
|
433
|
+
|
|
434
|
+
TwitterUser:
|
|
435
|
+
id: snowflake_id
|
|
436
|
+
name: full_name
|
|
437
|
+
screen_name: social_handle
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
Generates:
|
|
441
|
+
|
|
442
|
+
```json
|
|
443
|
+
{
|
|
444
|
+
"users": {
|
|
445
|
+
"1234567890123456789": {
|
|
446
|
+
"id": "1234567890123456789",
|
|
447
|
+
"name": "John Smith",
|
|
448
|
+
"screen_name": "johnsmith"
|
|
449
|
+
},
|
|
450
|
+
"9876543210987654321": {
|
|
451
|
+
"id": "9876543210987654321",
|
|
452
|
+
"name": "Jane Doe",
|
|
453
|
+
"screen_name": "janedoe"
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
### Syntax
|
|
460
|
+
|
|
461
|
+
```dsl
|
|
462
|
+
field_name: map_by_field("key_field", alias1: Schema1, alias2: Schema2, ...)
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
- `"key_field"` - The field to use as object keys (must exist in schemas)
|
|
466
|
+
- `alias: Schema` - Named entries in the map
|
|
467
|
+
|
|
468
|
+
### Accessing Map Values with Copy
|
|
469
|
+
|
|
470
|
+
Use the alias names to reference values:
|
|
471
|
+
|
|
472
|
+
```dsl
|
|
473
|
+
EventData:
|
|
474
|
+
users: map_by_field("id", sender: TwitterUser, recipient: shared(TwitterUser))
|
|
475
|
+
sender_id: copy("users.sender.id") # Access sender's ID
|
|
476
|
+
recipient_id: copy("users.recipient.id") # Access recipient's ID
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
### Combining with Shared
|
|
480
|
+
|
|
481
|
+
```dsl
|
|
482
|
+
# Recipient is shared across all records in generate_many()
|
|
483
|
+
EventData:
|
|
484
|
+
users: map_by_field("id", sender: TwitterUser, recipient: shared(TwitterUser))
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
---
|
|
488
|
+
|
|
489
|
+
## One Of Type
|
|
490
|
+
|
|
491
|
+
### What is One Of?
|
|
492
|
+
|
|
493
|
+
`one_of()` randomly selects and generates one of multiple schema options. This enables polymorphic data structures.
|
|
494
|
+
|
|
495
|
+
### Basic Usage
|
|
496
|
+
|
|
497
|
+
```dsl
|
|
498
|
+
WebhookEvent:
|
|
499
|
+
event_id: uuid
|
|
500
|
+
payload: one_of(MessagePayload, TypingPayload, ReadReceiptPayload)
|
|
501
|
+
|
|
502
|
+
MessagePayload:
|
|
503
|
+
type: const("message")
|
|
504
|
+
text: message_text
|
|
505
|
+
|
|
506
|
+
TypingPayload:
|
|
507
|
+
type: const("typing")
|
|
508
|
+
is_typing: boolean
|
|
509
|
+
|
|
510
|
+
ReadReceiptPayload:
|
|
511
|
+
type: const("read_receipt")
|
|
512
|
+
message_id: snowflake_id
|
|
513
|
+
```
|
|
514
|
+
|
|
515
|
+
### Weighted Selection
|
|
516
|
+
|
|
517
|
+
Use weights to control selection probability:
|
|
518
|
+
|
|
519
|
+
```dsl
|
|
520
|
+
# Messages 80%, typing 15%, read receipts 5%
|
|
521
|
+
payload: one_of(MessagePayload:80%, TypingPayload:15%, ReadReceiptPayload:5%)
|
|
522
|
+
```
|
|
523
|
+
|
|
524
|
+
### Use Cases
|
|
525
|
+
|
|
526
|
+
- Polymorphic API responses
|
|
527
|
+
- Mixed event types in streams
|
|
528
|
+
- Testing multiple payload formats
|
|
529
|
+
|
|
530
|
+
---
|
|
531
|
+
|
|
532
|
+
## Const Type
|
|
533
|
+
|
|
534
|
+
### What is Const?
|
|
535
|
+
|
|
536
|
+
`const()` generates a fixed, literal value. Use this for constant fields like type discriminators.
|
|
537
|
+
|
|
538
|
+
### Why Not Enum?
|
|
539
|
+
|
|
540
|
+
`enum()` is for selecting from multiple values. For single literal values, `const()` is more semantically correct:
|
|
541
|
+
|
|
542
|
+
```dsl
|
|
543
|
+
# ❌ Enum smell - single value enum
|
|
544
|
+
type: enum(message_create)
|
|
545
|
+
|
|
546
|
+
# ✅ Semantic - constant value
|
|
547
|
+
type: const("message_create")
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
### Basic Usage
|
|
551
|
+
|
|
552
|
+
```dsl
|
|
553
|
+
DirectMessageEvent:
|
|
554
|
+
type: const("message_create") # String literal
|
|
555
|
+
version: const(1) # Integer literal
|
|
556
|
+
enabled: const(true) # Boolean literal
|
|
557
|
+
```
|
|
558
|
+
|
|
559
|
+
### String vs Unquoted Values
|
|
560
|
+
|
|
561
|
+
```dsl
|
|
562
|
+
# Quoted strings (recommended)
|
|
563
|
+
type: const("message_create")
|
|
564
|
+
|
|
565
|
+
# Integer
|
|
566
|
+
version: const(42)
|
|
567
|
+
|
|
568
|
+
# Boolean
|
|
569
|
+
active: const(true)
|
|
570
|
+
```
|
|
571
|
+
|
|
572
|
+
---
|
|
573
|
+
|
|
574
|
+
## Custom Type
|
|
575
|
+
|
|
576
|
+
### What is Custom?
|
|
577
|
+
|
|
578
|
+
`custom` lets you compute field values dynamically using Ruby functions.
|
|
579
|
+
|
|
580
|
+
### Basic Usage
|
|
581
|
+
|
|
582
|
+
```dsl
|
|
583
|
+
Invoice:
|
|
584
|
+
items: array(LineItem, 1..5)
|
|
585
|
+
subtotal: custom(:calculate_subtotal)
|
|
586
|
+
tax: custom(:calculate_tax)
|
|
587
|
+
total: custom(:calculate_total)
|
|
588
|
+
|
|
589
|
+
LineItem:
|
|
590
|
+
quantity: number(1..5)
|
|
591
|
+
unit_price: number(9.99..99.99)
|
|
592
|
+
```
|
|
593
|
+
|
|
594
|
+
### Registering Functions
|
|
595
|
+
|
|
596
|
+
```ruby
|
|
597
|
+
FakeDataDSL.register_function(:calculate_subtotal) do |context|
|
|
598
|
+
items = context[:items] || []
|
|
599
|
+
items.sum { |item| item["quantity"] * item["unit_price"] }
|
|
600
|
+
end
|
|
601
|
+
|
|
602
|
+
FakeDataDSL.register_function(:calculate_tax) do |context|
|
|
603
|
+
subtotal = context[:subtotal] || 0
|
|
604
|
+
(subtotal * 0.08).round(2)
|
|
605
|
+
end
|
|
606
|
+
|
|
607
|
+
FakeDataDSL.register_function(:calculate_total) do |context|
|
|
608
|
+
subtotal = context[:subtotal] || 0
|
|
609
|
+
tax = context[:tax] || 0
|
|
610
|
+
(subtotal + tax).round(2)
|
|
611
|
+
end
|
|
612
|
+
```
|
|
613
|
+
|
|
614
|
+
### Context Object
|
|
615
|
+
|
|
616
|
+
The context contains:
|
|
617
|
+
- All previously generated fields
|
|
618
|
+
- Schema metadata
|
|
619
|
+
- Registry (if available)
|
|
620
|
+
|
|
621
|
+
```ruby
|
|
622
|
+
FakeDataDSL.register_function(:full_name) do |context|
|
|
623
|
+
"#{context['first_name']} #{context['last_name']}"
|
|
624
|
+
end
|
|
625
|
+
|
|
626
|
+
FakeDataDSL.register_function(:email_from_name) do |context|
|
|
627
|
+
name = context['name'].downcase.gsub(' ', '.')
|
|
628
|
+
"#{name}@company.com"
|
|
629
|
+
end
|
|
630
|
+
```
|
|
631
|
+
|
|
632
|
+
### Custom Function Examples
|
|
633
|
+
|
|
634
|
+
```ruby
|
|
635
|
+
# Conditional logic
|
|
636
|
+
FakeDataDSL.register_function(:discount_amount) do |context|
|
|
637
|
+
total = context[:total] || 0
|
|
638
|
+
tier = context[:customer_tier] || 'standard'
|
|
639
|
+
|
|
640
|
+
case tier
|
|
641
|
+
when 'gold' then total * 0.15
|
|
642
|
+
when 'silver' then total * 0.10
|
|
643
|
+
else total * 0.05
|
|
644
|
+
end
|
|
645
|
+
end
|
|
646
|
+
|
|
647
|
+
# External data
|
|
648
|
+
FakeDataDSL.register_function(:sku) do |context|
|
|
649
|
+
category = context[:category]
|
|
650
|
+
id = context[:id][0..7]
|
|
651
|
+
"#{category.upcase}-#{id}"
|
|
652
|
+
end
|
|
653
|
+
|
|
654
|
+
# Date calculations
|
|
655
|
+
FakeDataDSL.register_function(:expiration_date) do |context|
|
|
656
|
+
created = Date.parse(context[:created_at])
|
|
657
|
+
(created + 365).to_s
|
|
658
|
+
end
|
|
659
|
+
```
|
|
660
|
+
|
|
661
|
+
---
|
|
662
|
+
|
|
663
|
+
## Sequence Types
|
|
664
|
+
|
|
665
|
+
### id_sequence
|
|
666
|
+
|
|
667
|
+
Auto-incrementing integers:
|
|
668
|
+
|
|
669
|
+
```dsl
|
|
670
|
+
User:
|
|
671
|
+
id: id_sequence
|
|
672
|
+
order_number: id_sequence(start: 1000)
|
|
673
|
+
```
|
|
674
|
+
|
|
675
|
+
### character_sequence
|
|
676
|
+
|
|
677
|
+
Sequential letters:
|
|
678
|
+
|
|
679
|
+
```dsl
|
|
680
|
+
Item:
|
|
681
|
+
code: character_sequence
|
|
682
|
+
# Generates: A, B, C, ... Z, AA, AB, ...
|
|
683
|
+
```
|
|
684
|
+
|
|
685
|
+
### digit_sequence
|
|
686
|
+
|
|
687
|
+
Sequential numbers as strings:
|
|
688
|
+
|
|
689
|
+
```dsl
|
|
690
|
+
Invoice:
|
|
691
|
+
number: digit_sequence(start: "001", pad: 5)
|
|
692
|
+
# Generates: "00001", "00002", "00003", ...
|
|
693
|
+
```
|
|
694
|
+
|
|
695
|
+
---
|
|
696
|
+
|
|
697
|
+
## Formula Type
|
|
698
|
+
|
|
699
|
+
### Mathematical Expressions
|
|
700
|
+
|
|
701
|
+
```dsl
|
|
702
|
+
Product:
|
|
703
|
+
base_price: number(10..100)
|
|
704
|
+
tax_rate: number(0.05..0.15)
|
|
705
|
+
final_price: formula("base_price * (1 + tax_rate)")
|
|
706
|
+
```
|
|
707
|
+
|
|
708
|
+
### Supported Operations
|
|
709
|
+
|
|
710
|
+
- Arithmetic: `+`, `-`, `*`, `/`, `%`
|
|
711
|
+
- References to other fields
|
|
712
|
+
- Parentheses for grouping
|
|
713
|
+
|
|
714
|
+
```dsl
|
|
715
|
+
Order:
|
|
716
|
+
quantity: number(1..10)
|
|
717
|
+
unit_price: number(9.99..99.99)
|
|
718
|
+
discount: number(0..20)
|
|
719
|
+
|
|
720
|
+
subtotal: formula("quantity * unit_price")
|
|
721
|
+
discount_amount: formula("subtotal * discount / 100")
|
|
722
|
+
total: formula("subtotal - discount_amount")
|
|
723
|
+
```
|
|
724
|
+
|
|
725
|
+
---
|
|
726
|
+
|
|
727
|
+
## Template Type
|
|
728
|
+
|
|
729
|
+
### String Templates
|
|
730
|
+
|
|
731
|
+
```dsl
|
|
732
|
+
User:
|
|
733
|
+
first_name: first_name
|
|
734
|
+
last_name: last_name
|
|
735
|
+
display_name: template("${first_name} ${last_name}")
|
|
736
|
+
email: template("${first_name}.${last_name}@company.com")
|
|
737
|
+
```
|
|
738
|
+
|
|
739
|
+
### Template Syntax
|
|
740
|
+
|
|
741
|
+
- `${field_name}` - Insert field value
|
|
742
|
+
- Literal text around placeholders
|
|
743
|
+
|
|
744
|
+
```dsl
|
|
745
|
+
Product:
|
|
746
|
+
brand: text(5..20)
|
|
747
|
+
model: text(3..10)
|
|
748
|
+
sku: template("${brand}-${model}-001")
|
|
749
|
+
url: template("https://shop.com/products/${sku}")
|
|
750
|
+
```
|
|
751
|
+
|
|
752
|
+
---
|
|
753
|
+
|
|
754
|
+
## Type Composition
|
|
755
|
+
|
|
756
|
+
### Combining Types
|
|
757
|
+
|
|
758
|
+
```dsl
|
|
759
|
+
Order:
|
|
760
|
+
id: uuid(unique: true)
|
|
761
|
+
|
|
762
|
+
# Array of referenced items
|
|
763
|
+
item_ids: array(Ref(Product.id), 1..5)
|
|
764
|
+
|
|
765
|
+
# Nested objects
|
|
766
|
+
shipping: ShippingInfo
|
|
767
|
+
billing: BillingInfo
|
|
768
|
+
|
|
769
|
+
# Computed from nested data
|
|
770
|
+
total: custom(:sum_items)
|
|
771
|
+
|
|
772
|
+
ShippingInfo:
|
|
773
|
+
address: Address
|
|
774
|
+
method: enum(standard, express, overnight)
|
|
775
|
+
cost: money
|
|
776
|
+
|
|
777
|
+
BillingInfo:
|
|
778
|
+
address: Address
|
|
779
|
+
card_type: enum(visa, mastercard, amex)
|
|
780
|
+
last_four: text(4)
|
|
781
|
+
```
|
|
782
|
+
|
|
783
|
+
---
|
|
784
|
+
|
|
785
|
+
## Next Steps
|
|
786
|
+
|
|
787
|
+
- [Field Modifiers](field_modifiers.md) - Optional, nullable, conditional
|
|
788
|
+
- [Custom Types Guide](../api/custom_types.md) - Creating your own types
|
|
789
|
+
- [Custom Functions](../api/custom_functions.md) - Computed field functions
|
|
790
|
+
|