%%%------------------------------------------------------------------------ %% Copyright 2019, OpenTelemetry Authors %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. %% You may obtain a copy of the License at %% %% http://www.apache.org/licenses/LICENSE-2.0 %% %% Unless required by applicable law or agreed to in writing, software %% distributed under the License is distributed on an "AS IS" BASIS, %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %% See the License for the specific language governing permissions and %% limitations under the License. %% %% @doc %% %% @end %%%----------------------------------------------------------------------- -module(otel_resource). -export([create/1, create/2, merge/2, schema_url/1, attributes/1, is_key/2]). -type key() :: unicode:latin1_binary() | atom(). %% values allowed in attributes of a resource are limited -type value() :: unicode:latin1_binary() | integer() | float() | boolean(). -type schema_url() :: uri_string:uri_string(). -define(MAX_LENGTH, 255). -record(resource, {schema_url :: schema_url() | undefined, attributes :: otel_attributes:t()}). -type t() :: #resource{} | undefined. -export_type([t/0]). -spec create(#{key() => value()} | [{key(), value()}]) -> t(). create(Attributes) -> create(Attributes, undefined). %% verifies each key and value and drops any that don't pass verification -spec create(#{key() => value()} | [{key(), value()}], schema_url() | undefined) -> t(). create(Map, SchemaUrl) when is_map(Map) -> create(maps:to_list(Map), SchemaUrl); create(List, SchemaUrl) when is_list(List) -> List1 = lists:filtermap(fun({K, V}) -> %% TODO: log an info or debug message when dropping? case try_check_key(K, true) of {true, Key} -> case check_value(V) of {true, Value} -> {true, {Key, Value}}; _ -> false end; _ -> false end end, lists:ukeysort(1, List)), #resource{schema_url=SchemaUrl, attributes=otel_attributes:new(List1, 128, 255)}. -spec schema_url(t()) -> schema_url() | undefined. schema_url(#resource{schema_url=Schema}) -> Schema; schema_url(_) -> undefined. -spec attributes(t()) -> otel_attributes:t() | undefined. attributes(#resource{attributes=Attributes}) -> Attributes; attributes(_) -> undefined. -spec is_key(key(), t()) -> boolean(). is_key(K, #resource{attributes=Attributes}) -> case try_check_key(K, false) of {true, Key} -> maps:is_key(Key, otel_attributes:map(Attributes)); _ -> false end; is_key(_, _) -> false. %% in case of collision the updating, first argument, resource takes precedence. -spec merge(t(), t()) -> t(). merge(#resource{schema_url=NewSchemaUrl, attributes=NewAttributes}, Current=#resource{schema_url=CurrentSchemaUrl, attributes=CurrentAttributes}) -> SchameUrl = merge_schema_url(NewSchemaUrl, CurrentSchemaUrl), NewMap = otel_attributes:map(NewAttributes), Current#resource{schema_url=SchameUrl, attributes=otel_attributes:set(NewMap, CurrentAttributes)}. %% %% when merging resources the schemas are checked to verify they match. %% if they do match then the schema is set to `undefined' and the attributes are kept. merge_schema_url(SchemaUrl, undefined) -> SchemaUrl; merge_schema_url(undefined, SchemaUrl) -> SchemaUrl; merge_schema_url(NewSchemaUrl, CurrentSchemaUrl) -> merge_schema_url_(uri_string:normalize(NewSchemaUrl), uri_string:normalize(CurrentSchemaUrl)). merge_schema_url_(SchemaUrl, SchemaUrl) -> SchemaUrl; merge_schema_url_(_, _) -> undefined. %% a resource key must be a non-empty latin1 string or atom, stored as atom try_check_key(K, UnconditionalBinaryToAtom) -> try check_key(K, UnconditionalBinaryToAtom) catch error:badarg -> false end. check_key(<<>>, _) -> false; check_key(K, UnconditionalBinaryToAtom) when is_binary(K) -> case check_string_value(K) of {true, Key} when UnconditionalBinaryToAtom -> {true, binary_to_atom(Key, latin1)}; {true, Key} -> {true, binary_to_existing_atom(Key, latin1)}; false -> false end; check_key(K, UnconditionalBinaryToAtom) when is_list(K) -> check_key(list_to_binary(K), UnconditionalBinaryToAtom); check_key(K, _) when is_atom(K) -> % Atoms have an length upper limit of 255 utf8 codepoints, so the % latin1 representation, if it exists, shares that limit _ = atom_to_binary(K, latin1), {true, K}; check_key(_, _) -> false. %% a resource value can be a latin1 string, integer, float or boolean check_value(V) when is_integer(V) ; is_float(V) ; is_boolean(V) -> {true, V}; check_value(V) when is_binary(V) -> check_string_value(V); check_value(V) when is_list(V) -> try unicode:characters_to_binary(V) of B -> check_string_value(B) catch error:badarg -> %% must be an array attribute value {true, V} end; check_value(_) -> false. check_string_value(V) -> %% all resource strings, key or value, must be latin1 with length less than 255 case string:length(V) =< ?MAX_LENGTH of true -> {true, V}; false -> false end.