import gleam/float import gleam/http import gleam/http/request.{type Request} import gleam/http/response.{Response} import gleam/httpc import gleam/int import gleam/list import gleam/option.{type Option} import gleam/result import gleam/string import gleam/time/timestamp pub type Client { Client(url: String, token: String, bucket: String, org: String) } pub fn new(base_url url: String) -> Client { Client(url:, token: "", bucket: "", org: "") } pub fn set_url(client: Client, url: String) -> Client { Client(..client, url:) } pub fn set_token(client: Client, token: String) -> Client { Client(..client, token:) } pub fn set_bucket(client: Client, bucket: String) -> Client { Client(..client, bucket:) } pub fn set_org(client: Client, org: String) -> Client { Client(..client, org:) } pub type LineField { LineFloat(number: Float) LineInt(number: Int) LineUInt(number: Int) LineString(text: String) LineBool(boolean: Bool) } fn encode_line_field(field: LineField) -> String { case field { LineBool(b) -> case b { True -> "true" False -> "false" } LineFloat(number) -> number |> float.to_string LineInt(number) -> number |> int.to_string <> "i" LineString(s) -> "\"" <> s <> "\"" LineUInt(number) -> number |> int.to_string <> "u" } } pub type Point { Point( measurement: String, tags: List(#(String, String)), fields: List(#(String, LineField)), time: Option(timestamp.Timestamp), ) } fn encode_points(points: List(Point)) -> String { list.fold(points, "", fn(points_body, point) { [ points_body, point.measurement, ",", point.tags |> list.map(fn(tag) { tag.0 <> "=" <> tag.1 }) |> string.join(","), " ", point.fields |> list.map(fn(field) { field.0 <> "=" <> encode_line_field(field.1) }) |> string.join(","), " ", option.unwrap(point.time, timestamp.system_time()) |> timestamp.to_unix_seconds |> float.multiply(1_000_000_000.0) |> float.round |> int.to_string, "\n", ] |> string.join("") }) } pub fn set_measurements(client: Client, points: List(Point)) -> Request(String) { let assert Ok(req) = request.to(client.url <> "/api/v2") req |> request.set_body(encode_points(points)) |> request.set_query([#("org", client.org), #("bucket", client.bucket)]) |> request.set_header("Authorization", "Token " <> client.token) |> request.set_path(req.path <> "/write") |> request.set_method(http.Post) } pub fn append_measurements( req: Request(String), points: List(Point), ) -> Request(String) { req |> request.set_body(req.body <> encode_points(points)) } pub fn send_request(req: Request(String)) -> response.Response(String) { req |> httpc.send |> result.unwrap(Response( status: 500, headers: [], body: "failed to produce a response", )) }