<commit_before>
-module(ddfs_tag).
-behaviour(gen_server).

-include("config.hrl").

-export([start/2, init/1, handle_call/3, handle_cast/2,
        handle_info/2, terminate/2, code_change/3]).

-type replica() :: {timer:timestamp(), nonempty_string()}.
-type user_attr() :: [{binary(), binary()}].
-record(tagcontent, {id :: binary(),
                     last_modified :: binary(),
                     urls :: [binary()],
                     user :: user_attr()}).
-type tagcontent() :: #tagcontent{}.
-record(state, {tag, % :: binary(),
                data :: 'false'  | 'notfound' | 'deleted' | {'error', _}
                    | #tagcontent{},
                delayed :: {[{pid(), _}], [[binary()]]},
                timeout :: non_neg_integer(),
                replicas :: 'false' | 'too_many_failed_nodes'
                    | [{replica(), node()}],
                url_cache :: 'false' | gb_set()}).

-spec make_tagdata(tagcontent()) -> binary().
make_tagdata(D) ->
    list_to_binary(mochijson2:encode({struct,
        [
            {<<"id">>, D#tagcontent.id},
            {<<"version">>, 1},
            {<<"urls">>, D#tagcontent.urls},
            {<<"last-modified">>, D#tagcontent.last_modified},
            {<<"user-data">>, D#tagcontent.user}
        ]})).

-spec tag_lookup(any(), [binary()], [any()]) ->
     {'ok', [any()]} | {'error', _}.
tag_lookup(_, [], Results) ->
    {ok, lists:reverse(Results)};
tag_lookup(JsonBody, [Key|Rest], Results) ->
    case lists:keysearch(Key, 1, JsonBody) of
        {value, {_, Attrib}} ->
            tag_lookup(JsonBody, Rest, [Attrib|Results]);
        false ->
            case Key of
                <<"user-data">> ->
                    tag_lookup(JsonBody, Rest, [[] | Results]);
                _ ->
                    {error, not_found}
            end
    end.

-spec parse_tagcontent(binary()) -> {'error', _} | {'ok', tagcontent()}.
parse_tagcontent(TagData) ->
    case catch mochijson2:decode(TagData) of
        {'EXIT', _} ->
            {error, corrupted_json};
        {struct, Body} ->
            case tag_lookup(Body,
                            [<<"id">>, <<"urls">>, <<"last-modified">>, <<"user-data">>],
                            []) of
                {ok, [Id, Urls, LastModified, UserData]} ->
                    {ok, #tagcontent{id = Id,
                                     last_modified = LastModified,
                                     urls = Urls,
                                     user = UserData}};
                _ -> {error, invalid_object}
            end
    end.

-spec get_current_values(#state{}) -> {[binary()], user_attr()}.
get_current_values(S) ->
    if is_record(S#state.data, tagcontent) ->
            C = S#state.data,
            {C#tagcontent.urls, C#tagcontent.user};
       true ->
            {[], []}
    end.

-spec get_new_values(#state{}, atom() | {'user', binary()}, binary()) ->
          {'error', _} | {'ok', {[binary()], user_attr()}}.
get_new_values(S, Field, Value) ->
    {CurrentUrls, CurrentUser} = get_current_values(S),
    case Field of
        urls ->
            case validate_urls(Value) of
                true -><commit_msg>Fix a bug in attribute json marshaling.<commit_after>
-module(ddfs_tag).
-behaviour(gen_server).

-include("config.hrl").

-export([start/2, init/1, handle_call/3, handle_cast/2,
        handle_info/2, terminate/2, code_change/3]).

-type replica() :: {timer:timestamp(), nonempty_string()}.
-type user_attr() :: [{binary(), binary()}].
-record(tagcontent, {id :: binary(),
                     last_modified :: binary(),
                     urls :: [binary()],
                     user :: user_attr()}).
-type tagcontent() :: #tagcontent{}.
-record(state, {tag, % :: binary(),
                data :: 'false'  | 'notfound' | 'deleted' | {'error', _}
                    | #tagcontent{},
                delayed :: {[{pid(), _}], [[binary()]]},
                timeout :: non_neg_integer(),
                replicas :: 'false' | 'too_many_failed_nodes'
                    | [{replica(), node()}],
                url_cache :: 'false' | gb_set()}).

-spec make_tagdata(tagcontent()) -> binary().
make_tagdata(D) ->
    list_to_binary(mochijson2:encode({struct,
        [
            {<<"id">>, D#tagcontent.id},
            {<<"version">>, 1},
            {<<"urls">>, D#tagcontent.urls},
            {<<"last-modified">>, D#tagcontent.last_modified},
            {<<"user-data">>, {struct, D#tagcontent.user}}
        ]})).

-spec tag_lookup(any(), [binary()], [any()]) ->
     {'ok', [any()]} | {'error', _}.
tag_lookup(_, [], Results) ->
    {ok, lists:reverse(Results)};
tag_lookup(JsonBody, [Key|Rest], Results) ->
    case lists:keysearch(Key, 1, JsonBody) of
        {value, {_, Attrib}} ->
            tag_lookup(JsonBody, Rest, [Attrib|Results]);
        false ->
            case Key of
                <<"user-data">> ->
                    tag_lookup(JsonBody, Rest, [{struct, []} | Results]);
                _ ->
                    {error, not_found}
            end
    end.

-spec parse_tagcontent(binary()) -> {'error', _} | {'ok', tagcontent()}.
parse_tagcontent(TagData) ->
    case catch mochijson2:decode(TagData) of
        {'EXIT', _} ->
            {error, corrupted_json};
        {struct, Body} ->
            case tag_lookup(Body,
                            [<<"id">>, <<"urls">>, <<"last-modified">>, <<"user-data">>],
                            []) of
                {ok, [Id, Urls, LastModified, {struct, UserData}]} ->
                    {ok, #tagcontent{id = Id,
                                     last_modified = LastModified,
                                     urls = Urls,
                                     user = UserData}};
                _ -> {error, invalid_object}
            end
    end.

-spec get_current_values(#state{}) -> {[binary()], user_attr()}.
get_current_values(S) ->
    if is_record(S#state.data, tagcontent) ->
            C = S#state.data,
            {C#tagcontent.urls, C#tagcontent.user};
       true ->
            {[], []}
    end.

-spec get_new_values(#state{}, atom() | {'user', binary()}, binary()) ->
          {'error', _} | {'ok', {[binary()], user_attr()}}.
get_new_values(S, Field, Value) ->
    {CurrentUrls, CurrentUser} = get_current_values(S),
    case Field of
        urls ->
            case validate_urls(Value) of
                true ->
{'fraction_non_alphanumeric': 0.18882646691635455, 'fraction_numerical': 0.003433208489388265, 'mean_word_length': 1.8146684233640755, 'pattern_counts': {'":': 0, '<': 43, '<?xml version=': 0, '>': 89, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 10, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5158544', 'n_tokens_mistral': 2007, 'n_tokens_neox': 1961, 'n_words': 373}