-module(mongo@client). -compile(no_auto_import). -export([connect/1, collection/2, execute/2]). -export_type([auth/0, connection_info/0, database/0, collection/0]). -opaque auth() :: {auth, binary(), binary()}. -opaque connection_info() :: {connection_info, binary(), integer(), binary(), gleam@option:option(auth()), gleam@option:option(binary())}. -opaque 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 {ok, Info} -> case Info of {connection_info, Host, Port, Db, _@1, _@2} -> case tcp:connect(Host, Port) of {ok, Socket} -> {ok, {database, Socket, Db}}; {error, _@3} -> {error, nil} end end; {error, nil} -> {error, nil} 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 parse_connection_string(binary()) -> {ok, connection_info()} | {error, nil}. parse_connection_string(Uri) -> case gleam@uri:parse(Uri) of {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 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 erlang:element( 7, Parsed ) of {some, Query} -> case begin _pipe@1 = Query, gleam@uri:parse_query( _pipe@1 ) end of {ok, Opts} -> case gleam@list:key_find( Opts, <<"authSource"/utf8>> ) of {ok, Auth_source} -> {ok, {connection_info, Host, Port, Db, {some, {auth, Username, Password}}, {some, Auth_source}}}; {error, nil} -> {ok, {connection_info, Host, Port, Db, {some, {auth, Username, Password}}, none}} end; {error, nil} -> {error, nil} end; none -> {ok, {connection_info, Host, Port, Db, {some, {auth, Username, Password}}, none}} end; _@4 -> {error, nil} end; none -> {ok, {connection_info, Host, Port, Db, none, none}} end end; none -> {error, nil} end; {some, _@5} -> {error, nil}; none -> {error, nil} end; {error, nil} -> {error, nil} 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 => 160}) 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.