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,1287 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# =============================================================================
|
|
4
|
+
|
|
5
|
+
# Synthra Abstract Syntax Tree (AST) Nodes
|
|
6
|
+
# =============================================================================
|
|
7
|
+
#
|
|
8
|
+
# This file defines all AST node types used to represent the parsed
|
|
9
|
+
# structure of DSL files. The parser converts a token stream into
|
|
10
|
+
# a tree of these nodes.
|
|
11
|
+
#
|
|
12
|
+
# The AST serves as an intermediate representation between parsing
|
|
13
|
+
# and schema creation, enabling validation, transformation, and
|
|
14
|
+
# debugging of DSL definitions.
|
|
15
|
+
#
|
|
16
|
+
# Node Hierarchy:
|
|
17
|
+
# Node (base)
|
|
18
|
+
# ├── SchemaNode - Top-level schema definition
|
|
19
|
+
# ├── FieldNode - Field within a schema
|
|
20
|
+
# ├── TypeNode - Type reference (base for special types)
|
|
21
|
+
# │ ├── ArrayTypeNode - Array type with element and size
|
|
22
|
+
# │ └── CustomNode - Custom function call
|
|
23
|
+
# ├── BehaviorNode - Behavior annotation (@latency, etc.)
|
|
24
|
+
# ├── ConditionNode - Conditional field condition (if ...)
|
|
25
|
+
# ├── ReferenceNode - Cross-schema reference (Ref(User.id))
|
|
26
|
+
# ├── ModifierNode - Field modifier (unique, range)
|
|
27
|
+
# └── EnumValueNode - Enum value with optional probability
|
|
28
|
+
#
|
|
29
|
+
# =============================================================================
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
module Synthra
|
|
33
|
+
module Parser
|
|
34
|
+
|
|
35
|
+
# Abstract Syntax Tree nodes for representing parsed DSL structure
|
|
36
|
+
#
|
|
37
|
+
# The AST module contains node classes that represent the hierarchical
|
|
38
|
+
# structure of a parsed DSL file. Each node type corresponds to a
|
|
39
|
+
# different DSL construct.
|
|
40
|
+
#
|
|
41
|
+
# @example Parse DSL to AST
|
|
42
|
+
# parser = Synthra::Parser::Parser.new
|
|
43
|
+
# ast_nodes = parser.parse(dsl_source)
|
|
44
|
+
# # => [SchemaNode, SchemaNode, ...]
|
|
45
|
+
#
|
|
46
|
+
# @example Convert AST to Schema
|
|
47
|
+
# schema = ast_nodes.first.to_schema
|
|
48
|
+
# # => Synthra::Schema
|
|
49
|
+
#
|
|
50
|
+
|
|
51
|
+
module AST
|
|
52
|
+
|
|
53
|
+
# Base class for all AST nodes
|
|
54
|
+
#
|
|
55
|
+
# All AST nodes inherit from Node and share the `line` attribute
|
|
56
|
+
# for error reporting and debugging.
|
|
57
|
+
#
|
|
58
|
+
# @abstract Subclass and implement specific node behavior
|
|
59
|
+
#
|
|
60
|
+
# @!attribute [r] line
|
|
61
|
+
# Source line number where this node was defined
|
|
62
|
+
# @return [Integer, nil] the line number (1-based) or nil
|
|
63
|
+
#
|
|
64
|
+
|
|
65
|
+
class Node
|
|
66
|
+
attr_reader :line
|
|
67
|
+
|
|
68
|
+
# Create a new AST node
|
|
69
|
+
#
|
|
70
|
+
# @param line [Integer, nil] source line number (1-based)
|
|
71
|
+
#
|
|
72
|
+
|
|
73
|
+
def initialize(line: nil)
|
|
74
|
+
@line = line
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Schema definition node
|
|
79
|
+
#
|
|
80
|
+
# Represents a complete schema definition in the DSL. A SchemaNode
|
|
81
|
+
# contains the schema name, list of fields, and any schema-level
|
|
82
|
+
# behaviors.
|
|
83
|
+
#
|
|
84
|
+
# @example DSL
|
|
85
|
+
# User:
|
|
86
|
+
# @seed 12345
|
|
87
|
+
# id: uuid
|
|
88
|
+
# name: name
|
|
89
|
+
#
|
|
90
|
+
# @example AST
|
|
91
|
+
# SchemaNode.new(
|
|
92
|
+
# name: "User",
|
|
93
|
+
# fields: [FieldNode.new(...), FieldNode.new(...)],
|
|
94
|
+
# behaviors: [BehaviorNode.new(name: :seed, value: 12345)]
|
|
95
|
+
# )
|
|
96
|
+
#
|
|
97
|
+
|
|
98
|
+
class SchemaNode < Node
|
|
99
|
+
|
|
100
|
+
# @!attribute [r] name
|
|
101
|
+
# The schema name (e.g., "User", "Order")
|
|
102
|
+
# @return [String] schema name
|
|
103
|
+
|
|
104
|
+
attr_reader :name
|
|
105
|
+
|
|
106
|
+
# @!attribute [r] fields
|
|
107
|
+
# Field definitions within this schema
|
|
108
|
+
# @return [Array<FieldNode>] list of field nodes
|
|
109
|
+
|
|
110
|
+
attr_reader :fields
|
|
111
|
+
|
|
112
|
+
# @!attribute [r] behaviors
|
|
113
|
+
# Schema-level behaviors (@latency, @seed, etc.)
|
|
114
|
+
# @return [Array<BehaviorNode>] list of behavior nodes
|
|
115
|
+
|
|
116
|
+
attr_reader :behaviors
|
|
117
|
+
|
|
118
|
+
# @!attribute [r] parent_name
|
|
119
|
+
# Name of the schema this one inherits from (nil if none)
|
|
120
|
+
# @return [String, nil] parent schema name
|
|
121
|
+
|
|
122
|
+
attr_reader :parent_name
|
|
123
|
+
|
|
124
|
+
# Create a new SchemaNode
|
|
125
|
+
#
|
|
126
|
+
# @param name [String] the schema name
|
|
127
|
+
# @param fields [Array<FieldNode>] field definitions
|
|
128
|
+
# @param behaviors [Array<BehaviorNode>] schema-level behaviors
|
|
129
|
+
# @param parent_name [String, nil] name of an inherited parent schema
|
|
130
|
+
# @param line [Integer, nil] source line number
|
|
131
|
+
#
|
|
132
|
+
|
|
133
|
+
def initialize(name:, fields: [], behaviors: [], parent_name: nil, line: nil)
|
|
134
|
+
super(line: line)
|
|
135
|
+
@name = name
|
|
136
|
+
@fields = fields
|
|
137
|
+
@behaviors = behaviors
|
|
138
|
+
@parent_name = parent_name
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# Convert this AST node to a Schema object
|
|
142
|
+
#
|
|
143
|
+
# Creates a Synthra::Schema instance from this node,
|
|
144
|
+
# converting all child nodes (fields, behaviors) as well.
|
|
145
|
+
#
|
|
146
|
+
# @return [Synthra::Schema] the schema object
|
|
147
|
+
#
|
|
148
|
+
# @example
|
|
149
|
+
# schema_node.to_schema
|
|
150
|
+
# # => #<Schema User fields=2 behaviors=1>
|
|
151
|
+
#
|
|
152
|
+
|
|
153
|
+
def to_schema
|
|
154
|
+
all_behaviors = behaviors.map(&:to_behavior)
|
|
155
|
+
|
|
156
|
+
# Extract version from behaviors
|
|
157
|
+
version_behavior = all_behaviors.find { |b| b[:name] == :version }
|
|
158
|
+
version = version_behavior&.dig(:value)
|
|
159
|
+
|
|
160
|
+
# Extract deprecated from behaviors
|
|
161
|
+
deprecated_behavior = all_behaviors.find { |b| b[:name] == :deprecated }
|
|
162
|
+
deprecated = !deprecated_behavior.nil?
|
|
163
|
+
deprecation_message = deprecated_behavior&.dig(:value)
|
|
164
|
+
|
|
165
|
+
# Filter out version/deprecated from runtime behaviors
|
|
166
|
+
runtime_behaviors = all_behaviors.reject { |b| [:version, :deprecated].include?(b[:name]) }
|
|
167
|
+
|
|
168
|
+
Schema.new(
|
|
169
|
+
name: name,
|
|
170
|
+
fields: fields.map(&:to_field),
|
|
171
|
+
behaviors: runtime_behaviors,
|
|
172
|
+
metadata: {},
|
|
173
|
+
version: version,
|
|
174
|
+
deprecated: deprecated,
|
|
175
|
+
deprecation_message: deprecation_message
|
|
176
|
+
)
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# Field definition node
|
|
181
|
+
#
|
|
182
|
+
# Represents a single field within a schema. Contains the field
|
|
183
|
+
# name, type information, optional flag, conditions, and behaviors.
|
|
184
|
+
#
|
|
185
|
+
# @example DSL
|
|
186
|
+
# email?: email if has_contact @partial_data 20%
|
|
187
|
+
#
|
|
188
|
+
# @example AST
|
|
189
|
+
# FieldNode.new(
|
|
190
|
+
# name: "email",
|
|
191
|
+
# type_node: TypeNode.new(name: "email"),
|
|
192
|
+
# optional: true,
|
|
193
|
+
# condition: ConditionNode.new(field_name: "has_contact"),
|
|
194
|
+
# behaviors: [BehaviorNode.new(name: :partial_data, value: 20)]
|
|
195
|
+
# )
|
|
196
|
+
#
|
|
197
|
+
|
|
198
|
+
class FieldNode < Node
|
|
199
|
+
|
|
200
|
+
# @!attribute [r] name
|
|
201
|
+
# The field name (without ? suffix)
|
|
202
|
+
# @return [String] field name
|
|
203
|
+
|
|
204
|
+
attr_reader :name
|
|
205
|
+
|
|
206
|
+
# @!attribute [r] type_node
|
|
207
|
+
# The type definition for this field
|
|
208
|
+
# @return [TypeNode] type node
|
|
209
|
+
|
|
210
|
+
attr_reader :type_node
|
|
211
|
+
|
|
212
|
+
# @!attribute [r] optional
|
|
213
|
+
# Whether the field is optional (? suffix)
|
|
214
|
+
# @return [Boolean] true if optional
|
|
215
|
+
|
|
216
|
+
attr_reader :optional
|
|
217
|
+
|
|
218
|
+
# @!attribute [r] condition
|
|
219
|
+
# Condition for including this field
|
|
220
|
+
# @return [ConditionNode, nil] condition or nil
|
|
221
|
+
|
|
222
|
+
attr_reader :condition
|
|
223
|
+
|
|
224
|
+
# @!attribute [r] behaviors
|
|
225
|
+
# Field-level behaviors
|
|
226
|
+
# @return [Array<BehaviorNode>] list of behaviors
|
|
227
|
+
|
|
228
|
+
attr_reader :behaviors
|
|
229
|
+
|
|
230
|
+
# Create a new FieldNode
|
|
231
|
+
#
|
|
232
|
+
# @param name [String] field name (without ?)
|
|
233
|
+
# @param type_node [TypeNode] type definition
|
|
234
|
+
# @param optional [Boolean] is field optional?
|
|
235
|
+
# @param condition [ConditionNode, nil] inclusion condition
|
|
236
|
+
# @param behaviors [Array<BehaviorNode>] field behaviors
|
|
237
|
+
# @param line [Integer, nil] source line number
|
|
238
|
+
#
|
|
239
|
+
|
|
240
|
+
def initialize(name:, type_node:, optional: false, condition: nil, behaviors: [], line: nil)
|
|
241
|
+
super(line: line)
|
|
242
|
+
@name = name
|
|
243
|
+
@type_node = type_node
|
|
244
|
+
@optional = optional
|
|
245
|
+
@condition = condition
|
|
246
|
+
@behaviors = behaviors
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
# Check if this field is optional
|
|
250
|
+
#
|
|
251
|
+
# @return [Boolean] true if the field is optional
|
|
252
|
+
#
|
|
253
|
+
|
|
254
|
+
def optional?
|
|
255
|
+
@optional
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
# Convert this AST node to a Field object
|
|
259
|
+
#
|
|
260
|
+
# Creates a Synthra::Field instance from this node.
|
|
261
|
+
#
|
|
262
|
+
# @return [Synthra::Field] the field object
|
|
263
|
+
#
|
|
264
|
+
# @example
|
|
265
|
+
# field_node.to_field
|
|
266
|
+
# # => #<Field email?: email>
|
|
267
|
+
#
|
|
268
|
+
|
|
269
|
+
def to_field
|
|
270
|
+
Field.new(
|
|
271
|
+
name: optional? ? "#{name}?" : name,
|
|
272
|
+
type_name: type_node.name,
|
|
273
|
+
type_args: type_node.arguments,
|
|
274
|
+
nullable: type_node.nullable?,
|
|
275
|
+
condition: condition&.field_name,
|
|
276
|
+
behaviors: behaviors.map(&:to_field_behavior)
|
|
277
|
+
)
|
|
278
|
+
end
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
# Type reference node
|
|
282
|
+
#
|
|
283
|
+
# Represents a type declaration with optional arguments. This is the
|
|
284
|
+
# base class for type nodes; ArrayTypeNode and CustomNode extend it
|
|
285
|
+
# for special cases.
|
|
286
|
+
#
|
|
287
|
+
# @example DSL
|
|
288
|
+
# age: number(18..80)
|
|
289
|
+
# status: enum(active, inactive:30%)
|
|
290
|
+
#
|
|
291
|
+
# @example AST
|
|
292
|
+
# TypeNode.new(
|
|
293
|
+
# name: "number",
|
|
294
|
+
# arguments: { range: 18..80 }
|
|
295
|
+
# )
|
|
296
|
+
#
|
|
297
|
+
|
|
298
|
+
class TypeNode < Node
|
|
299
|
+
|
|
300
|
+
# @!attribute [r] name
|
|
301
|
+
# The type name (uuid, text, number, etc.)
|
|
302
|
+
# @return [String] type name
|
|
303
|
+
|
|
304
|
+
attr_reader :name
|
|
305
|
+
|
|
306
|
+
# @!attribute [r] arguments
|
|
307
|
+
# Type arguments (min, max, values, etc.)
|
|
308
|
+
# @return [Hash] argument name => value pairs
|
|
309
|
+
|
|
310
|
+
attr_reader :arguments
|
|
311
|
+
|
|
312
|
+
# @!attribute [r] nullable
|
|
313
|
+
# Whether the type can be null (? suffix on type)
|
|
314
|
+
# @return [Boolean] true if nullable
|
|
315
|
+
|
|
316
|
+
attr_reader :nullable
|
|
317
|
+
|
|
318
|
+
# Create a new TypeNode
|
|
319
|
+
#
|
|
320
|
+
# @param name [String] type name (may have ? suffix)
|
|
321
|
+
# @param arguments [Hash] type arguments
|
|
322
|
+
# @param nullable [Boolean] is type nullable?
|
|
323
|
+
# @param line [Integer, nil] source line number
|
|
324
|
+
#
|
|
325
|
+
|
|
326
|
+
def initialize(name:, arguments: {}, nullable: false, line: nil)
|
|
327
|
+
super(line: line)
|
|
328
|
+
@name = name.to_s.delete_suffix("?")
|
|
329
|
+
@nullable = nullable || name.to_s.end_with?("?")
|
|
330
|
+
@arguments = arguments
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
# Check if this type can be null
|
|
334
|
+
#
|
|
335
|
+
# @return [Boolean] true if nullable
|
|
336
|
+
#
|
|
337
|
+
|
|
338
|
+
def nullable?
|
|
339
|
+
@nullable
|
|
340
|
+
end
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
# Behavior annotation node
|
|
344
|
+
#
|
|
345
|
+
# Represents a behavior declaration at schema or field level.
|
|
346
|
+
# Behaviors modify generation (latency, failure) or set metadata (seed).
|
|
347
|
+
#
|
|
348
|
+
# @example DSL
|
|
349
|
+
# @latency 100..500ms
|
|
350
|
+
# @failure 10%
|
|
351
|
+
# @seed 12345
|
|
352
|
+
#
|
|
353
|
+
# @example AST
|
|
354
|
+
# BehaviorNode.new(name: :latency, value: 100..500)
|
|
355
|
+
# BehaviorNode.new(name: :failure, value: 10)
|
|
356
|
+
# BehaviorNode.new(name: :seed, value: 12345)
|
|
357
|
+
#
|
|
358
|
+
|
|
359
|
+
class BehaviorNode < Node
|
|
360
|
+
|
|
361
|
+
# @!attribute [r] name
|
|
362
|
+
# Behavior name (latency, failure, seed, etc.)
|
|
363
|
+
# @return [Symbol] behavior name
|
|
364
|
+
|
|
365
|
+
attr_reader :name
|
|
366
|
+
|
|
367
|
+
# @!attribute [r] value
|
|
368
|
+
# Behavior configuration value
|
|
369
|
+
# @return [Object] the value (Integer, Range, Hash, etc.)
|
|
370
|
+
|
|
371
|
+
attr_reader :value
|
|
372
|
+
|
|
373
|
+
# Create a new BehaviorNode
|
|
374
|
+
#
|
|
375
|
+
# @param name [String, Symbol] behavior name
|
|
376
|
+
# @param value [Object] behavior value
|
|
377
|
+
# @param line [Integer, nil] source line number
|
|
378
|
+
#
|
|
379
|
+
|
|
380
|
+
def initialize(name:, value:, line: nil)
|
|
381
|
+
super(line: line)
|
|
382
|
+
@name = name.to_sym
|
|
383
|
+
@value = value
|
|
384
|
+
end
|
|
385
|
+
|
|
386
|
+
# Convert to FieldBehavior for field-level behaviors
|
|
387
|
+
#
|
|
388
|
+
# @return [Synthra::FieldBehavior] field behavior object
|
|
389
|
+
#
|
|
390
|
+
|
|
391
|
+
def to_field_behavior
|
|
392
|
+
FieldBehavior.new(type: name, value: value)
|
|
393
|
+
end
|
|
394
|
+
|
|
395
|
+
# Convert to behavior configuration hash for schema-level
|
|
396
|
+
#
|
|
397
|
+
# @return [Hash] behavior configuration hash
|
|
398
|
+
#
|
|
399
|
+
|
|
400
|
+
def to_behavior
|
|
401
|
+
{ name: name, value: value }
|
|
402
|
+
end
|
|
403
|
+
end
|
|
404
|
+
|
|
405
|
+
# Condition node for conditional fields
|
|
406
|
+
#
|
|
407
|
+
# Represents the condition part of a conditional field (if ...).
|
|
408
|
+
# The condition references another field that must be truthy.
|
|
409
|
+
#
|
|
410
|
+
# @example DSL
|
|
411
|
+
# admin_note: text if is_admin
|
|
412
|
+
#
|
|
413
|
+
# @example AST
|
|
414
|
+
# ConditionNode.new(field_name: "is_admin")
|
|
415
|
+
#
|
|
416
|
+
|
|
417
|
+
class ConditionNode < Node
|
|
418
|
+
|
|
419
|
+
# Dangerous method names that should never be allowed in conditions
|
|
420
|
+
# @api private
|
|
421
|
+
DANGEROUS_METHODS = %w[class instance_eval eval send __send__ method_missing].freeze
|
|
422
|
+
|
|
423
|
+
# @!attribute [r] field_name
|
|
424
|
+
# Name of the field to check
|
|
425
|
+
# @return [String] field name
|
|
426
|
+
|
|
427
|
+
attr_reader :field_name
|
|
428
|
+
|
|
429
|
+
# Create a new ConditionNode
|
|
430
|
+
#
|
|
431
|
+
# @param field_name [String] name of field to check for truthiness
|
|
432
|
+
# @param line [Integer, nil] source line number
|
|
433
|
+
#
|
|
434
|
+
|
|
435
|
+
def initialize(field_name:, line: nil)
|
|
436
|
+
super(line: line)
|
|
437
|
+
@field_name = field_name
|
|
438
|
+
end
|
|
439
|
+
|
|
440
|
+
# Evaluate the condition against a context
|
|
441
|
+
#
|
|
442
|
+
# Checks if the referenced field has a truthy value in the
|
|
443
|
+
# given context. Security: Only uses hash access, never method calls,
|
|
444
|
+
# and validates field names against a whitelist of dangerous methods.
|
|
445
|
+
#
|
|
446
|
+
# @param context [Hash, Generator::Context] generation context
|
|
447
|
+
# @return [Boolean] true if condition is met
|
|
448
|
+
#
|
|
449
|
+
# @example
|
|
450
|
+
# condition = ConditionNode.new(field_name: "is_admin")
|
|
451
|
+
# condition.evaluate({ "is_admin" => true }) # => true
|
|
452
|
+
# condition.evaluate({ "is_admin" => false }) # => false
|
|
453
|
+
# condition.evaluate({ "is_admin" => nil }) # => false
|
|
454
|
+
#
|
|
455
|
+
|
|
456
|
+
def evaluate(context)
|
|
457
|
+
|
|
458
|
+
# Security: Only use hash access, never public_send
|
|
459
|
+
# Reject dangerous field names that could be exploited
|
|
460
|
+
field_name_str = @field_name.to_s
|
|
461
|
+
return false if field_name_str.start_with?("__")
|
|
462
|
+
|
|
463
|
+
# Whitelist validation: reject dangerous method names
|
|
464
|
+
return false if self.class::DANGEROUS_METHODS.include?(field_name_str)
|
|
465
|
+
|
|
466
|
+
return false unless context.respond_to?(:[])
|
|
467
|
+
|
|
468
|
+
value = context[@field_name] || context[@field_name.to_sym]
|
|
469
|
+
!!value
|
|
470
|
+
end
|
|
471
|
+
end
|
|
472
|
+
|
|
473
|
+
# Reference to another schema's field
|
|
474
|
+
#
|
|
475
|
+
# Represents a Ref() expression that references a field from
|
|
476
|
+
# another schema. Used for cross-schema relationships.
|
|
477
|
+
#
|
|
478
|
+
# @example DSL
|
|
479
|
+
# user_id: Ref(User.id)
|
|
480
|
+
# category: Ref(Category.name)
|
|
481
|
+
#
|
|
482
|
+
# @example AST
|
|
483
|
+
# ReferenceNode.new(
|
|
484
|
+
# schema_name: "User",
|
|
485
|
+
# field_path: "id"
|
|
486
|
+
# )
|
|
487
|
+
#
|
|
488
|
+
|
|
489
|
+
class ReferenceNode < Node
|
|
490
|
+
|
|
491
|
+
# @!attribute [r] schema_name
|
|
492
|
+
# Name of the referenced schema
|
|
493
|
+
# @return [String] schema name
|
|
494
|
+
|
|
495
|
+
attr_reader :schema_name
|
|
496
|
+
|
|
497
|
+
# @!attribute [r] field_path
|
|
498
|
+
# Path to the field within the schema
|
|
499
|
+
# @return [String] field path (may contain dots for nesting)
|
|
500
|
+
|
|
501
|
+
attr_reader :field_path
|
|
502
|
+
|
|
503
|
+
# Create a new ReferenceNode
|
|
504
|
+
#
|
|
505
|
+
# @param schema_name [String] name of referenced schema
|
|
506
|
+
# @param field_path [String] path to field in schema
|
|
507
|
+
# @param line [Integer, nil] source line number
|
|
508
|
+
#
|
|
509
|
+
|
|
510
|
+
def initialize(schema_name:, field_path:, line: nil)
|
|
511
|
+
super(line: line)
|
|
512
|
+
@schema_name = schema_name
|
|
513
|
+
@field_path = field_path
|
|
514
|
+
end
|
|
515
|
+
|
|
516
|
+
# Get full reference string
|
|
517
|
+
#
|
|
518
|
+
# @return [String] "SchemaName.field_path"
|
|
519
|
+
#
|
|
520
|
+
# @example
|
|
521
|
+
# ref.to_s # => "User.id"
|
|
522
|
+
#
|
|
523
|
+
|
|
524
|
+
def to_s
|
|
525
|
+
"#{schema_name}.#{field_path}"
|
|
526
|
+
end
|
|
527
|
+
end
|
|
528
|
+
|
|
529
|
+
# Modifier node for field modifiers
|
|
530
|
+
#
|
|
531
|
+
# Represents a modifier applied to a field (unique, range, etc.).
|
|
532
|
+
#
|
|
533
|
+
# @example DSL
|
|
534
|
+
# id: uuid unique
|
|
535
|
+
# age: number(18..80) # range is often embedded in type args
|
|
536
|
+
#
|
|
537
|
+
# @example AST
|
|
538
|
+
# ModifierNode.new(name: :unique, value: true)
|
|
539
|
+
#
|
|
540
|
+
|
|
541
|
+
class ModifierNode < Node
|
|
542
|
+
|
|
543
|
+
# @!attribute [r] name
|
|
544
|
+
# Modifier name (unique, range, etc.)
|
|
545
|
+
# @return [Symbol] modifier name
|
|
546
|
+
|
|
547
|
+
attr_reader :name
|
|
548
|
+
|
|
549
|
+
# @!attribute [r] value
|
|
550
|
+
# Modifier value
|
|
551
|
+
# @return [Object] the value
|
|
552
|
+
|
|
553
|
+
attr_reader :value
|
|
554
|
+
|
|
555
|
+
# Create a new ModifierNode
|
|
556
|
+
#
|
|
557
|
+
# @param name [String, Symbol] modifier name
|
|
558
|
+
# @param value [Object] modifier value
|
|
559
|
+
# @param line [Integer, nil] source line number
|
|
560
|
+
#
|
|
561
|
+
|
|
562
|
+
def initialize(name:, value:, line: nil)
|
|
563
|
+
super(line: line)
|
|
564
|
+
@name = name.to_sym
|
|
565
|
+
@value = value
|
|
566
|
+
end
|
|
567
|
+
end
|
|
568
|
+
|
|
569
|
+
# Enum value with optional probability
|
|
570
|
+
#
|
|
571
|
+
# Represents a single value within an enum type, optionally
|
|
572
|
+
# with a probability weight.
|
|
573
|
+
#
|
|
574
|
+
# @example DSL
|
|
575
|
+
# status: enum(pending, active:60%, completed:30%)
|
|
576
|
+
#
|
|
577
|
+
# @example AST
|
|
578
|
+
# EnumValueNode.new(value: "pending")
|
|
579
|
+
# EnumValueNode.new(value: "active", probability: 60)
|
|
580
|
+
#
|
|
581
|
+
|
|
582
|
+
class EnumValueNode < Node
|
|
583
|
+
|
|
584
|
+
# @!attribute [r] value
|
|
585
|
+
# The enum value
|
|
586
|
+
# @return [String] enum value
|
|
587
|
+
|
|
588
|
+
attr_reader :value
|
|
589
|
+
|
|
590
|
+
# @!attribute [r] probability
|
|
591
|
+
# Optional probability percentage
|
|
592
|
+
# @return [Integer, nil] probability (0-100) or nil
|
|
593
|
+
|
|
594
|
+
attr_reader :probability
|
|
595
|
+
|
|
596
|
+
# Create a new EnumValueNode
|
|
597
|
+
#
|
|
598
|
+
# @param value [String] enum value
|
|
599
|
+
# @param probability [Integer, nil] probability percentage
|
|
600
|
+
# @param line [Integer, nil] source line number
|
|
601
|
+
#
|
|
602
|
+
|
|
603
|
+
def initialize(value:, probability: nil, line: nil)
|
|
604
|
+
super(line: line)
|
|
605
|
+
@value = value
|
|
606
|
+
@probability = probability
|
|
607
|
+
end
|
|
608
|
+
end
|
|
609
|
+
|
|
610
|
+
# Array type node
|
|
611
|
+
#
|
|
612
|
+
# Represents an array type with element type and size specification.
|
|
613
|
+
# Extends TypeNode with array-specific attributes.
|
|
614
|
+
#
|
|
615
|
+
# @example DSL
|
|
616
|
+
# tags: array(text, 1..5)
|
|
617
|
+
# items: array(Item, 3)
|
|
618
|
+
#
|
|
619
|
+
# @example AST
|
|
620
|
+
# ArrayTypeNode.new(
|
|
621
|
+
# element_type: TypeNode.new(name: "text"),
|
|
622
|
+
# size_range: 1..5
|
|
623
|
+
# )
|
|
624
|
+
#
|
|
625
|
+
|
|
626
|
+
class ArrayTypeNode < TypeNode
|
|
627
|
+
|
|
628
|
+
# @!attribute [r] element_type
|
|
629
|
+
# Type of elements in the array
|
|
630
|
+
# @return [TypeNode] element type node
|
|
631
|
+
|
|
632
|
+
attr_reader :element_type
|
|
633
|
+
|
|
634
|
+
# @!attribute [r] size_range
|
|
635
|
+
# Size specification (Range or fixed Integer)
|
|
636
|
+
# @return [Range, Integer, Hash] size specification
|
|
637
|
+
|
|
638
|
+
attr_reader :size_range
|
|
639
|
+
|
|
640
|
+
# Create a new ArrayTypeNode
|
|
641
|
+
#
|
|
642
|
+
# @param element_type [TypeNode] element type
|
|
643
|
+
# @param size_range [Range, Integer, Hash] size specification
|
|
644
|
+
# @param nullable [Boolean] whether the array can be null
|
|
645
|
+
# @param line [Integer, nil] source line number
|
|
646
|
+
#
|
|
647
|
+
|
|
648
|
+
def initialize(element_type:, size_range:, nullable: false, line: nil)
|
|
649
|
+
super(
|
|
650
|
+
name: "array",
|
|
651
|
+
arguments: { element: element_type, size: size_range },
|
|
652
|
+
nullable: nullable,
|
|
653
|
+
line: line
|
|
654
|
+
)
|
|
655
|
+
@element_type = element_type
|
|
656
|
+
@size_range = size_range
|
|
657
|
+
end
|
|
658
|
+
end
|
|
659
|
+
|
|
660
|
+
# Custom function call node
|
|
661
|
+
#
|
|
662
|
+
# Represents a custom() type that calls a registered function.
|
|
663
|
+
# The function is called during generation with the context.
|
|
664
|
+
#
|
|
665
|
+
# @example DSL
|
|
666
|
+
# full_name: custom(:compute_full_name)
|
|
667
|
+
#
|
|
668
|
+
# @example AST
|
|
669
|
+
# CustomNode.new(function_name: :compute_full_name)
|
|
670
|
+
#
|
|
671
|
+
|
|
672
|
+
class CustomNode < TypeNode
|
|
673
|
+
|
|
674
|
+
# @!attribute [r] function_name
|
|
675
|
+
# Name of the function to call
|
|
676
|
+
# @return [Symbol] function name
|
|
677
|
+
|
|
678
|
+
attr_reader :function_name
|
|
679
|
+
|
|
680
|
+
# Create a new CustomNode
|
|
681
|
+
#
|
|
682
|
+
# @param function_name [Symbol] name of registered function
|
|
683
|
+
# @param line [Integer, nil] source line number
|
|
684
|
+
#
|
|
685
|
+
|
|
686
|
+
def initialize(function_name:, line: nil)
|
|
687
|
+
super(
|
|
688
|
+
name: "custom",
|
|
689
|
+
arguments: { function: function_name },
|
|
690
|
+
line: line
|
|
691
|
+
)
|
|
692
|
+
@function_name = function_name
|
|
693
|
+
end
|
|
694
|
+
end
|
|
695
|
+
|
|
696
|
+
# =========================================================================
|
|
697
|
+
# Simulyra DSL - New AST Nodes for Layered Architecture
|
|
698
|
+
# =========================================================================
|
|
699
|
+
|
|
700
|
+
# Program node - root of the AST
|
|
701
|
+
#
|
|
702
|
+
# Contains all top-level definitions: schemas and APIs.
|
|
703
|
+
# This is the entry point for the parsed DSL.
|
|
704
|
+
#
|
|
705
|
+
# @example DSL
|
|
706
|
+
# schema User:
|
|
707
|
+
# id: uuid
|
|
708
|
+
#
|
|
709
|
+
# api GET /users:
|
|
710
|
+
# scenario Success:
|
|
711
|
+
# @status 200
|
|
712
|
+
#
|
|
713
|
+
# @example AST
|
|
714
|
+
# ProgramNode.new(
|
|
715
|
+
# schemas: [SchemaDefinitionNode.new(...)],
|
|
716
|
+
# apis: [ApiDefinitionNode.new(...)]
|
|
717
|
+
# )
|
|
718
|
+
#
|
|
719
|
+
|
|
720
|
+
class ProgramNode < Node
|
|
721
|
+
|
|
722
|
+
# @!attribute [r] schemas
|
|
723
|
+
# Schema definitions in the program
|
|
724
|
+
# @return [Array<SchemaDefinitionNode>] list of schema definitions
|
|
725
|
+
|
|
726
|
+
attr_reader :schemas
|
|
727
|
+
|
|
728
|
+
# @!attribute [r] apis
|
|
729
|
+
# API definitions in the program
|
|
730
|
+
# @return [Array<ApiDefinitionNode>] list of API definitions
|
|
731
|
+
|
|
732
|
+
attr_reader :apis
|
|
733
|
+
|
|
734
|
+
# @!attribute [r] legacy_schemas
|
|
735
|
+
# Legacy schema definitions (without 'schema' keyword)
|
|
736
|
+
# @return [Array<SchemaNode>] list of legacy schema nodes
|
|
737
|
+
|
|
738
|
+
attr_reader :legacy_schemas
|
|
739
|
+
|
|
740
|
+
# Create a new ProgramNode
|
|
741
|
+
#
|
|
742
|
+
# @param schemas [Array<SchemaDefinitionNode>] schema definitions
|
|
743
|
+
# @param apis [Array<ApiDefinitionNode>] API definitions
|
|
744
|
+
# @param legacy_schemas [Array<SchemaNode>] legacy schema definitions
|
|
745
|
+
# @param line [Integer, nil] source line number
|
|
746
|
+
#
|
|
747
|
+
|
|
748
|
+
def initialize(schemas: [], apis: [], legacy_schemas: [], line: nil)
|
|
749
|
+
super(line: line)
|
|
750
|
+
@schemas = schemas
|
|
751
|
+
@apis = apis
|
|
752
|
+
@legacy_schemas = legacy_schemas
|
|
753
|
+
end
|
|
754
|
+
|
|
755
|
+
# Get all schema nodes (both new and legacy)
|
|
756
|
+
#
|
|
757
|
+
# @return [Array<SchemaDefinitionNode, SchemaNode>] all schemas
|
|
758
|
+
#
|
|
759
|
+
|
|
760
|
+
def all_schemas
|
|
761
|
+
schemas + legacy_schemas
|
|
762
|
+
end
|
|
763
|
+
end
|
|
764
|
+
|
|
765
|
+
# Schema definition node (new syntax)
|
|
766
|
+
#
|
|
767
|
+
# Represents a schema definition using the new Simulyra syntax:
|
|
768
|
+
# `schema Name:`
|
|
769
|
+
#
|
|
770
|
+
# This differs from the legacy SchemaNode which just uses `Name:`
|
|
771
|
+
#
|
|
772
|
+
# @example DSL
|
|
773
|
+
# schema User:
|
|
774
|
+
# id: uuid
|
|
775
|
+
# email: email
|
|
776
|
+
#
|
|
777
|
+
# @example AST
|
|
778
|
+
# SchemaDefinitionNode.new(
|
|
779
|
+
# name: "User",
|
|
780
|
+
# fields: [FieldNode.new(...), FieldNode.new(...)]
|
|
781
|
+
# )
|
|
782
|
+
#
|
|
783
|
+
|
|
784
|
+
class SchemaDefinitionNode < Node
|
|
785
|
+
|
|
786
|
+
# @!attribute [r] name
|
|
787
|
+
# The schema name
|
|
788
|
+
# @return [String] schema name
|
|
789
|
+
|
|
790
|
+
attr_reader :name
|
|
791
|
+
|
|
792
|
+
# @!attribute [r] fields
|
|
793
|
+
# Field definitions within this schema
|
|
794
|
+
# @return [Array<FieldNode>] list of field nodes
|
|
795
|
+
|
|
796
|
+
attr_reader :fields
|
|
797
|
+
|
|
798
|
+
# @!attribute [r] behaviors
|
|
799
|
+
# Schema-level behaviors (for compatibility)
|
|
800
|
+
# @return [Array<BehaviorNode>] list of behavior nodes
|
|
801
|
+
|
|
802
|
+
attr_reader :behaviors
|
|
803
|
+
|
|
804
|
+
# Create a new SchemaDefinitionNode
|
|
805
|
+
#
|
|
806
|
+
# @param name [String] the schema name
|
|
807
|
+
# @param fields [Array<FieldNode>] field definitions
|
|
808
|
+
# @param behaviors [Array<BehaviorNode>] schema-level behaviors
|
|
809
|
+
# @param line [Integer, nil] source line number
|
|
810
|
+
#
|
|
811
|
+
|
|
812
|
+
def initialize(name:, fields: [], behaviors: [], line: nil)
|
|
813
|
+
super(line: line)
|
|
814
|
+
@name = name
|
|
815
|
+
@fields = fields
|
|
816
|
+
@behaviors = behaviors
|
|
817
|
+
end
|
|
818
|
+
|
|
819
|
+
# Convert to Schema object
|
|
820
|
+
#
|
|
821
|
+
# @return [Synthra::Schema] the schema object
|
|
822
|
+
#
|
|
823
|
+
|
|
824
|
+
def to_schema
|
|
825
|
+
all_behaviors = behaviors.map(&:to_behavior)
|
|
826
|
+
|
|
827
|
+
version_behavior = all_behaviors.find { |b| b[:name] == :version }
|
|
828
|
+
version = version_behavior&.dig(:value)
|
|
829
|
+
|
|
830
|
+
deprecated_behavior = all_behaviors.find { |b| b[:name] == :deprecated }
|
|
831
|
+
deprecated = !deprecated_behavior.nil?
|
|
832
|
+
deprecation_message = deprecated_behavior&.dig(:value)
|
|
833
|
+
|
|
834
|
+
runtime_behaviors = all_behaviors.reject { |b| [:version, :deprecated].include?(b[:name]) }
|
|
835
|
+
|
|
836
|
+
Schema.new(
|
|
837
|
+
name: name,
|
|
838
|
+
fields: fields.map(&:to_field),
|
|
839
|
+
behaviors: runtime_behaviors,
|
|
840
|
+
metadata: {},
|
|
841
|
+
version: version,
|
|
842
|
+
deprecated: deprecated,
|
|
843
|
+
deprecation_message: deprecation_message
|
|
844
|
+
)
|
|
845
|
+
end
|
|
846
|
+
end
|
|
847
|
+
|
|
848
|
+
# API definition node
|
|
849
|
+
#
|
|
850
|
+
# Represents an API endpoint definition with HTTP method, path,
|
|
851
|
+
# and response scenarios.
|
|
852
|
+
#
|
|
853
|
+
# @example DSL
|
|
854
|
+
# api GET /users:
|
|
855
|
+
# scenario Success:
|
|
856
|
+
# @status 200
|
|
857
|
+
# users: schema(:User).array
|
|
858
|
+
#
|
|
859
|
+
# @example AST
|
|
860
|
+
# ApiDefinitionNode.new(
|
|
861
|
+
# method: :get,
|
|
862
|
+
# path: "/users",
|
|
863
|
+
# scenarios: [ScenarioNode.new(...)]
|
|
864
|
+
# )
|
|
865
|
+
#
|
|
866
|
+
|
|
867
|
+
class ApiDefinitionNode < Node
|
|
868
|
+
|
|
869
|
+
# @!attribute [r] method
|
|
870
|
+
# HTTP method (get, post, put, patch, delete, options, head)
|
|
871
|
+
# @return [Symbol] HTTP method as lowercase symbol
|
|
872
|
+
|
|
873
|
+
attr_reader :method
|
|
874
|
+
|
|
875
|
+
# @!attribute [r] path
|
|
876
|
+
# URL path (e.g., "/users", "/users/:id")
|
|
877
|
+
# @return [String] URL path
|
|
878
|
+
|
|
879
|
+
attr_reader :path
|
|
880
|
+
|
|
881
|
+
# @!attribute [r] scenarios
|
|
882
|
+
# Response scenarios for this API
|
|
883
|
+
# @return [Array<ScenarioNode>] list of scenario nodes
|
|
884
|
+
|
|
885
|
+
attr_reader :scenarios
|
|
886
|
+
|
|
887
|
+
# Create a new ApiDefinitionNode
|
|
888
|
+
#
|
|
889
|
+
# @param method [Symbol] HTTP method (:get, :post, etc.)
|
|
890
|
+
# @param path [String] URL path
|
|
891
|
+
# @param scenarios [Array<ScenarioNode>] response scenarios
|
|
892
|
+
# @param line [Integer, nil] source line number
|
|
893
|
+
#
|
|
894
|
+
|
|
895
|
+
def initialize(method:, path:, scenarios: [], line: nil)
|
|
896
|
+
super(line: line)
|
|
897
|
+
@method = method
|
|
898
|
+
@path = path
|
|
899
|
+
@scenarios = scenarios
|
|
900
|
+
end
|
|
901
|
+
end
|
|
902
|
+
|
|
903
|
+
# Scenario node
|
|
904
|
+
#
|
|
905
|
+
# Represents a response scenario within an API definition.
|
|
906
|
+
# Scenarios define possible HTTP responses with status codes,
|
|
907
|
+
# probabilities, and response body shapes.
|
|
908
|
+
#
|
|
909
|
+
# @example DSL
|
|
910
|
+
# scenario Success:
|
|
911
|
+
# @status 200
|
|
912
|
+
# @probability 80%
|
|
913
|
+
# users: schema(:User).array
|
|
914
|
+
#
|
|
915
|
+
# @example AST
|
|
916
|
+
# ScenarioNode.new(
|
|
917
|
+
# name: "Success",
|
|
918
|
+
# directives: [StatusDirective.new(status_code: 200), ...],
|
|
919
|
+
# response_fields: [ResponseFieldNode.new(...)]
|
|
920
|
+
# )
|
|
921
|
+
#
|
|
922
|
+
|
|
923
|
+
class ScenarioNode < Node
|
|
924
|
+
|
|
925
|
+
# @!attribute [r] name
|
|
926
|
+
# Scenario name (e.g., "Success", "NotFound", "ValidationError")
|
|
927
|
+
# @return [String] scenario name
|
|
928
|
+
|
|
929
|
+
attr_reader :name
|
|
930
|
+
|
|
931
|
+
# @!attribute [r] directives
|
|
932
|
+
# HTTP directives (@status, @probability, @header, @latency)
|
|
933
|
+
# @return [Array<DirectiveNode>] list of directive nodes
|
|
934
|
+
|
|
935
|
+
attr_reader :directives
|
|
936
|
+
|
|
937
|
+
# @!attribute [r] response_fields
|
|
938
|
+
# Response body field definitions
|
|
939
|
+
# @return [Array<ResponseFieldNode>] list of response field nodes
|
|
940
|
+
|
|
941
|
+
attr_reader :response_fields
|
|
942
|
+
|
|
943
|
+
# Create a new ScenarioNode
|
|
944
|
+
#
|
|
945
|
+
# @param name [String] scenario name
|
|
946
|
+
# @param directives [Array<DirectiveNode>] HTTP directives
|
|
947
|
+
# @param response_fields [Array<ResponseFieldNode>] response fields
|
|
948
|
+
# @param line [Integer, nil] source line number
|
|
949
|
+
#
|
|
950
|
+
|
|
951
|
+
def initialize(name:, directives: [], response_fields: [], line: nil)
|
|
952
|
+
super(line: line)
|
|
953
|
+
@name = name
|
|
954
|
+
@directives = directives
|
|
955
|
+
@response_fields = response_fields
|
|
956
|
+
end
|
|
957
|
+
|
|
958
|
+
# Get the status code from directives
|
|
959
|
+
#
|
|
960
|
+
# @return [Integer, nil] HTTP status code or nil
|
|
961
|
+
#
|
|
962
|
+
|
|
963
|
+
def status_code
|
|
964
|
+
status_directive = directives.find { |d| d.is_a?(StatusDirective) }
|
|
965
|
+
status_directive&.status_code
|
|
966
|
+
end
|
|
967
|
+
|
|
968
|
+
# Get the probability from directives
|
|
969
|
+
#
|
|
970
|
+
# @return [Integer, nil] probability percentage or nil
|
|
971
|
+
#
|
|
972
|
+
|
|
973
|
+
def probability
|
|
974
|
+
prob_directive = directives.find { |d| d.is_a?(ProbabilityDirective) }
|
|
975
|
+
prob_directive&.percentage
|
|
976
|
+
end
|
|
977
|
+
end
|
|
978
|
+
|
|
979
|
+
# Response field node
|
|
980
|
+
#
|
|
981
|
+
# Represents a field in a scenario's response body.
|
|
982
|
+
# Similar to FieldNode but specifically for response definitions.
|
|
983
|
+
#
|
|
984
|
+
# @example DSL
|
|
985
|
+
# users: schema(:User).array
|
|
986
|
+
# message: string
|
|
987
|
+
#
|
|
988
|
+
# @example AST
|
|
989
|
+
# ResponseFieldNode.new(
|
|
990
|
+
# name: "users",
|
|
991
|
+
# type_node: SchemaReferenceNode.new(schema_name: "User", array: true)
|
|
992
|
+
# )
|
|
993
|
+
#
|
|
994
|
+
|
|
995
|
+
class ResponseFieldNode < Node
|
|
996
|
+
|
|
997
|
+
# @!attribute [r] name
|
|
998
|
+
# Field name
|
|
999
|
+
# @return [String] field name
|
|
1000
|
+
|
|
1001
|
+
attr_reader :name
|
|
1002
|
+
|
|
1003
|
+
# @!attribute [r] type_node
|
|
1004
|
+
# Type definition
|
|
1005
|
+
# @return [TypeNode, SchemaReferenceNode] type node
|
|
1006
|
+
|
|
1007
|
+
attr_reader :type_node
|
|
1008
|
+
|
|
1009
|
+
# Create a new ResponseFieldNode
|
|
1010
|
+
#
|
|
1011
|
+
# @param name [String] field name
|
|
1012
|
+
# @param type_node [TypeNode] type definition
|
|
1013
|
+
# @param line [Integer, nil] source line number
|
|
1014
|
+
#
|
|
1015
|
+
|
|
1016
|
+
def initialize(name:, type_node:, line: nil)
|
|
1017
|
+
super(line: line)
|
|
1018
|
+
@name = name
|
|
1019
|
+
@type_node = type_node
|
|
1020
|
+
end
|
|
1021
|
+
end
|
|
1022
|
+
|
|
1023
|
+
# Schema reference node
|
|
1024
|
+
#
|
|
1025
|
+
# Represents an explicit schema reference using the syntax:
|
|
1026
|
+
# `schema(:Name)` or `schema(:Name).array`
|
|
1027
|
+
#
|
|
1028
|
+
# This is the ONLY valid way to reference schemas in the new DSL.
|
|
1029
|
+
# The system must NEVER infer schemas from capitalization.
|
|
1030
|
+
#
|
|
1031
|
+
# @example DSL
|
|
1032
|
+
# user: schema(:User)
|
|
1033
|
+
# users: schema(:User).array
|
|
1034
|
+
# users: schema(:User).array(1..10)
|
|
1035
|
+
#
|
|
1036
|
+
# @example AST
|
|
1037
|
+
# SchemaReferenceNode.new(schema_name: "User")
|
|
1038
|
+
# SchemaReferenceNode.new(schema_name: "User", array: true)
|
|
1039
|
+
# SchemaReferenceNode.new(schema_name: "User", array: true, array_range: 1..10)
|
|
1040
|
+
#
|
|
1041
|
+
|
|
1042
|
+
class SchemaReferenceNode < TypeNode
|
|
1043
|
+
|
|
1044
|
+
# @!attribute [r] schema_name
|
|
1045
|
+
# Name of the referenced schema
|
|
1046
|
+
# @return [String] schema name
|
|
1047
|
+
|
|
1048
|
+
attr_reader :schema_name
|
|
1049
|
+
|
|
1050
|
+
# @!attribute [r] array
|
|
1051
|
+
# Whether this is an array of the schema type
|
|
1052
|
+
# @return [Boolean] true if array
|
|
1053
|
+
|
|
1054
|
+
attr_reader :array
|
|
1055
|
+
|
|
1056
|
+
# @!attribute [r] array_range
|
|
1057
|
+
# Optional range for array size
|
|
1058
|
+
# @return [Range, nil] size range or nil
|
|
1059
|
+
|
|
1060
|
+
attr_reader :array_range
|
|
1061
|
+
|
|
1062
|
+
# @!attribute [r] optional
|
|
1063
|
+
# Whether this reference is optional
|
|
1064
|
+
# @return [Boolean] true if optional
|
|
1065
|
+
|
|
1066
|
+
attr_reader :optional
|
|
1067
|
+
|
|
1068
|
+
# Create a new SchemaReferenceNode
|
|
1069
|
+
#
|
|
1070
|
+
# @param schema_name [String] name of the referenced schema
|
|
1071
|
+
# @param array [Boolean] whether this is an array
|
|
1072
|
+
# @param array_range [Range, nil] optional array size range
|
|
1073
|
+
# @param optional [Boolean] whether the reference is optional
|
|
1074
|
+
# @param line [Integer, nil] source line number
|
|
1075
|
+
#
|
|
1076
|
+
|
|
1077
|
+
def initialize(schema_name:, array: false, array_range: nil, optional: false, line: nil)
|
|
1078
|
+
super(
|
|
1079
|
+
name: "schema_ref",
|
|
1080
|
+
arguments: {
|
|
1081
|
+
schema_name: schema_name,
|
|
1082
|
+
array: array,
|
|
1083
|
+
array_range: array_range
|
|
1084
|
+
},
|
|
1085
|
+
nullable: optional,
|
|
1086
|
+
line: line
|
|
1087
|
+
)
|
|
1088
|
+
@schema_name = schema_name
|
|
1089
|
+
@array = array
|
|
1090
|
+
@array_range = array_range
|
|
1091
|
+
@optional = optional
|
|
1092
|
+
end
|
|
1093
|
+
|
|
1094
|
+
# Check if this is an array reference
|
|
1095
|
+
#
|
|
1096
|
+
# @return [Boolean] true if array
|
|
1097
|
+
#
|
|
1098
|
+
|
|
1099
|
+
def array?
|
|
1100
|
+
@array
|
|
1101
|
+
end
|
|
1102
|
+
|
|
1103
|
+
# Check if this is an optional reference
|
|
1104
|
+
#
|
|
1105
|
+
# @return [Boolean] true if optional
|
|
1106
|
+
#
|
|
1107
|
+
|
|
1108
|
+
def optional?
|
|
1109
|
+
@optional
|
|
1110
|
+
end
|
|
1111
|
+
end
|
|
1112
|
+
|
|
1113
|
+
# =========================================================================
|
|
1114
|
+
# Directive Nodes for Scenarios
|
|
1115
|
+
# =========================================================================
|
|
1116
|
+
|
|
1117
|
+
# Base class for directive nodes
|
|
1118
|
+
#
|
|
1119
|
+
# Directives are scenario-level declarations that control HTTP behavior.
|
|
1120
|
+
# All directives start with @ in the DSL.
|
|
1121
|
+
#
|
|
1122
|
+
|
|
1123
|
+
class DirectiveNode < Node
|
|
1124
|
+
end
|
|
1125
|
+
|
|
1126
|
+
# Status directive
|
|
1127
|
+
#
|
|
1128
|
+
# Sets the HTTP status code for a scenario.
|
|
1129
|
+
#
|
|
1130
|
+
# @example DSL
|
|
1131
|
+
# @status 200
|
|
1132
|
+
# @status 404
|
|
1133
|
+
#
|
|
1134
|
+
|
|
1135
|
+
class StatusDirective < DirectiveNode
|
|
1136
|
+
|
|
1137
|
+
# @!attribute [r] status_code
|
|
1138
|
+
# HTTP status code
|
|
1139
|
+
# @return [Integer] status code (100-599)
|
|
1140
|
+
|
|
1141
|
+
attr_reader :status_code
|
|
1142
|
+
|
|
1143
|
+
# Create a new StatusDirective
|
|
1144
|
+
#
|
|
1145
|
+
# @param status_code [Integer] HTTP status code
|
|
1146
|
+
# @param line [Integer, nil] source line number
|
|
1147
|
+
#
|
|
1148
|
+
|
|
1149
|
+
def initialize(status_code:, line: nil)
|
|
1150
|
+
super(line: line)
|
|
1151
|
+
@status_code = status_code
|
|
1152
|
+
end
|
|
1153
|
+
end
|
|
1154
|
+
|
|
1155
|
+
# Probability directive
|
|
1156
|
+
#
|
|
1157
|
+
# Sets the probability of this scenario being selected.
|
|
1158
|
+
#
|
|
1159
|
+
# @example DSL
|
|
1160
|
+
# @probability 80%
|
|
1161
|
+
#
|
|
1162
|
+
|
|
1163
|
+
class ProbabilityDirective < DirectiveNode
|
|
1164
|
+
|
|
1165
|
+
# @!attribute [r] percentage
|
|
1166
|
+
# Probability percentage (0-100)
|
|
1167
|
+
# @return [Integer] percentage
|
|
1168
|
+
|
|
1169
|
+
attr_reader :percentage
|
|
1170
|
+
|
|
1171
|
+
# Create a new ProbabilityDirective
|
|
1172
|
+
#
|
|
1173
|
+
# @param percentage [Integer] probability percentage
|
|
1174
|
+
# @param line [Integer, nil] source line number
|
|
1175
|
+
#
|
|
1176
|
+
|
|
1177
|
+
def initialize(percentage:, line: nil)
|
|
1178
|
+
super(line: line)
|
|
1179
|
+
@percentage = percentage
|
|
1180
|
+
end
|
|
1181
|
+
end
|
|
1182
|
+
|
|
1183
|
+
# Latency directive
|
|
1184
|
+
#
|
|
1185
|
+
# Sets simulated response latency for a scenario.
|
|
1186
|
+
#
|
|
1187
|
+
# @example DSL
|
|
1188
|
+
# @latency 100..500ms
|
|
1189
|
+
#
|
|
1190
|
+
|
|
1191
|
+
class LatencyDirective < DirectiveNode
|
|
1192
|
+
|
|
1193
|
+
# @!attribute [r] min_ms
|
|
1194
|
+
# Minimum latency in milliseconds
|
|
1195
|
+
# @return [Integer] minimum latency
|
|
1196
|
+
|
|
1197
|
+
attr_reader :min_ms
|
|
1198
|
+
|
|
1199
|
+
# @!attribute [r] max_ms
|
|
1200
|
+
# Maximum latency in milliseconds
|
|
1201
|
+
# @return [Integer] maximum latency
|
|
1202
|
+
|
|
1203
|
+
attr_reader :max_ms
|
|
1204
|
+
|
|
1205
|
+
# Create a new LatencyDirective
|
|
1206
|
+
#
|
|
1207
|
+
# @param min_ms [Integer] minimum latency
|
|
1208
|
+
# @param max_ms [Integer] maximum latency
|
|
1209
|
+
# @param line [Integer, nil] source line number
|
|
1210
|
+
#
|
|
1211
|
+
|
|
1212
|
+
def initialize(min_ms:, max_ms:, line: nil)
|
|
1213
|
+
super(line: line)
|
|
1214
|
+
@min_ms = min_ms
|
|
1215
|
+
@max_ms = max_ms
|
|
1216
|
+
end
|
|
1217
|
+
end
|
|
1218
|
+
|
|
1219
|
+
# Header directive
|
|
1220
|
+
#
|
|
1221
|
+
# Sets a custom HTTP header for the response.
|
|
1222
|
+
#
|
|
1223
|
+
# @example DSL
|
|
1224
|
+
# @header Content-Type: application/json
|
|
1225
|
+
# @header X-Custom: value
|
|
1226
|
+
#
|
|
1227
|
+
|
|
1228
|
+
class HeaderDirective < DirectiveNode
|
|
1229
|
+
|
|
1230
|
+
# @!attribute [r] key
|
|
1231
|
+
# Header name
|
|
1232
|
+
# @return [String] header key
|
|
1233
|
+
|
|
1234
|
+
attr_reader :key
|
|
1235
|
+
|
|
1236
|
+
# @!attribute [r] value
|
|
1237
|
+
# Header value
|
|
1238
|
+
# @return [String] header value
|
|
1239
|
+
|
|
1240
|
+
attr_reader :value
|
|
1241
|
+
|
|
1242
|
+
# Create a new HeaderDirective
|
|
1243
|
+
#
|
|
1244
|
+
# @param key [String] header name
|
|
1245
|
+
# @param value [String] header value
|
|
1246
|
+
# @param line [Integer, nil] source line number
|
|
1247
|
+
#
|
|
1248
|
+
|
|
1249
|
+
def initialize(key:, value:, line: nil)
|
|
1250
|
+
super(line: line)
|
|
1251
|
+
@key = key
|
|
1252
|
+
@value = value
|
|
1253
|
+
end
|
|
1254
|
+
end
|
|
1255
|
+
|
|
1256
|
+
# Method directive
|
|
1257
|
+
#
|
|
1258
|
+
# Associates a scenario with specific HTTP methods.
|
|
1259
|
+
# Used in the legacy response scenarios format.
|
|
1260
|
+
#
|
|
1261
|
+
# @example DSL
|
|
1262
|
+
# @method GET
|
|
1263
|
+
# @method POST
|
|
1264
|
+
#
|
|
1265
|
+
|
|
1266
|
+
class MethodDirective < DirectiveNode
|
|
1267
|
+
|
|
1268
|
+
# @!attribute [r] http_method
|
|
1269
|
+
# HTTP method
|
|
1270
|
+
# @return [Symbol] http method as lowercase symbol
|
|
1271
|
+
|
|
1272
|
+
attr_reader :http_method
|
|
1273
|
+
|
|
1274
|
+
# Create a new MethodDirective
|
|
1275
|
+
#
|
|
1276
|
+
# @param http_method [Symbol] HTTP method
|
|
1277
|
+
# @param line [Integer, nil] source line number
|
|
1278
|
+
#
|
|
1279
|
+
|
|
1280
|
+
def initialize(http_method:, line: nil)
|
|
1281
|
+
super(line: line)
|
|
1282
|
+
@http_method = http_method
|
|
1283
|
+
end
|
|
1284
|
+
end
|
|
1285
|
+
end
|
|
1286
|
+
end
|
|
1287
|
+
end
|