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