Skip to content

Commit

Permalink
feat: Return an error when buildng a request with missing required fi…
Browse files Browse the repository at this point in the history
…elds
  • Loading branch information
custompro98 committed Jul 11, 2024
1 parent 6aba493 commit 8beb4ad
Show file tree
Hide file tree
Showing 5 changed files with 99 additions and 61 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ pub fn main() {
))
|> glibsql.build
let assert Ok(request) = request
use response <- result.try(httpc.send(request))
// ...
Expand All @@ -50,6 +52,8 @@ pub fn main() {
|> glibsql.with_baton("<baton from previous request>")
|> glibsql.build
let assert Ok(second_request) = second_request
use response <- result.try(httpc.send(second_request))
}
Expand Down
2 changes: 1 addition & 1 deletion gleam.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name = "glibsql"
version = "0.3.0"
version = "0.4.0"

# Fill out these fields if you intend to generate HTML documentation or publish
# your project to the Hex package manager.
Expand Down
123 changes: 75 additions & 48 deletions src/glibsql/http.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import gleam/http/request as http_request
import gleam/json
import gleam/list
import gleam/option.{type Option, None, Some}
import gleam/result
import gleam/string

/// Statement wraps the supported types of requests.
Expand All @@ -26,32 +27,38 @@ pub type Statement {
CloseStatement
}

/// Request encapsulates everything needed to execute
/// HttpRequest encapsulates everything needed to execute
/// a Hrana over HTTP libSQL request.
///
/// see `new_request()` to construct this record.
pub opaque type Request {
Request(
database: String,
organization: String,
host: String,
path: String,
token: String,
pub opaque type HttpRequest {
HttpRequest(
database: Option(String),
organization: Option(String),
host: Option(String),
path: Option(String),
token: Option(String),
statements: List(Statement),
baton: Option(String),
)
}

/// Error type for all possible errors returned by glibsql/http.
pub opaque type GlibsqlError {
/// Raised when a required property is not provided.
MissingPropertyError(String)
}

/// Create a new Hrana over HTTP libSQL request.
///
/// Uses the builder pattern to construct everything necessary to send a request.
pub fn new_request() -> Request {
Request(
database: "",
organization: "",
host: "turso.io",
path: "/v2/pipeline",
token: "",
pub fn new_request() -> HttpRequest {
HttpRequest(
database: None,
organization: None,
host: None,
path: None,
token: None,
statements: [],
baton: None,
)
Expand All @@ -62,8 +69,8 @@ pub fn new_request() -> Request {
///
/// Given a Turso databse URL like libsql://example-database-myorganization.turso.io
/// The database name is "example-database"
pub fn with_database(request: Request, database: String) -> Request {
Request(..request, database: database)
pub fn with_database(request: HttpRequest, database: String) -> HttpRequest {
HttpRequest(..request, database: Some(database))
}

/// Set the target database organization.
Expand All @@ -72,10 +79,10 @@ pub fn with_database(request: Request, database: String) -> Request {
/// Given a Turso databse URL like libsql://example-database-myorganization.turso.io
/// The database name is "myorganization"
pub fn with_organization(
request: Request,
request: HttpRequest,
organization: String,
) -> Request {
Request(..request, organization: organization)
) -> HttpRequest {
HttpRequest(..request, organization: Some(organization))
}

/// Set the target database host.
Expand All @@ -84,61 +91,81 @@ pub fn with_organization(
///
/// Given a Turso databse URL like libsql://example-database-myorganization.turso.io
/// The host name is "turso.io"
pub fn with_host(request: Request, host: String) -> Request {
Request(..request, host: host)
pub fn with_host(request: HttpRequest, host: String) -> HttpRequest {
HttpRequest(..request, host: Some(host))
}

/// Set the target database path on the host.
/// NOTE: this defaults to Turso's /v2/pipeline
/// Calling this function multiple times will override the previous value.
pub fn with_path(request: Request, path: String) -> Request {
Request(..request, path: path)
pub fn with_path(request: HttpRequest, path: String) -> HttpRequest {
HttpRequest(..request, path: Some(path))
}

/// Set the Bearer token to access the database. Do not include `Bearer `.
/// Calling this function multiple times will override the previous value.
pub fn with_token(request: Request, token: String) -> Request {
Request(..request, token: token)
pub fn with_token(request: HttpRequest, token: String) -> HttpRequest {
HttpRequest(..request, token: Some(token))
}

/// Set a statement on the request.
/// This function may be called multiple times, additional statements will be
/// executed in order.
pub fn with_statement(request: Request, statement: Statement) -> Request {
Request(..request, statements: [statement, ..request.statements])
pub fn with_statement(request: HttpRequest, statement: Statement) -> HttpRequest {
HttpRequest(..request, statements: [statement, ..request.statements])
}

/// Clear all statements from the request.
pub fn clear_statements(request: Request) -> Request {
Request(..request, statements: [])
pub fn clear_statements(request: HttpRequest) -> HttpRequest {
HttpRequest(..request, statements: [])
}

/// Set the baton from a previous connection to be reused.
pub fn with_baton(request: Request, baton: String) -> Request {
Request(..request, baton: Some(baton))
pub fn with_baton(request: HttpRequest, baton: String) -> HttpRequest {
HttpRequest(..request, baton: Some(baton))
}

/// Build the request using the previously provided values.
/// Returns a gleam/http request suitable to be used in your HTTP client of choice.
pub fn build(request: Request) -> http_request.Request(String) {
http_request.new()
|> http_request.set_method(http.Post)
|> http_request.set_scheme(http.Https)
|> http_request.set_host(
request.database <> "-" <> request.organization <> "." <> request.host,
)
|> http_request.set_path(request.path)
|> http_request.set_header(
"Authorization",
string.join(["Bearer", request.token], " "),
pub fn build(
request: HttpRequest,
) -> Result(http_request.Request(String), GlibsqlError) {
use database <- result.try(option.to_result(
request.database,
MissingPropertyError("database"),
))

use organization <- result.try(option.to_result(
request.organization,
MissingPropertyError("organization"),
))

let host = option.unwrap(request.host, "turso.io")
let path = option.unwrap(request.path, "/v2/pipeline")

use token <- result.try(option.to_result(
request.token,
MissingPropertyError("token"),
))

Ok(
http_request.new()
|> http_request.set_method(http.Post)
|> http_request.set_scheme(http.Https)
|> http_request.set_host(database <> "-" <> organization <> "." <> host)
|> http_request.set_path(path)
|> http_request.set_header(
"Authorization",
string.join(["Bearer", token], " "),
)
|> http_request.set_header("Content-Type", "application/json")
|> http_request.set_header("Accept", "application/json")
|> http_request.set_header("User-Agent", "glibsql/0.4.0")
|> http_request.set_body(build_json(request)),
)
|> http_request.set_header("Content-Type", "application/json")
|> http_request.set_header("Accept", "application/json")
|> http_request.set_header("User-Agent", "glibsql/0.3.0")
|> http_request.set_body(build_json(request))
}

fn build_json(req: Request) {
fn build_json(req: HttpRequest) {
let statements =
list.reverse(req.statements)
|> list.map(fn(stmt) {
Expand Down
1 change: 1 addition & 0 deletions src/glibsql/libsql.gleam
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

30 changes: 18 additions & 12 deletions test/glibsql/http_test.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ pub fn builder_custom_host_test() {
|> http_request.set_header("Authorization", "Bearer token")
|> http_request.set_header("Content-Type", "application/json")
|> http_request.set_header("Accept", "application/json")
|> http_request.set_header("User-Agent", "glibsql/0.3.0")
|> http_request.set_header("User-Agent", "glibsql/0.4.0")
|> http_request.set_body("{\"baton\":null,\"requests\":[]}")

glibsql.new_request()
Expand All @@ -23,7 +23,7 @@ pub fn builder_custom_host_test() {
|> glibsql.with_path("/v1/acme")
|> glibsql.with_token("token")
|> glibsql.build
|> should.equal(expected)
|> should.equal(Ok(expected))
}

pub fn builder_no_statements_test() {
Expand All @@ -36,15 +36,15 @@ pub fn builder_no_statements_test() {
|> http_request.set_header("Authorization", "Bearer token")
|> http_request.set_header("Content-Type", "application/json")
|> http_request.set_header("Accept", "application/json")
|> http_request.set_header("User-Agent", "glibsql/0.3.0")
|> http_request.set_header("User-Agent", "glibsql/0.4.0")
|> http_request.set_body("{\"baton\":null,\"requests\":[]}")

glibsql.new_request()
|> glibsql.with_database("database")
|> glibsql.with_organization("organization")
|> glibsql.with_token("token")
|> glibsql.build
|> should.equal(expected)
|> should.equal(Ok(expected))
}

pub fn builder_single_statement_test() {
Expand All @@ -57,7 +57,7 @@ pub fn builder_single_statement_test() {
|> http_request.set_header("Authorization", "Bearer token")
|> http_request.set_header("Content-Type", "application/json")
|> http_request.set_header("Accept", "application/json")
|> http_request.set_header("User-Agent", "glibsql/0.3.0")
|> http_request.set_header("User-Agent", "glibsql/0.4.0")
|> http_request.set_body(
"{\"baton\":null,\"requests\":[{\"type\":\"execute\",\"stmt\":{\"sql\":\"SELECT * FROM users\"}},{\"type\":\"close\"}]}",
)
Expand All @@ -69,7 +69,7 @@ pub fn builder_single_statement_test() {
|> glibsql.with_statement(glibsql.ExecuteStatement(sql: "SELECT * FROM users"))
|> glibsql.with_statement(glibsql.CloseStatement)
|> glibsql.build
|> should.equal(expected)
|> should.equal(Ok(expected))
}

pub fn builder_many_statement_test() {
Expand All @@ -82,7 +82,7 @@ pub fn builder_many_statement_test() {
|> http_request.set_header("Authorization", "Bearer token")
|> http_request.set_header("Content-Type", "application/json")
|> http_request.set_header("Accept", "application/json")
|> http_request.set_header("User-Agent", "glibsql/0.3.0")
|> http_request.set_header("User-Agent", "glibsql/0.4.0")
|> http_request.set_body(
"{\"baton\":null,\"requests\":[{\"type\":\"execute\",\"stmt\":{\"sql\":\"SELECT * FROM users\"}},{\"type\":\"execute\",\"stmt\":{\"sql\":\"SELECT * FROM posts\"}},{\"type\":\"close\"}]}",
)
Expand All @@ -95,7 +95,7 @@ pub fn builder_many_statement_test() {
|> glibsql.with_statement(glibsql.ExecuteStatement(sql: "SELECT * FROM posts"))
|> glibsql.with_statement(glibsql.CloseStatement)
|> glibsql.build
|> should.equal(expected)
|> should.equal(Ok(expected))
}

pub fn builder_clear_statements_test() {
Expand All @@ -108,7 +108,7 @@ pub fn builder_clear_statements_test() {
|> http_request.set_header("Authorization", "Bearer token")
|> http_request.set_header("Content-Type", "application/json")
|> http_request.set_header("Accept", "application/json")
|> http_request.set_header("User-Agent", "glibsql/0.3.0")
|> http_request.set_header("User-Agent", "glibsql/0.4.0")
|> http_request.set_body("{\"baton\":null,\"requests\":[]}")

glibsql.new_request()
Expand All @@ -120,7 +120,7 @@ pub fn builder_clear_statements_test() {
|> glibsql.with_statement(glibsql.CloseStatement)
|> glibsql.clear_statements
|> glibsql.build
|> should.equal(expected)
|> should.equal(Ok(expected))
}

pub fn builder_baton_test() {
Expand All @@ -133,7 +133,7 @@ pub fn builder_baton_test() {
|> http_request.set_header("Authorization", "Bearer token")
|> http_request.set_header("Content-Type", "application/json")
|> http_request.set_header("Accept", "application/json")
|> http_request.set_header("User-Agent", "glibsql/0.3.0")
|> http_request.set_header("User-Agent", "glibsql/0.4.0")
|> http_request.set_body(
"{\"baton\":\"baton\",\"requests\":[{\"type\":\"close\"}]}",
)
Expand All @@ -145,5 +145,11 @@ pub fn builder_baton_test() {
|> glibsql.with_statement(glibsql.CloseStatement)
|> glibsql.with_baton("baton")
|> glibsql.build
|> should.equal(expected)
|> should.equal(Ok(expected))
}

pub fn builder_missing_fields_test() {
glibsql.new_request()
|> glibsql.build
|> should.be_error
}

0 comments on commit 8beb4ad

Please sign in to comment.