Skip to main content

template_model/
consts.rs

1use crate::template_section::TopLevelSection;
2
3pub const PSEUDO_PREFIX: &str = "AWS::";
4pub const PSEUDO_NO_VALUE: &str = "AWS::NoValue";
5pub const PSEUDO_ACCOUNT_ID: &str = "AWS::AccountId";
6pub const PSEUDO_NOTIFICATION_ARNS: &str = "AWS::NotificationARNs";
7pub const PSEUDO_PARTITION: &str = "AWS::Partition";
8pub const PSEUDO_REGION: &str = "AWS::Region";
9pub const PSEUDO_STACK_ID: &str = "AWS::StackId";
10pub const PSEUDO_STACK_NAME: &str = "AWS::StackName";
11pub const PSEUDO_URL_SUFFIX: &str = "AWS::URLSuffix";
12
13pub const PSEUDO_PARAMETERS: &[&str] = &[
14    PSEUDO_ACCOUNT_ID,
15    PSEUDO_NOTIFICATION_ARNS,
16    PSEUDO_NO_VALUE,
17    PSEUDO_PARTITION,
18    PSEUDO_REGION,
19    PSEUDO_STACK_ID,
20    PSEUDO_STACK_NAME,
21    PSEUDO_URL_SUFFIX,
22];
23
24pub const DEFAULT_ACCOUNT_ID: &str = "123456789012";
25pub const DEFAULT_STACK_NAME: &str = "teststack";
26
27// Section keys derive from the shared `TopLevelSection` enum - the single
28// definition of the documented template sections. `Globals` is SAM-only and
29// not part of the documented template anatomy, so it stays a plain constant.
30pub const SECTION_PARAMETERS: &str = TopLevelSection::Parameters.name();
31pub const SECTION_MAPPINGS: &str = TopLevelSection::Mappings.name();
32pub const SECTION_CONDITIONS: &str = TopLevelSection::Conditions.name();
33pub const SECTION_RESOURCES: &str = TopLevelSection::Resources.name();
34pub const SECTION_OUTPUTS: &str = TopLevelSection::Outputs.name();
35pub const SECTION_RULES: &str = TopLevelSection::Rules.name();
36pub const SECTION_METADATA: &str = TopLevelSection::Metadata.name();
37pub const SECTION_GLOBALS: &str = "Globals";
38pub const SECTION_FORMAT_VERSION: &str = TopLevelSection::FormatVersion.name();
39pub const SECTION_DESCRIPTION: &str = TopLevelSection::Description.name();
40pub const SECTION_TRANSFORM: &str = TopLevelSection::Transform.name();
41
42pub const KEY_TYPE: &str = "Type";
43pub const KEY_CONDITION: &str = "Condition";
44pub const KEY_PROPERTIES: &str = "Properties";
45pub const KEY_DEPENDS_ON: &str = "DependsOn";
46pub const KEY_DELETION_POLICY: &str = "DeletionPolicy";
47pub const KEY_UPDATE_REPLACE_POLICY: &str = "UpdateReplacePolicy";
48pub const KEY_UPDATE_POLICY: &str = "UpdatePolicy";
49pub const KEY_CREATION_POLICY: &str = "CreationPolicy";
50pub const KEY_METADATA: &str = "Metadata";
51
52pub const KEY_DEFAULT: &str = "Default";
53pub const KEY_ALLOWED_VALUES: &str = "AllowedValues";
54pub const KEY_ALLOWED_PATTERN: &str = "AllowedPattern";
55pub const KEY_NO_ECHO: &str = "NoEcho";
56pub const KEY_MIN_LENGTH: &str = "MinLength";
57pub const KEY_MAX_LENGTH: &str = "MaxLength";
58pub const KEY_MIN_VALUE: &str = "MinValue";
59pub const KEY_MAX_VALUE: &str = "MaxValue";
60
61pub const KEY_VALUE: &str = "Value";
62pub const KEY_EXPORT: &str = "Export";
63pub const KEY_NAME: &str = "Name";
64pub const KEY_CONSTRAINT_DESCRIPTION: &str = "ConstraintDescription";
65
66/// Optional fourth element of `Fn::FindInMap`: `{ "DefaultValue": ... }`.
67pub const KEY_DEFAULT_VALUE: &str = "DefaultValue";
68
69/// Argument keys of `Fn::GetStackOutput`: `StackName` and `OutputName` are
70/// required; `Region` and `RoleArn` are optional. No other keys are permitted.
71pub const KEY_STACK_NAME: &str = "StackName";
72pub const KEY_OUTPUT_NAME: &str = "OutputName";
73pub const KEY_REGION: &str = "Region";
74pub const KEY_ROLE_ARN: &str = "RoleArn";
75
76pub const SAM_SERVERLESS_TYPE_PREFIX: &str = "AWS::Serverless::";
77pub const SAM_FUNCTION_TYPE: &str = "AWS::Serverless::Function";
78pub const SAM_API_TYPE: &str = "AWS::Serverless::Api";
79pub const SAM_HTTP_API_TYPE: &str = "AWS::Serverless::HttpApi";
80pub const SAM_SIMPLE_TABLE_TYPE: &str = "AWS::Serverless::SimpleTable";
81pub const SAM_LAYER_VERSION_TYPE: &str = "AWS::Serverless::LayerVersion";
82pub const SAM_APPLICATION_TYPE: &str = "AWS::Serverless::Application";
83pub const SAM_STATE_MACHINE_TYPE: &str = "AWS::Serverless::StateMachine";
84pub const SAM_CONNECTOR_TYPE: &str = "AWS::Serverless::Connector";
85pub const SAM_GRAPHQL_API_TYPE: &str = "AWS::Serverless::GraphQLApi";
86
87pub const DYNAMODB_TABLE_TYPE: &str = "AWS::DynamoDB::Table";
88pub const CLOUDFORMATION_STACK_TYPE: &str = "AWS::CloudFormation::Stack";
89
90/// Returns the CloudFormation resource type a transform-backed shorthand
91/// deploys as when that type affects cross-resource semantics.
92pub fn effective_deployed_resource_type(resource_type: &str) -> &str {
93    match resource_type {
94        SAM_SIMPLE_TABLE_TYPE => DYNAMODB_TABLE_TYPE,
95        SAM_APPLICATION_TYPE => CLOUDFORMATION_STACK_TYPE,
96        _ => resource_type,
97    }
98}
99pub const SAM_EVENT_TYPE_API: &str = "Api";
100pub const SAM_EVENT_TYPE_HTTP_API: &str = "HttpApi";
101pub const SAM_EVENT_TYPE_SCHEDULE: &str = "Schedule";
102pub const SAM_IMPLICIT_REST_API: &str = "ServerlessRestApi";
103pub const SAM_IMPLICIT_HTTP_API: &str = "ServerlessHttpApi";
104/// The SAM transform gives the implicit REST API a stage named `Prod`, whose
105/// logical id is `ServerlessRestApi` + `Prod` + `Stage`.
106pub const SAM_IMPLICIT_REST_API_STAGE: &str = "ServerlessRestApiProdStage";
107pub const SAM_AUTO_PUBLISH_ALIAS: &str = "AutoPublishAlias";
108pub const SAM_LAYER_CONTENT_URI: &str = "ContentUri";
109pub const SAM_APPLICATION_LOCATION: &str = "Location";
110pub const SAM_FUNCTION_EVENTS: &str = "Events";
111pub const SAM_FUNCTION_ROLE: &str = "Role";
112pub const SAM_SCHEDULE_PROPERTY: &str = "Schedule";
113pub const SAM_API_STAGE_NAME: &str = "StageName";
114pub const SAM_DEFINITION: &str = "Definition";
115pub const SAM_DEFINITION_URI: &str = "DefinitionUri";
116pub const SAM_CONNECTOR_SOURCE: &str = "Source";
117pub const SAM_CONNECTOR_DESTINATION: &str = "Destination";
118pub const SAM_CONNECTOR_PERMISSIONS: &str = "Permissions";
119pub const SAM_GRAPHQL_AUTH: &str = "Auth";
120pub const SAM_SIMPLE_TABLE_PRIMARY_KEY: &str = "PrimaryKey";
121pub const SAM_PRIMARY_KEY_TYPE: &str = "Type";
122/// Valid DynamoDB attribute types for a `SimpleTable` PrimaryKey.
123pub const SAM_PRIMARY_KEY_TYPES: &[&str] = &["String", "Number", "Binary"];
124
125// Function properties involved in transform-error validation.
126pub const SAM_FUNCTION_PACKAGE_TYPE: &str = "PackageType";
127pub const SAM_FUNCTION_RUNTIME: &str = "Runtime";
128pub const SAM_FUNCTION_HANDLER: &str = "Handler";
129pub const SAM_FUNCTION_LAYERS: &str = "Layers";
130pub const SAM_FUNCTION_DEAD_LETTER_QUEUE: &str = "DeadLetterQueue";
131pub const SAM_FUNCTION_TARGET_ARN: &str = "TargetArn";
132pub const SAM_FUNCTION_PROVISIONED_CONCURRENCY: &str = "ProvisionedConcurrencyConfig";
133pub const SAM_FUNCTION_CAPACITY_PROVIDER: &str = "CapacityProviderConfig";
134pub const SAM_FUNCTION_VPC_CONFIG: &str = "VpcConfig";
135pub const SAM_FUNCTION_SCALING_CONFIG: &str = "FunctionScalingConfig";
136pub const SAM_FUNCTION_VERSION_DELETION_POLICY: &str = "VersionDeletionPolicy";
137pub const SAM_FUNCTION_IMAGE_URI: &str = "ImageUri";
138pub const SAM_FUNCTION_IMAGE_CONFIG: &str = "ImageConfig";
139pub const SAM_FUNCTION_URL_CONFIG: &str = "FunctionUrlConfig";
140pub const SAM_FUNCTION_URL_AUTH_TYPE: &str = "AuthType";
141pub const SAM_FUNCTION_DEPLOYMENT_PREFERENCE: &str = "DeploymentPreference";
142pub const SAM_PACKAGE_TYPE_ZIP: &str = "Zip";
143pub const SAM_PACKAGE_TYPE_IMAGE: &str = "Image";
144/// Valid DeadLetterQueue target types.
145pub const SAM_DLQ_TYPES: &[&str] = &["SQS", "SNS"];
146/// Valid FunctionUrlConfig auth types.
147pub const SAM_FUNCTION_URL_AUTH_TYPES: &[&str] = &["AWS_IAM", "NONE"];
148
149// LayerVersion properties involved in transform-error validation.
150pub const SAM_LAYER_RETENTION_POLICY: &str = "RetentionPolicy";
151pub const SAM_LAYER_COMPATIBLE_ARCHITECTURES: &str = "CompatibleArchitectures";
152/// Valid LayerVersion RetentionPolicy values.
153pub const SAM_LAYER_RETENTION_POLICIES: &[&str] = &["Retain", "Delete"];
154/// Valid Lambda architectures (LayerVersion CompatibleArchitectures).
155pub const SAM_ARCHITECTURES: &[&str] = &["x86_64", "arm64"];
156
157/// Maps each `Globals` template-section key to the SAM resource type whose
158/// defaults it carries.
159pub const SAM_GLOBALS_TYPE_MAP: &[(&str, &str)] = &[
160    (SAM_FUNCTION_GLOBALS_KEY, SAM_FUNCTION_TYPE),
161    (SAM_API_GLOBALS_KEY, SAM_API_TYPE),
162    (SAM_HTTP_API_GLOBALS_KEY, SAM_HTTP_API_TYPE),
163    (SAM_SIMPLE_TABLE_GLOBALS_KEY, SAM_SIMPLE_TABLE_TYPE),
164];
165
166// Keys under the `Globals` template section, each naming the SAM resource type
167// whose defaults follow (see `SAM_GLOBALS_TYPE_MAP`).
168pub const SAM_FUNCTION_GLOBALS_KEY: &str = "Function";
169pub const SAM_API_GLOBALS_KEY: &str = "Api";
170pub const SAM_HTTP_API_GLOBALS_KEY: &str = "HttpApi";
171pub const SAM_SIMPLE_TABLE_GLOBALS_KEY: &str = "SimpleTable";
172
173pub const CDK_METADATA_TYPE: &str = "AWS::CDK::Metadata";
174
175pub const OUTPUT_PSEUDO_RESOURCE_PREFIX: &str = "__output__";
176pub const OUTPUTS_PSEUDO_RESOURCE: &str = "__outputs__";
177
178/// Sentinel value used by the satisfiability search to represent "any value
179/// other than the literals the parameter is compared against". Added to the
180/// candidate-value set of a parameter (or pseudo-parameter) without an explicit
181/// `AllowedValues` list or override, so the SAT solver treats the symbol as a
182/// free variable that can also disagree with every literal it is compared to.
183/// Without this sentinel a parameter compared against a single literal would be
184/// pinned to that literal and the SAT solver would mark the negative branch
185/// unreachable.
186pub const PARAM_UNKNOWN_SENTINEL: &str = "__unknown__";
187
188pub const RULE_PSEUDO_RESOURCE_PREFIX: &str = "__rule__";
189
190pub const MAX_TEMPLATE_SIZE_BYTES: usize = 10 * 1024 * 1024;
191
192/// Maximum number of distinct (value, condition-assignment) scenarios a single
193/// value may resolve into before that value's scenario enumeration is
194/// truncated. A value composed from or gated by many conditions/parameters can
195/// take many concrete forms; this bounds that set so per-scenario rule
196/// evaluation stays bounded. This is a *per-value* cap only - the cumulative
197/// scenario work across an entire validation is bounded separately by
198/// `MAX_TOTAL_SCENARIO_COMBINATIONS`. Sits above `MAX_ENUM_EXPANSION` (per-value
199/// variant expansion) and below the parameter/satisfiability bounds.
200pub const MAX_SCENARIO_COMBINATIONS: usize = 262_144;
201
202/// Cumulative scenario-expansion budget across all values resolved during a
203/// single validation. `MAX_SCENARIO_COMBINATIONS` bounds one value's expansion,
204/// but scenario resolution runs per resource property and per rule, so the
205/// *number* of expansions is itself unbounded on adversarial input - a template
206/// packed with many heavily-gated values would otherwise drive up to
207/// `num_values * MAX_SCENARIO_COMBINATIONS` of work with no global ceiling. This
208/// caps the total scenarios materialized for one model; once it is reached,
209/// further scenario resolution yields no scenarios rather than continuing to
210/// expand. Sized far above the worst legitimate template - real values resolve
211/// to a single scenario and conditional ones to a handful - while still cutting
212/// off a pathological blow-up. Mirrors the SAT path's `MAX_TOTAL_SAT_ITERATIONS`
213/// (here 128x the per-value cap).
214pub const MAX_TOTAL_SCENARIO_COMBINATIONS: u64 = 33_554_432;
215
216/// Largest joint product of a container's member scenarios that is enumerated in
217/// full. A list or map whose members are gated by N independent conditions has
218/// 2^N joint assignments; past this many, the product is replaced by an
219/// alternative-covering set (see `resolved_value::cover_cartesian_scenarios`).
220/// The covering set includes every member scenario and pair of condition states
221/// while growing linearly in the sum of member alternatives. Matches
222/// `MAX_ENUM_EXPANSION` so a single value never materializes more joint copies
223/// than enumerated variants.
224pub const MAX_COMPLETE_SCENARIO_PRODUCT: usize = 4_096;
225
226/// Largest number of `ResolvedValue` nodes a fully enumerated joint product may
227/// materialize (scenario count times nodes per scenario). Bounds the memory of
228/// one expansion by size rather than count: a product of large member values
229/// switches to the covering set before `MAX_COMPLETE_SCENARIO_PRODUCT` would.
230pub const MAX_SCENARIO_PRODUCT_NODES: usize = 1 << 20;
231
232pub const MAX_RESOLVE_DEPTH: u32 = 512;
233
234/// Maximum structural nesting depth the YAML loader permits before rejecting
235/// the document with a parse error. Each `MappingStart` or `SequenceStart` event
236/// increments the depth; the corresponding end event decrements it. Once the
237/// ceiling is reached, the loader sets a structural parse error and suppresses
238/// further tree construction without attempting to build or drop a recursively
239/// deep `Yaml` value.
240///
241/// The bound matches the existing intrinsic-resolution ceiling and leaves ample
242/// room for deeply composed templates while preventing a malicious YAML document
243/// from reaching unbounded recursive tree passes. JSON parsing is independently
244/// bounded by `serde_json`'s built-in recursion limit.
245pub const MAX_YAML_NESTING_DEPTH: usize = 512;
246
247/// Maximum number of YAML nodes that alias resolution may copy into one
248/// document. An alias is resolved by copying its anchored value, so nested
249/// aliases multiply: eight levels of nine aliases over a nine-element list
250/// expand a few hundred bytes into 9^8 scalars. The bound counts copied nodes
251/// across the whole document and rejects the template with a located parse
252/// error once exceeded. It is far above any template that uses anchors to share
253/// configuration (a 10 MiB template cannot author this many nodes directly) and
254/// well below the point where the copies exhaust memory.
255pub const MAX_YAML_ALIAS_EXPANSION_NODES: usize = 1 << 20;
256
257/// Cumulative work budget for `Fn::ForEach` transform expansion across all
258/// sections of a single template. Charged for every loop iteration (one unit per
259/// element emitted) and every recursive body-traversal node visited. Once
260/// exhausted, expansion stops immediately and emits a single visible transform
261/// diagnostic - no further materialization occurs, no silent truncation.
262///
263/// The limit provides enough headroom for legitimate templates (CloudFormation
264/// caps a template at 500 resources; a loop producing 500 entries with moderate
265/// body complexity costs well under this budget) while cutting off branch-explosion
266/// attacks where nested loops generate combinatorial output.
267pub const MAX_FOREACH_EXPANSION_WORK: u64 = 100_000;
268
269/// Maximum number of concrete variants a single value may expand to during
270/// intrinsic resolution (e.g. an `Fn::Join` over enumerated elements). Bounds
271/// per-value combinatorial blow-up and the memory a resolved value holds;
272/// beyond it the expansion is truncated. The narrowest analysis bound - it
273/// operates on a single value.
274pub const MAX_ENUM_EXPANSION: usize = 4_096;
275
276/// Maximum nesting depth walked while building an expression fingerprint. A
277/// subtree deeper than this yields no fingerprint at all rather than a truncated
278/// one, because a truncated fingerprint could be shared by two expressions that
279/// differ only below the cut.
280pub const MAX_FINGERPRINT_DEPTH: u32 = 64;
281
282/// Schema scenario-assignment expansion cap per property group. Used in the
283/// schema-validator to bound the per-group satisfiability search.
284pub const MAX_SCHEMA_SCENARIO_ASSIGNMENTS: usize = 256;
285
286/// Schema scenario merge-attempt budget per property group. Used in the
287/// schema-validator to bound the pairwise merge pass.
288pub const MAX_SCHEMA_SCENARIO_MERGE_ATTEMPTS: usize = 4_096;
289
290/// Schema recursive match depth. Used in the schema-validator to bound
291/// recursive schema property traversal.
292pub const MAX_SCHEMA_MATCH_DEPTH: usize = 16;
293
294/// Required-property combination expansion cap. Used in the schema-validator
295/// to bound the cross-product of requiredXor/requiredOr groups.
296pub const MAX_REQUIRED_PROPERTY_COMBINATIONS: usize = 256;
297
298/// Per-query budget for a single satisfiability search: the maximum number of
299/// evaluation steps `ConditionModel::is_satisfiable` performs before returning a
300/// conservative `true`. This is the *effective* per-query work bound -
301/// `MAX_PARAM_COMBINATIONS` is only an O(1) pre-filter on the size of a query's
302/// parameter space, so a query that passes that pre-filter is still explored only
303/// up to this budget.
304///
305/// A query searches parameter assignments, abandons a branch as soon as the values
306/// bound so far decide an assumed condition against its assumption, and derives
307/// what the assumptions force on conditions the parameters leave undetermined - so
308/// the steps a real template needs are far fewer than its parameter space is wide.
309/// Measured across templates built to be expensive on purpose - two hundred
310/// conditions layered over a few shared pseudo-parameters, two hundred independent
311/// flags, six parameters with twenty values each combined six at a time - and a
312/// real deployment template with over two hundred conditions and ninety
313/// parameters, no single query exceeded ~18K steps. This budget sits roughly fifty
314/// times above that, so exactness is never traded away on a template anyone would
315/// write, while one query stays bounded to milliseconds.
316///
317/// It does *not* guarantee exact enumeration for every query: one whose branches
318/// cannot be pruned and whose parameter space is near the pre-filter cap can cost
319/// more than this budget and fall back to the conservative `true`. That is
320/// acceptable because such a query is pathological, not realistic.
321/// `MAX_TOTAL_SAT_ITERATIONS` then bounds the sum of these per-query budgets
322/// across a whole validation.
323pub const MAX_SAT_ITERATIONS: u64 = 1_000_000;
324
325/// Largest parameter space a single satisfiability query will search. The query
326/// searches assignments of concrete values to the parameters its conditions read;
327/// the number of such assignments is the product of each parameter's candidate
328/// values, and so is exponential in the number of distinct parameters. When that
329/// product exceeds this cap the query returns a conservative `true` without
330/// searching - see `ConditionModel::is_satisfiable` for the exact
331/// diagnostic-direction guarantee, including that through a negated use
332/// (`condition_implies`) a conservative `true` can surface an extra false-positive
333/// diagnostic.
334///
335/// Because of that false-positive risk the cap is sized generously: 2^20 covers a
336/// query reading up to twenty binary parameters (or, e.g., eight five-valued
337/// ones), well beyond any realistic condition - a condition rarely reads more
338/// than a handful of parameters - so legitimate templates resolve exactly and
339/// never reach the conservative path. This cap is only an O(1) pre-filter on the
340/// size of the space; the per-query budget (`MAX_SAT_ITERATIONS`) bounds the work
341/// actually performed inside it, and the cumulative budget
342/// (`MAX_TOTAL_SAT_ITERATIONS`) bounds how much such work one validation can do
343/// in total.
344pub const MAX_PARAM_COMBINATIONS: u64 = 1_048_576;
345
346/// Cumulative satisfiability search budget across all queries of a single
347/// validation. `MAX_SAT_ITERATIONS` bounds one query, but the condition model
348/// issues a query per pairwise condition-compatibility check - quadratic in the
349/// condition count - plus per-resource and per-rule checks, so the *number* of
350/// queries is itself unbounded on adversarial input. This caps the total search
351/// work for one model so no template can drive validation into a denial of
352/// service.
353///
354/// This budget is what ultimately bounds how long one template can spend deciding
355/// conditions, so it is sized from measurement rather than from a round number. A
356/// real deployment template with over two hundred conditions and ninety
357/// parameters, whose quadratic pairwise pass is analyzed in full, consumes about 7M
358/// steps; the most expensive shape built on purpose - two hundred conditions all
359/// connected through three shared inputs, with resources gated on them - consumes
360/// about 17M. This budget sits about six times above that worst measured case:
361/// enough that a template anyone would write always resolves exactly, while the
362/// worst case adversarial input can reach stays in seconds rather than minutes.
363/// Raising it gives that latency ceiling away; lowering it starts costing precision
364/// on real templates.
365///
366/// Once the budget is spent, further queries fall back to the conservative
367/// "assume satisfiable" answer rather than being rejected, and reaching it is
368/// reported so a truncated analysis is never silent.
369pub const MAX_TOTAL_SAT_ITERATIONS: u64 = 100_000_000;
370
371pub const FORMAT_VERSION: &str = "2010-09-09";
372
373// Used by serialization.rs to encode unresolved values in diagnostic JSON.
374// Engines check these markers to detect dynamic/unresolvable content.
375
376pub const MARKER_DYNAMIC: &str = "__dynamic";
377pub const MARKER_REF: &str = "__ref";
378
379/// Prefixes of the `ResolvedValue::Dynamic` reason produced by a partially
380/// resolved `Fn::Sub` or `Fn::Join`, and the placeholders standing in for values
381/// the partial could not resolve. Defined once because the resolver writes them
382/// and length estimation reads them back: a partial that still carries a
383/// placeholder has no known length, and only a shared spelling keeps the reader
384/// and the writer from drifting apart.
385pub const SUB_PARTIAL_PREFIX: &str = "Sub:";
386pub const JOIN_PARTIAL_PREFIX: &str = "Join:";
387pub const UNRESOLVED_REF_PLACEHOLDER_PREFIX: &str = "{ref:";
388pub const UNRESOLVED_DYNAMIC_PLACEHOLDER: &str = "{dynamic}";
389pub const MARKER_CONDITIONAL: &str = "__conditional";
390pub const MARKER_INTRINSIC: &str = "__intrinsic";
391pub const MARKER_ENUM: &str = "__enum";
392pub const MARKER_PARAM_TYPE: &str = "__param_type";
393pub const MARKER_KIND: &str = "__kind";
394pub const MARKER_IF_TRUE: &str = "__if_true";
395pub const MARKER_IF_FALSE: &str = "__if_false";
396
397pub const KEY_RULE_CONDITION: &str = "RuleCondition";
398pub const KEY_ASSERTIONS: &str = "Assertions";
399pub const KEY_ASSERT: &str = "Assert";
400pub const KEY_ASSERT_DESCRIPTION: &str = "AssertDescription";
401
402pub const FN_PREFIX: &str = "Fn::";
403
404pub const FN_REF: &str = "Ref";
405pub const FN_GET_ATT: &str = "Fn::GetAtt";
406pub const FN_SUB: &str = "Fn::Sub";
407pub const FN_JOIN: &str = "Fn::Join";
408pub const FN_SELECT: &str = "Fn::Select";
409pub const FN_IF: &str = "Fn::If";
410pub const FN_FIND_IN_MAP: &str = "Fn::FindInMap";
411pub const FN_SPLIT: &str = "Fn::Split";
412pub const FN_BASE64: &str = "Fn::Base64";
413pub const FN_CIDR: &str = "Fn::Cidr";
414pub const FN_GET_AZS: &str = "Fn::GetAZs";
415pub const FN_GET_STACK_OUTPUT: &str = "Fn::GetStackOutput";
416pub const FN_IMPORT_VALUE: &str = "Fn::ImportValue";
417pub const FN_TRANSFORM: &str = "Fn::Transform";
418pub const FN_AND: &str = "Fn::And";
419pub const FN_OR: &str = "Fn::Or";
420pub const FN_NOT: &str = "Fn::Not";
421pub const FN_EQUALS: &str = "Fn::Equals";
422pub const FN_TO_JSON_STRING: &str = "Fn::ToJsonString";
423pub const FN_LENGTH: &str = "Fn::Length";
424pub const FN_FOR_EACH: &str = "Fn::ForEach";
425pub const FN_CONDITION: &str = "Condition";
426
427pub const FN_VALUE_OF: &str = "Fn::ValueOf";
428pub const FN_VALUE_OF_ALL: &str = "Fn::ValueOfAll";
429pub const FN_REF_ALL: &str = "Fn::RefAll";
430pub const FN_CONTAINS: &str = "Fn::Contains";
431pub const FN_EACH_MEMBER_EQUALS: &str = "Fn::EachMemberEquals";
432pub const FN_EACH_MEMBER_IN: &str = "Fn::EachMemberIn";
433
434pub const FN_FOR_EACH_KEY_PREFIX: &str = "Fn::ForEach::";
435
436/// Every intrinsic-function key that the parser can write into a node's build
437/// path. Path-based checks that ask "is this string nested inside a function?"
438/// must match against this list rather than the bare `Fn::` prefix: a user map
439/// key may legitimately start with `Fn::` (e.g. a Lambda environment variable
440/// named `Fn::Custom`) without being a function.
441pub const INTRINSIC_FN_PATH_SEGMENTS: &[&str] = &[
442    FN_GET_ATT,
443    FN_SUB,
444    FN_JOIN,
445    FN_SELECT,
446    FN_IF,
447    FN_FIND_IN_MAP,
448    FN_SPLIT,
449    FN_BASE64,
450    FN_CIDR,
451    FN_GET_AZS,
452    FN_GET_STACK_OUTPUT,
453    FN_IMPORT_VALUE,
454    FN_TRANSFORM,
455    FN_AND,
456    FN_OR,
457    FN_NOT,
458    FN_EQUALS,
459    FN_TO_JSON_STRING,
460    FN_LENGTH,
461    FN_FOR_EACH,
462    FN_VALUE_OF,
463    FN_VALUE_OF_ALL,
464    FN_REF_ALL,
465    FN_CONTAINS,
466    FN_EACH_MEMBER_EQUALS,
467    FN_EACH_MEMBER_IN,
468];
469
470/// Resource property paths where an `ssm-secure` dynamic reference is
471/// supported - the fixed set CloudFormation documents for secure-string
472/// resolution. Paths use the resource *type*
473/// (not the logical ID) and `*` for array indices.
474pub const SSM_SECURE_ALLOWED_PROPERTY_PATHS: &[&str] = &[
475    "Resources/AWS::DirectoryService::MicrosoftAD/Properties/Password",
476    "Resources/AWS::DirectoryService::SimpleAD/Properties/Password",
477    "Resources/AWS::ElastiCache::ReplicationGroup/Properties/AuthToken",
478    "Resources/AWS::IAM::User/Properties/LoginProfile/Password",
479    "Resources/AWS::KinesisFirehose::DeliveryStream/Properties/RedshiftDestinationConfiguration/Password",
480    "Resources/AWS::OpsWorks::App/Properties/AppSource/Password",
481    "Resources/AWS::OpsWorks::Stack/Properties/RdsDbInstances/*/DbPassword",
482    "Resources/AWS::OpsWorks::Stack/Properties/CustomCookbooksSource/Password",
483    "Resources/AWS::RDS::DBCluster/Properties/MasterUserPassword",
484    "Resources/AWS::RDS::DBInstance/Properties/MasterUserPassword",
485    "Resources/AWS::Redshift::Cluster/Properties/MasterUserPassword",
486];
487
488/// The YAML 1.1 merge key. A mapping entry `<<: <alias-or-list-of-aliases>` splices
489/// the referenced mapping(s) into the enclosing mapping, with explicit keys winning
490/// over merged ones and earlier merge sources winning over later ones.
491pub const YAML_MERGE_KEY: &str = "<<";
492
493// Short (bare) intrinsic names - the suffix after the `Fn::` prefix that appears in
494// YAML shorthand tags (`!GetAtt`) and in the serialized reference graph. `Ref` and
495// `Condition` have no `Fn::` form, so their short and long spellings coincide.
496pub const TAG_REF: &str = "Ref";
497pub const TAG_GET_ATT: &str = "GetAtt";
498pub const TAG_SUB: &str = "Sub";
499pub const TAG_JOIN: &str = "Join";
500pub const TAG_SELECT: &str = "Select";
501pub const TAG_IF: &str = "If";
502pub const TAG_FIND_IN_MAP: &str = "FindInMap";
503pub const TAG_SPLIT: &str = "Split";
504pub const TAG_BASE64: &str = "Base64";
505pub const TAG_CIDR: &str = "Cidr";
506pub const TAG_GET_AZS: &str = "GetAZs";
507pub const TAG_GET_STACK_OUTPUT: &str = "GetStackOutput";
508pub const TAG_IMPORT_VALUE: &str = "ImportValue";
509pub const TAG_TRANSFORM: &str = "Transform";
510pub const TAG_AND: &str = "And";
511pub const TAG_OR: &str = "Or";
512pub const TAG_NOT: &str = "Not";
513pub const TAG_EQUALS: &str = "Equals";
514pub const TAG_CONDITION: &str = "Condition";
515pub const TAG_TO_JSON_STRING: &str = "ToJsonString";
516pub const TAG_LENGTH: &str = "Length";
517pub const TAG_FOR_EACH: &str = "ForEach";
518pub const TAG_VALUE_OF: &str = "ValueOf";
519pub const TAG_VALUE_OF_ALL: &str = "ValueOfAll";
520pub const TAG_REF_ALL: &str = "RefAll";
521pub const TAG_CONTAINS: &str = "Contains";
522pub const TAG_EACH_MEMBER_EQUALS: &str = "EachMemberEquals";
523pub const TAG_EACH_MEMBER_IN: &str = "EachMemberIn";
524
525/// Display label for the expression form of `Fn::If` (the condition is itself an
526/// intrinsic rather than a named condition). This is an internal variant, not a
527/// CloudFormation tag, so it is deliberately absent from `SHORT_TAG_TO_FN_KEY`.
528pub const TAG_IF_EXPR: &str = "IfExpr";
529
530/// YAML shorthand tags map their bare suffix to the canonical `Fn::`-prefixed key
531/// used everywhere downstream. `Ref` and `Condition` map to themselves.
532pub const SHORT_TAG_TO_FN_KEY: &[(&str, &str)] = &[
533    (TAG_REF, FN_REF),
534    (TAG_GET_ATT, FN_GET_ATT),
535    (TAG_SUB, FN_SUB),
536    (TAG_JOIN, FN_JOIN),
537    (TAG_SELECT, FN_SELECT),
538    (TAG_IF, FN_IF),
539    (TAG_FIND_IN_MAP, FN_FIND_IN_MAP),
540    (TAG_SPLIT, FN_SPLIT),
541    (TAG_BASE64, FN_BASE64),
542    (TAG_CIDR, FN_CIDR),
543    (TAG_GET_AZS, FN_GET_AZS),
544    (TAG_GET_STACK_OUTPUT, FN_GET_STACK_OUTPUT),
545    (TAG_IMPORT_VALUE, FN_IMPORT_VALUE),
546    (TAG_TRANSFORM, FN_TRANSFORM),
547    (TAG_AND, FN_AND),
548    (TAG_OR, FN_OR),
549    (TAG_NOT, FN_NOT),
550    (TAG_EQUALS, FN_EQUALS),
551    (TAG_CONDITION, FN_CONDITION),
552    (TAG_TO_JSON_STRING, FN_TO_JSON_STRING),
553    (TAG_LENGTH, FN_LENGTH),
554    (TAG_FOR_EACH, FN_FOR_EACH),
555    (TAG_VALUE_OF, FN_VALUE_OF),
556    (TAG_VALUE_OF_ALL, FN_VALUE_OF_ALL),
557    (TAG_REF_ALL, FN_REF_ALL),
558    (TAG_CONTAINS, FN_CONTAINS),
559    (TAG_EACH_MEMBER_EQUALS, FN_EACH_MEMBER_EQUALS),
560    (TAG_EACH_MEMBER_IN, FN_EACH_MEMBER_IN),
561];
562
563/// Keys that identify a well-formed boolean condition expression when used
564/// as the sole key of a single-key mapping. Inputs to `Fn::And`, `Fn::Or`,
565/// and `Fn::Not` must be one of these.
566///
567/// Includes both Conditions-section intrinsics (`Fn::Equals`, `Fn::And`,
568/// `Fn::Or`, `Fn::Not`, `Condition`) and Rules-section boolean-producing
569/// intrinsics (`Fn::Contains`, `Fn::EachMemberEquals`, `Fn::EachMemberIn`).
570/// Section-placement constraints are enforced separately in `rules.rs` via
571/// `validate_allowed_functions`.
572pub const BOOLEAN_FN_KEYS: &[&str] =
573    &[FN_CONDITION, FN_EQUALS, FN_AND, FN_OR, FN_NOT, FN_CONTAINS, FN_EACH_MEMBER_EQUALS, FN_EACH_MEMBER_IN];
574
575/// Intrinsic functions whose output can stand in for an `Fn::Equals` argument.
576/// An `Fn::Equals` argument that is a single-key mapping must use one of these
577/// keys to be considered well-formed. An `Fn::Equals` operand must resolve to a
578/// scalar, so only the string/value-producing functions are permitted. Boolean
579/// and reference-shaped functions (`Fn::And`/`Fn::Or`/`Fn::Not`, a nested
580/// `Fn::Equals`, `Condition`, `Fn::GetAtt`, `Fn::GetAZs`, `Fn::ImportValue`,
581/// `Fn::Base64`, and the Rules-section membership functions) produce a
582/// non-scalar and are rejected here, matching CloudFormation's own restriction
583/// on comparison operands.
584pub const EQUALS_ARG_FN_KEYS: &[&str] =
585    &[FN_REF, FN_FIND_IN_MAP, FN_SUB, FN_JOIN, FN_SELECT, FN_SPLIT, FN_LENGTH, FN_TO_JSON_STRING];
586
587// Edge kind values used in the serialized reference graph.
588// These are distinct from FN_* names (e.g. EDGE_KIND_GET_ATT = "GetAtt" vs FN_GET_ATT = "Fn::GetAtt").
589pub const EDGE_KIND_REF: &str = "Ref";
590pub const EDGE_KIND_GET_ATT: &str = "GetAtt";
591pub const EDGE_KIND_SUB: &str = "Sub";
592pub const EDGE_KIND_DEPENDS_ON: &str = "DependsOn";
593pub const EDGE_KIND_SELECT: &str = "Select";
594pub const EDGE_KIND_CONDITION: &str = "Condition";
595
596// Serialized DiagnosticModel field keys (camelCase, produced by serde rename_all).
597pub const FIELD_RESOURCES: &str = "resources";
598pub const FIELD_PARAMETERS: &str = "parameters";
599pub const FIELD_CONDITIONS: &str = "conditions";
600pub const FIELD_MAPPINGS: &str = "mappings";
601pub const FIELD_OUTPUTS: &str = "outputs";
602pub const FIELD_EDGES: &str = "edges";
603pub const FIELD_TRANSFORMS: &str = "transforms";
604pub const FIELD_RESOURCE_TYPE: &str = "resourceType";
605pub const FIELD_PROPERTIES: &str = "properties";
606pub const FIELD_DEPENDS_ON: &str = "dependsOn";
607pub const FIELD_CONDITION: &str = "condition";
608pub const FIELD_OUTGOING_REFS: &str = "outgoingRefs";
609pub const FIELD_INCOMING_REFS: &str = "incomingRefs";
610pub const FIELD_DELETION_POLICY: &str = "deletionPolicy";
611pub const FIELD_UPDATE_REPLACE_POLICY: &str = "updateReplacePolicy";
612pub const FIELD_CREATION_POLICY: &str = "creationPolicy";
613pub const FIELD_UPDATE_POLICY: &str = "updatePolicy";
614pub const FIELD_KIND: &str = "kind";
615pub const FIELD_TARGET: &str = "target";
616pub const FIELD_SOURCE: &str = "source";
617pub const FIELD_SOURCE_PATH: &str = "sourcePath";
618pub const FIELD_ATTR: &str = "attr";
619pub const FIELD_CONDITION_CONTEXT: &str = "conditionContext";
620
621// CloudFormation transform identifiers.
622pub const TRANSFORM_LANGUAGE_EXTENSIONS: &str = "AWS::LanguageExtensions";
623pub const TRANSFORM_SERVERLESS: &str = "AWS::Serverless-2016-10-31";
624pub const TRANSFORM_INCLUDE: &str = "AWS::Include";
625
626// DeletionPolicy / UpdateReplacePolicy values.
627pub const POLICY_DELETE: &str = "Delete";
628pub const POLICY_RETAIN: &str = "Retain";
629pub const POLICY_SNAPSHOT: &str = "Snapshot";
630pub const POLICY_RETAIN_EXCEPT_ON_CREATE: &str = "RetainExceptOnCreate";
631
632// Convention prefix for encoding condition references inside Ref nodes.
633pub const CONDITION_REF_PREFIX: &str = "Condition:";
634
635pub const PARAM_TYPE_STRING: &str = "String";
636pub const PARAM_TYPE_NUMBER: &str = "Number";
637pub const PARAM_TYPE_COMMA_DELIMITED_LIST: &str = "CommaDelimitedList";
638pub const SSM_PARAMETER_VALUE_TYPE_PREFIX: &str = "AWS::SSM::Parameter::Value<";
639
640// SAM transform-error identity. The transform is applied while building the
641// semantic model, so this crate owns the rule ID and message prefix that mark
642// a failed transform; downstream layers gate on them because a failed SAM
643// transform stops CloudFormation before resource validation.
644pub const SAM_TRANSFORM_ERROR_RULE_ID: &str = "E0001";
645
646/// Message prefix shared by every SAM transform-error finding, regardless of
647/// which layer produced it.
648pub const SAM_TRANSFORM_ERROR_PREFIX: &str = "Error transforming template:";
649
650/// Returns `true` when `message` belongs to a SAM transform-error finding.
651pub fn is_sam_transform_error_message(message: &str) -> bool {
652    message.starts_with(SAM_TRANSFORM_ERROR_PREFIX)
653}