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