-module(caffeine_lang@codegen@datadog). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/caffeine_lang/codegen/datadog.gleam"). -export([resolve_indicators/1, window_to_timeframe/1, ir_to_terraform_resource/1, generate_resources/1]). -if(?OTP_RELEASE >= 27). -define(MODULEDOC(Str), -moduledoc(Str)). -define(DOC(Str), -doc(Str)). -else. -define(MODULEDOC(Str), -compile([])). -define(DOC(Str), -compile([])). -endif. -file("src/caffeine_lang/codegen/datadog.gleam", 166). ?DOC( " Resolve a single indicator entry. Returns a triple `#(name, resolved_value,\n" " indicator_source)`:\n" " - `resolved_value` is the post-template-resolution Value (StringValue\n" " for inline-query indicators, ExternalIndicatorValue for relay-fed)\n" " for the `ir.values` writeback.\n" " - `indicator_source` is the typed `IndicatorSource` for `slo.indicators`.\n" " External-indicator `value_extraction` is preserved from the pre-existing\n" " `ir.slo.indicators` entry (populated by the linker) — the Value layer\n" " can't carry the resolved type, so we look it up here.\n" ). -spec resolve_one_indicator( binary(), caffeine_lang@value:value(), caffeine_lang@linker@ir:intermediate_representation(caffeine_lang@linker@ir:deps_validated()), binary() ) -> {ok, {binary(), caffeine_lang@value:value(), caffeine_lang@linker@ir:indicator_source()}} | {error, caffeine_lang@errors:compilation_error()}. resolve_one_indicator(Name, Val, Ir, Identifier) -> case Val of {string_value, Q} -> gleam@result:map( caffeine_lang@codegen@datadog_template:parse_and_resolve_query_template( Q, erlang:element(4, Ir), Identifier ), fun(Resolved) -> {Name, {string_value, Resolved}, {literal_query, Resolved}} end ); {external_indicator_value, Source, Match, Value_path} -> gleam@result:'try'( begin _pipe = Match, _pipe@1 = maps:to_list(_pipe), _pipe@2 = gleam@list:try_map( _pipe@1, fun(Pair) -> {Field, Field_val} = Pair, case Field_val of {string_value, S} -> gleam@result:map( caffeine_lang@codegen@datadog_template:parse_and_resolve_query_template( S, erlang:element(4, Ir), Identifier ), fun(Resolved@1) -> {Field, {string_value, Resolved@1}} end ); Other -> {ok, {Field, Other}} end end ), gleam@result:map(_pipe@2, fun maps:from_list/1) end, fun(Resolved_match) -> Value_extraction = case gleam_stdlib:map_get( erlang:element(3, erlang:element(5, Ir)), Name ) of {ok, {external_signal, _, _, Ve}} -> Ve; _ -> none end, {ok, {Name, {external_indicator_value, Source, Resolved_match, Value_path}, {external_signal, Source, Resolved_match, Value_extraction}}} end ); Other@1 -> {error, caffeine_lang@errors:semantic_analysis_template_resolution_error( <<<<<<<<<<"expectation '"/utf8, Identifier/binary>>/binary, "' - indicator '"/utf8>>/binary, Name/binary>>/binary, "' has unsupported value shape: "/utf8>>/binary, (caffeine_lang@value:classify(Other@1))/binary>> )} end. -file("src/caffeine_lang/codegen/datadog.gleam", 39). ?DOC(false). -spec resolve_indicators( caffeine_lang@linker@ir:intermediate_representation(caffeine_lang@linker@ir:deps_validated()) ) -> {ok, caffeine_lang@linker@ir:intermediate_representation(caffeine_lang@linker@ir:resolved())} | {error, caffeine_lang@errors:compilation_error()}. resolve_indicators(Ir) -> Values_index = caffeine_lang@helpers:index_value_tuples( erlang:element(4, Ir) ), gleam@result:'try'( begin _pipe = gleam_stdlib:map_get(Values_index, <<"indicators"/utf8>>), gleam@result:replace_error( _pipe, caffeine_lang@errors:semantic_analysis_template_resolution_error( <<<<"expectation '"/utf8, (caffeine_lang@linker@ir:ir_to_identifier(Ir))/binary>>/binary, "' - missing 'indicators' field in IR"/utf8>> ) ) end, fun(Indicators_value_tuple) -> gleam@result:'try'( begin _pipe@1 = caffeine_lang@value:extract_dict( erlang:element(4, Indicators_value_tuple) ), gleam@result:map_error( _pipe@1, fun(_) -> caffeine_lang@errors:semantic_analysis_template_resolution_error( <<<<"expectation '"/utf8, (caffeine_lang@linker@ir:ir_to_identifier( Ir ))/binary>>/binary, "' - failed to decode indicators"/utf8>> ) end ) end, fun(Raw_indicators_dict) -> Identifier = caffeine_lang@linker@ir:ir_to_identifier(Ir), gleam@result:'try'( begin _pipe@2 = Raw_indicators_dict, _pipe@3 = maps:to_list(_pipe@2), gleam@list:try_map( _pipe@3, fun(Pair) -> {Name, Raw_value} = Pair, resolve_one_indicator( Name, Raw_value, Ir, Identifier ) end ) end, fun(Resolved_pairs) -> New_value_dict = begin _pipe@4 = Resolved_pairs, _pipe@5 = gleam@list:map( _pipe@4, fun(Triple) -> {erlang:element(1, Triple), erlang:element(2, Triple)} end ), _pipe@6 = maps:from_list(_pipe@5), {dict_value, _pipe@6} end, New_indicators_value_tuple = {value_tuple, <<"indicators"/utf8>>, erlang:element(3, Indicators_value_tuple), New_value_dict}, Resolved_indicators_dict = begin _pipe@7 = Resolved_pairs, _pipe@8 = gleam@list:map( _pipe@7, fun(Triple@1) -> {erlang:element(1, Triple@1), erlang:element(3, Triple@1)} end ), maps:from_list(_pipe@8) end, gleam@result:'try'( case gleam_stdlib:map_get( Values_index, <<"evaluation"/utf8>> ) of {error, _} -> {ok, none}; {ok, Evaluation_tuple} -> gleam@result:'try'( begin _pipe@9 = caffeine_lang@value:extract_string( erlang:element( 4, Evaluation_tuple ) ), gleam@result:map_error( _pipe@9, fun(_) -> caffeine_lang@errors:semantic_analysis_template_resolution_error( <<<<"expectation '"/utf8, (caffeine_lang@linker@ir:ir_to_identifier( Ir ))/binary>>/binary, "' - failed to decode 'evaluation' field as string"/utf8>> ) end ) end, fun(Evaluation_string) -> _pipe@10 = caffeine_lang@codegen@datadog_template:parse_and_resolve_query_template( Evaluation_string, erlang:element(4, Ir), Identifier ), gleam@result:map( _pipe@10, fun(Resolved_evaluation) -> {some, {value_tuple, <<"evaluation"/utf8>>, erlang:element( 3, Evaluation_tuple ), {string_value, Resolved_evaluation}}} end ) end ) end, fun(Resolved_evaluation_tuple) -> New_values = begin _pipe@11 = erlang:element(4, Ir), gleam@list:map( _pipe@11, fun(Vt) -> case erlang:element(2, Vt) of <<"indicators"/utf8>> -> New_indicators_value_tuple; <<"evaluation"/utf8>> -> case Resolved_evaluation_tuple of {some, New_evaluation} -> New_evaluation; none -> Vt end; _ -> Vt end end ) end, Resolved_eval = case Resolved_evaluation_tuple of {some, Vt@1} -> _pipe@12 = caffeine_lang@value:extract_string( erlang:element(4, Vt@1) ), gleam@option:from_result(_pipe@12); none -> erlang:element( 5, erlang:element(5, Ir) ) end, New_ir = caffeine_lang@linker@ir:map_slo( {intermediate_representation, erlang:element(2, Ir), erlang:element(3, Ir), New_values, erlang:element(5, Ir), erlang:element(6, Ir)}, fun(Slo) -> {slo_fields, erlang:element(2, Slo), Resolved_indicators_dict, erlang:element(4, Slo), Resolved_eval, erlang:element(6, Slo), erlang:element(7, Slo), erlang:element(8, Slo), erlang:element(9, Slo), erlang:element(10, Slo), erlang:element(11, Slo)} end ), {ok, caffeine_lang@linker@ir:promote(New_ir)} end ) end ) end ) end ). -file("src/caffeine_lang/codegen/datadog.gleam", 252). ?DOC( " Synthesize a Datadog metric query string for an indicator. `LiteralQuery`\n" " passes through unchanged (the user-authored query). `ExternalSignal`\n" " produces a query against the synthesized metric the relay will emit to:\n" " - no value extraction → count-style:\n" " `sum:caffeine.{indicator:}.as_count()`\n" " - with value extraction → distribution-style:\n" " `avg:caffeine.{indicator:}`\n" "\n" " One metric per measurement, distinguished by the `indicator:` tag.\n" " This is the idiomatic Datadog metric-SLO shape — every working example\n" " in the provider's acceptance tests uses the same metric on both sides\n" " of the ratio, sliced by tags (e.g. `{type:good}` vs `{*}`). Emitting two\n" " sibling metrics like `caffeine..good` and `caffeine..total`\n" " works in theory but doubles the chicken-and-egg problem (both metrics\n" " must exist before the SLO can be created).\n" ). -spec synthesize_indicator_query( binary(), binary(), caffeine_lang@linker@ir:indicator_source() ) -> binary(). synthesize_indicator_query(Unique_identifier, Indicator_name, Src) -> case Src of {literal_query, Q} -> Q; {external_signal, _, _, Value_extraction} -> Metric = <<"caffeine."/utf8, Unique_identifier/binary>>, Filter = <<<<"{indicator:"/utf8, Indicator_name/binary>>/binary, "}"/utf8>>, case Value_extraction of none -> <<<<<<"sum:"/utf8, Metric/binary>>/binary, Filter/binary>>/binary, ".as_count()"/utf8>>; {some, _} -> <<<<"avg:"/utf8, Metric/binary>>/binary, Filter/binary>> end end. -file("src/caffeine_lang/codegen/datadog.gleam", 476). -spec description_text_to_expr(binary()) -> terra_madre@hcl:expr(). description_text_to_expr(Text) -> case gleam_stdlib:contains_string(Text, <<"\n"/utf8>>) of true -> {heredoc, <<"EOT"/utf8>>, true, [{literal_part, <>}]}; false -> {string_literal, Text} end. -file("src/caffeine_lang/codegen/datadog.gleam", 462). ?DOC( " Combine the SLO description (from `###` doc comments) with the runbook\n" " link, if either is present. Multi-line descriptions render as an HCL\n" " heredoc; single-line descriptions render as a plain string literal.\n" ). -spec build_description_expr( gleam@option:option(binary()), gleam@option:option(binary()) ) -> gleam@option:option(terra_madre@hcl:expr()). build_description_expr(Description, Runbook) -> Runbook_link = gleam@option:map( Runbook, fun(Url) -> <<<<"[Runbook]("/utf8, Url/binary>>/binary, ")"/utf8>> end ), Combined = case {Description, Runbook_link} of {none, none} -> none; {{some, D}, none} -> {some, D}; {none, {some, R}} -> {some, R}; {{some, D@1}, {some, R@1}} -> {some, <<<>/binary, R@1/binary>>} end, gleam@option:map(Combined, fun description_text_to_expr/1). -file("src/caffeine_lang/codegen/datadog.gleam", 509). ?DOC(false). -spec window_to_timeframe(integer()) -> {ok, binary()} | {error, caffeine_lang@errors:compilation_error()}. window_to_timeframe(Days) -> Days_string = erlang:integer_to_binary(Days), case Days of 7 -> {ok, <>}; 30 -> {ok, <>}; 90 -> {ok, <>}; _ -> {error, caffeine_lang@codegen@generator_utils:resolution_error( <<"datadog"/utf8>>, <<<<"Illegal window_in_days value: "/utf8, Days_string/binary>>/binary, ". Accepted values are 7, 30, or 90."/utf8>> )} end. -file("src/caffeine_lang/codegen/datadog.gleam", 485). ?DOC( " Build dependency relation tag pairs from the relations dict.\n" " Generates pairs like #(\"soft_dependency\", \"target1,target2\").\n" ). -spec build_dependency_tags( gleam@dict:dict(caffeine_lang@linker@dependency:dependency_relation_type(), list(binary())) ) -> list({binary(), binary()}). build_dependency_tags(Relations) -> _pipe = Relations, _pipe@1 = maps:to_list(_pipe), _pipe@2 = gleam@list:sort( _pipe@1, fun(A, B) -> gleam@string:compare( caffeine_lang@linker@dependency:relation_type_to_string( erlang:element(1, A) ), caffeine_lang@linker@dependency:relation_type_to_string( erlang:element(1, B) ) ) end ), gleam@list:map( _pipe@2, fun(Pair) -> {Relation_type, Targets} = Pair, Sorted_targets = begin _pipe@3 = Targets, gleam@list:sort(_pipe@3, fun gleam@string:compare/2) end, {<<(caffeine_lang@linker@dependency:relation_type_to_string( Relation_type ))/binary, "_dependency"/utf8>>, gleam@string:join(Sorted_targets, <<","/utf8>>)} end ). -file("src/caffeine_lang/codegen/datadog.gleam", 290). ?DOC(false). -spec ir_to_terraform_resource( caffeine_lang@linker@ir:intermediate_representation(caffeine_lang@linker@ir:resolved()) ) -> {ok, {terra_madre@terraform:resource(), list(binary())}} | {error, caffeine_lang@errors:compilation_error()}. ir_to_terraform_resource(Ir) -> Resource_name = terra_madre@common:sanitize_terraform_identifier( erlang:element(3, Ir) ), Slo = erlang:element(5, Ir), Threshold = erlang:element(2, Slo), Window_in_days = erlang:element(4, Slo), Indicators = erlang:element(3, Slo), Evaluation_expr = begin _pipe = erlang:element(5, Slo), gleam@option:unwrap(_pipe, <<"numerator / denominator"/utf8>>) end, Runbook = erlang:element(7, Slo), Description = erlang:element(9, Slo), Indicator_strings = begin _pipe@1 = Indicators, _pipe@2 = maps:to_list(_pipe@1), _pipe@3 = gleam@list:map( _pipe@2, fun(Pair) -> {Name, Src} = Pair, {Name, synthesize_indicator_query(erlang:element(3, Ir), Name, Src)} end ), maps:from_list(_pipe@3) end, gleam@result:'try'( begin _pipe@4 = caffeine_lang@codegen@datadog_cql:resolve_slo_to_hcl( Evaluation_expr, Indicator_strings, erlang:element(10, Slo) ), gleam@result:map_error( _pipe@4, fun(Err) -> caffeine_lang@errors:generator_slo_query_resolution_error( <<<<<<"expectation '"/utf8, (caffeine_lang@linker@ir:ir_to_identifier( Ir ))/binary>>/binary, "' - failed to resolve SLO query: "/utf8>>/binary, Err/binary>> ) end ) end, fun(_use0) -> {resolved_slo_hcl, Slo_type, Slo_blocks} = _use0, Dependency_tags = case erlang:element(8, Slo) of {some, Relations} -> build_dependency_tags(Relations); none -> [] end, User_tag_pairs = erlang:element(6, Slo), System_tag_pairs = begin _pipe@5 = caffeine_lang@helpers:build_system_tag_pairs( erlang:element(3, erlang:element(2, Ir)), erlang:element(6, erlang:element(2, Ir)), erlang:element(4, erlang:element(2, Ir)), erlang:element(5, erlang:element(2, Ir)), erlang:element(2, erlang:element(2, Ir)), erlang:element(7, erlang:element(2, Ir)) ), lists:append(_pipe@5, Dependency_tags) end, System_tag_keys = begin _pipe@6 = System_tag_pairs, _pipe@7 = gleam@list:map( _pipe@6, fun(Pair@1) -> erlang:element(1, Pair@1) end ), gleam@set:from_list(_pipe@7) end, User_tag_keys = begin _pipe@8 = User_tag_pairs, _pipe@9 = gleam@list:map( _pipe@8, fun(Pair@2) -> erlang:element(1, Pair@2) end ), gleam@set:from_list(_pipe@9) end, Overlapping_keys = gleam@set:intersection( System_tag_keys, User_tag_keys ), {Final_system_tag_pairs, Overshadowing_warnings} = case gleam@set:size( Overlapping_keys ) > 0 of true -> Warn_msgs = begin _pipe@10 = Overlapping_keys, _pipe@11 = gleam@set:to_list(_pipe@10), _pipe@12 = gleam@list:sort( _pipe@11, fun gleam@string:compare/2 ), gleam@list:map( _pipe@12, fun(Key) -> <<<<<<(caffeine_lang@linker@ir:ir_to_identifier( Ir ))/binary, " - user tag '"/utf8>>/binary, Key/binary>>/binary, "' overshadows system tag"/utf8>> end ) end, Filtered = begin _pipe@13 = System_tag_pairs, gleam@list:filter( _pipe@13, fun(Pair@3) -> not gleam@set:contains( Overlapping_keys, erlang:element(1, Pair@3) ) end ) end, {Filtered, Warn_msgs}; false -> {System_tag_pairs, []} end, At_prefixed_attrs = begin _pipe@14 = Indicator_strings, _pipe@15 = maps:values(_pipe@14), _pipe@16 = lists:append(_pipe@15, [Evaluation_expr]), _pipe@17 = gleam@list:flat_map( _pipe@16, fun datadog_query@lint:at_prefixed_attrs/1 ), gleam@list:unique(_pipe@17) end, At_prefix_warnings = begin _pipe@18 = At_prefixed_attrs, _pipe@19 = gleam@list:sort(_pipe@18, fun gleam@string:compare/2), gleam@list:map( _pipe@19, fun(Attr) -> <<<<<<<<<<(caffeine_lang@linker@ir:ir_to_identifier(Ir))/binary, " - query references '"/utf8>>/binary, Attr/binary>>/binary, "' which Datadog rejects in metric SLO filters; use bare '"/utf8>>/binary, (gleam@string:drop_start(Attr, 1))/binary>>/binary, "' instead (Datadog strips '@' from log-based metric attribute names)"/utf8>> end ) end, Warnings = lists:append(Overshadowing_warnings, At_prefix_warnings), Tags = begin _pipe@20 = lists:append(Final_system_tag_pairs, User_tag_pairs), _pipe@21 = gleam@list:map( _pipe@20, fun(Pair@4) -> {string_literal, datadog_query@filter:tag( erlang:element(1, Pair@4), erlang:element(2, Pair@4) )} end ), {list_expr, _pipe@21} end, Identifier = caffeine_lang@linker@ir:ir_to_identifier(Ir), gleam@result:'try'( begin _pipe@22 = window_to_timeframe(Window_in_days), gleam@result:map_error( _pipe@22, fun(Err@1) -> caffeine_lang@errors:prefix_error(Err@1, Identifier) end ) end, fun(Window_in_days_string) -> Thresholds_block = terra_madre@hcl:simple_block( <<"thresholds"/utf8>>, [{<<"timeframe"/utf8>>, {string_literal, Window_in_days_string}}, {<<"target"/utf8>>, {float_literal, Threshold}}] ), Type_str = case Slo_type of time_slice_slo -> <<"time_slice"/utf8>>; metric_slo -> <<"metric"/utf8>> end, Base_attributes = [{<<"name"/utf8>>, {string_literal, erlang:element( 2, erlang:element(2, erlang:element(2, Ir)) )}}, {<<"type"/utf8>>, {string_literal, Type_str}}, {<<"tags"/utf8>>, Tags}], Attributes = case build_description_expr( Description, Runbook ) of {some, Desc_expr} -> [{<<"description"/utf8>>, Desc_expr} | Base_attributes]; none -> Base_attributes end, {ok, {{resource, <<"datadog_service_level_objective"/utf8>>, Resource_name, maps:from_list(Attributes), lists:append(Slo_blocks, [Thresholds_block]), terra_madre@hcl:empty_meta(), none}, Warnings}} end ) end ). -file("src/caffeine_lang/codegen/datadog.gleam", 273). ?DOC(false). -spec generate_resources( list(caffeine_lang@linker@ir:intermediate_representation(caffeine_lang@linker@ir:resolved())) ) -> {ok, {list(terra_madre@terraform:resource()), list(binary())}} | {error, caffeine_lang@errors:compilation_error()}. generate_resources(Irs) -> _pipe = Irs, _pipe@1 = gleam@list:try_fold( _pipe, {[], []}, fun(Acc, Ir) -> {Resources, Warning_lists} = Acc, gleam@result:'try'( ir_to_terraform_resource(Ir), fun(_use0) -> {Resource, Ir_warnings} = _use0, {ok, {[Resource | Resources], [Ir_warnings | Warning_lists]}} end ) end ), gleam@result:map( _pipe@1, fun(Pair) -> {lists:reverse(erlang:element(1, Pair)), lists:append(lists:reverse(erlang:element(2, Pair)))} end ).