Packages
gleam_mongo
0.11.0
0.18.1
retired
0.18.0
retired
0.17.0
retired
0.16.0
retired
0.15.1
retired
0.15.0
retired
0.14.0
retired
0.13.0
retired
0.12.0
retired
0.11.0
retired
0.10.0
retired
0.9.0
retired
0.8.0
retired
0.7.0
retired
0.6.0
retired
0.5.0
retired
0.4.0
retired
0.3.0
0.2.0
retired
0.1.3
retired
0.1.2
retired
0.1.1
retired
0.1.0
retired
A mongodb driver for gleam
Retired package: Renamed - Republished as mungo
Current section
Files
Jump to
Current section
Files
src/mongo/client.gleam
import tcp
import gleam/uri
import gleam/list
import bson/types
import mongo/scram
import gleam/string
import gleam/option
import gleam/bit_string
import bson.{decode, encode}
pub opaque type ConnectionInfo {
ConnectionInfo(
host: String,
port: Int,
db: String,
auth: option.Option(#(String, String)),
auth_source: option.Option(String),
)
}
pub type Database {
Database(socket: tcp.Socket, name: String)
}
pub type Collection {
Collection(db: Database, name: String)
}
pub fn connect(uri: String) -> Result(Database, Nil) {
try info = parse_connection_string(uri)
case info {
ConnectionInfo(host, port, db, auth, auth_source) -> {
try socket = tcp.connect(host, port)
case auth {
option.None -> Ok(Database(socket, db))
option.Some(#(username, password)) -> {
try _ = case auth_source {
option.None -> authenticate(socket, username, password, db)
option.Some(source) ->
authenticate(socket, username, password, source)
}
Ok(Database(socket, db))
}
}
}
}
}
pub fn collection(db: Database, name: String) -> Collection {
Collection(db, name)
}
pub fn execute(
collection: Collection,
cmd: types.Value,
) -> Result(List(#(String, types.Value)), #(Int, String)) {
case collection.db {
Database(socket, name) ->
case send_cmd(socket, name, cmd) {
Ok([
#("ok", types.Double(0.0)),
#("errmsg", types.Str(msg)),
#("code", types.Integer(code)),
#("codeName", _),
]) -> Error(#(code, msg))
Ok(result) -> Ok(result)
Error(Nil) -> Error(#(-2, ""))
}
}
}
fn authenticate(
socket: tcp.Socket,
username: String,
password: String,
auth_source: String,
) {
let first_payload = scram.first_payload(username)
let first = scram.first_message(first_payload)
try reply = send_cmd(socket, auth_source, first)
try #(server_params, server_payload, cid) = scram.parse_first_reply(reply)
try #(second, server_signature) =
scram.second_message(
server_params,
first_payload,
server_payload,
cid,
password,
)
try reply = send_cmd(socket, auth_source, second)
case reply {
[#("ok", types.Double(0.0)), ..] -> Error(Nil)
reply -> scram.parse_second_reply(reply, server_signature)
}
}
fn send_cmd(
socket: tcp.Socket,
db: String,
cmd: types.Value,
) -> Result(List(#(String, types.Value)), Nil) {
assert types.Document(cmd) = cmd
let cmd = list.append(cmd, [#("$db", types.Str(db))])
let encoded = encode(cmd)
let size = bit_string.byte_size(encoded) + 21
let packet =
[<<size:32-little, 0:32, 0:32, 2013:32-little, 0:32, 0>>, encoded]
|> bit_string.concat
case tcp.send(socket, packet) {
tcp.Ok ->
case tcp.receive(socket) {
Ok(response) -> {
let <<_:168, rest:bit_string>> = response
case decode(rest) {
Ok(result) -> Ok(result)
Error(Nil) -> Error(Nil)
}
}
Error(Nil) -> Error(Nil)
}
_ -> Error(Nil)
}
}
fn parse_connection_string(uri: String) -> Result(ConnectionInfo, Nil) {
try parsed = uri.parse(uri)
case parsed.scheme {
option.Some("mongodb") ->
case parsed.host {
option.Some("") -> Error(Nil)
option.Some(host) -> {
let port = option.unwrap(parsed.port, 27017)
case parsed.path {
"" -> Error(Nil)
"/" -> Error(Nil)
path -> {
let [_, db] = string.split(path, "/")
try db = uri.percent_decode(db)
case parsed.userinfo {
option.Some(userinfo) ->
case string.split(userinfo, ":") {
["", _] -> Error(Nil)
[_, ""] -> Error(Nil)
[username, password] ->
case
[username, password]
|> list.map(uri.percent_decode)
{
[Ok(username), Ok(password)] ->
case parsed.query {
option.Some(query) -> {
try opts = uri.parse_query(query)
case list.key_find(opts, "authSource") {
Ok(auth_source) ->
ConnectionInfo(
host,
port,
db,
auth: option.Some(#(username, password)),
auth_source: option.Some(auth_source),
)
|> Ok
Error(Nil) ->
ConnectionInfo(
host,
port,
db,
auth: option.Some(#(username, password)),
auth_source: option.None,
)
|> Ok
}
}
option.None ->
ConnectionInfo(
host,
port,
db,
auth: option.Some(#(username, password)),
auth_source: option.None,
)
|> Ok
}
_ -> Error(Nil)
}
_ -> Error(Nil)
}
option.None ->
ConnectionInfo(
host,
port,
db,
auth: option.None,
auth_source: option.None,
)
|> Ok
}
}
}
}
option.None -> Error(Nil)
}
option.Some(_) -> Error(Nil)
option.None -> Error(Nil)
}
}