-module(mongo@client). -compile([no_auto_import, nowarn_unused_vars]). -export([collection/2, execute/2, get_more/3, connect/1]). -export_type([connection_info/0, database/0, collection/0]). -opaque connection_info() :: {connection_info, binary(), integer(), binary(), gleam@option:option({binary(), binary()}), gleam@option:option(binary())}. -type database() :: {database, mongo@tcp:socket(), binary()}. -type collection() :: {collection, database(), binary()}. -spec collection(database(), binary()) -> collection(). collection(Db, Name) -> {collection, Db, Name}. -spec send_cmd(mongo@tcp:socket(), binary(), bson@value:value()) -> {ok, list({binary(), bson@value:value()})} | {error, nil}. send_cmd(Socket, Db, Cmd) -> {document, Cmd@1} = case Cmd of {document, _} -> Cmd; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Assertion pattern match failed"/utf8>>, value => _assert_fail, module => <<"mongo/client"/utf8>>, function => <<"send_cmd"/utf8>>, line => 122}) end, Cmd@2 = gleam@list:key_set(Cmd@1, <<"$db"/utf8>>, {str, Db}), Encoded = bson:encode(Cmd@2), Size = gleam@bit_string:byte_size(Encoded) + 21, Packet = begin _pipe = [<>, Encoded], gleam@bit_string:concat(_pipe) end, case mongo@tcp:send(Socket, Packet) of ok -> case mongo@tcp:'receive'(Socket) of {ok, Response} -> <<_:168, Rest/bitstring>> = Response, case bson:decode(Rest) of {ok, Result} -> {ok, Result}; {error, nil} -> {error, nil} end; {error, nil} -> {error, nil} end; _ -> {error, nil} end. -spec execute(collection(), bson@value:value()) -> {ok, list({binary(), bson@value:value()})} | {error, {integer(), binary()}}. execute(Collection, Cmd) -> case erlang:element(2, Collection) of {database, Socket, Name} -> case send_cmd(Socket, Name, Cmd) of {ok, [{<<"ok"/utf8>>, {double, 0.0}}, {<<"errmsg"/utf8>>, {str, Msg}}, {<<"code"/utf8>>, {int32, Code}}, {<<"codeName"/utf8>>, _}]} -> {error, {Code, Msg}}; {ok, Result} -> {ok, Result}; {error, nil} -> {error, {-2, <<""/utf8>>}} end end. -spec get_more(collection(), integer(), integer()) -> {ok, list({binary(), bson@value:value()})} | {error, {integer(), binary()}}. get_more(Collection, Id, Batch_size) -> execute( Collection, {document, [{<<"getMore"/utf8>>, {int64, Id}}, {<<"collection"/utf8>>, {str, erlang:element(3, Collection)}}, {<<"batchSize"/utf8>>, {int32, Batch_size}}]} ). -spec authenticate(mongo@tcp:socket(), binary(), binary(), binary()) -> {ok, nil} | {error, nil}. authenticate(Socket, Username, Password, Auth_source) -> First_payload = mongo@scram:first_payload(Username), First = mongo@scram:first_message(First_payload), gleam@result:then( send_cmd(Socket, Auth_source, First), fun(Reply) -> gleam@result:then( mongo@scram:parse_first_reply(Reply), fun(_use0) -> {Server_params, Server_payload, Cid} = _use0, gleam@result:then( mongo@scram:second_message( Server_params, First_payload, Server_payload, Cid, Password ), fun(_use0@1) -> {Second, Server_signature} = _use0@1, gleam@result:then( send_cmd(Socket, Auth_source, Second), fun(Reply@1) -> case Reply@1 of [{<<"ok"/utf8>>, {double, 0.0}} | _] -> {error, nil}; Reply@2 -> mongo@scram:parse_second_reply( Reply@2, Server_signature ) end end ) end ) end ) end ). -spec parse_connection_string(binary()) -> {ok, connection_info()} | {error, nil}. parse_connection_string(Uri) -> gleam@result:then( gleam@uri:parse(Uri), fun(Parsed) -> gleam@bool:guard( erlang:element(2, Parsed) /= {some, <<"mongodb"/utf8>>}, {error, nil}, fun() -> gleam@bool:guard( gleam@option:is_none(erlang:element(4, Parsed)) orelse (erlang:element( 4, Parsed ) =:= {some, <<""/utf8>>}), {error, nil}, fun() -> gleam@bool:guard( gleam@list:contains( [<<""/utf8>>, <<"/"/utf8>>], erlang:element(6, Parsed) ), {error, nil}, fun() -> _assert_subject = erlang:element(4, Parsed), {some, Host} = case _assert_subject of {some, _} -> _assert_subject; _assert_fail -> erlang:error( #{gleam_error => let_assert, message => <<"Assertion pattern match failed"/utf8>>, value => _assert_fail, module => <<"mongo/client"/utf8>>, function => <<"parse_connection_string"/utf8>>, line => 155} ) end, Port = gleam@option:unwrap( erlang:element(5, Parsed), 27017 ), Path = erlang:element(6, Parsed), [_, Db] = gleam@string:split( Path, <<"/"/utf8>> ), gleam@result:then( gleam@uri:percent_decode(Db), fun(Db@1) -> gleam@bool:guard( gleam@option:is_none( erlang:element(3, Parsed) ), {ok, {connection_info, Host, Port, Db@1, none, none}}, fun() -> _assert_subject@1 = erlang:element( 3, Parsed ), {some, Userinfo} = case _assert_subject@1 of {some, _} -> _assert_subject@1; _assert_fail@1 -> erlang:error( #{gleam_error => let_assert, message => <<"Assertion pattern match failed"/utf8>>, value => _assert_fail@1, module => <<"mongo/client"/utf8>>, function => <<"parse_connection_string"/utf8>>, line => 171} ) end, case gleam@string:split( Userinfo, <<":"/utf8>> ) of [<<""/utf8>>, _] -> {error, nil}; [_, <<""/utf8>>] -> {error, nil}; [Username, Password] -> case begin _pipe = [Username, Password], gleam@list:map( _pipe, fun gleam@uri:percent_decode/1 ) end of [{ok, Username@1}, {ok, Password@1}] -> case erlang:element( 7, Parsed ) of {some, Query} -> gleam@result:then( gleam@uri:parse_query( Query ), fun( Opts ) -> case gleam@list:key_find( Opts, <<"authSource"/utf8>> ) of {ok, Auth_source} -> _pipe@1 = {connection_info, Host, Port, Db@1, {some, {Username@1, Password@1}}, {some, Auth_source}}, {ok, _pipe@1}; {error, nil} -> _pipe@2 = {connection_info, Host, Port, Db@1, {some, {Username@1, Password@1}}, none}, {ok, _pipe@2} end end ); none -> _pipe@3 = {connection_info, Host, Port, Db@1, {some, {Username@1, Password@1}}, none}, {ok, _pipe@3} end; _ -> {error, nil} end; _ -> {error, nil} end end ) end ) end ) end ) end ) end ). -spec connect(binary()) -> {ok, database()} | {error, nil}. connect(Uri) -> gleam@result:then(parse_connection_string(Uri), fun(Info) -> case Info of {connection_info, Host, Port, Db, Auth, Auth_source} -> gleam@result:then( mongo@tcp:connect(Host, Port), fun(Socket) -> case Auth of none -> {ok, {database, Socket, Db}}; {some, {Username, Password}} -> gleam@result:then(case Auth_source of none -> authenticate( Socket, Username, Password, Db ); {some, Source} -> authenticate( Socket, Username, Password, Source ) end, fun(_) -> {ok, {database, Socket, Db}} end) end end ) end end).