diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/client.ml | 4 | ||||
-rw-r--r-- | lib/dune | 4 | ||||
-rw-r--r-- | lib/operation.ml | 49 | ||||
-rw-r--r-- | lib/query.ml | 37 | ||||
-rw-r--r-- | lib/router.ml | 46 | ||||
-rw-r--r-- | lib/router.mli | 1 | ||||
-rw-r--r-- | lib/utils.ml | 121 |
7 files changed, 247 insertions, 15 deletions
diff --git a/lib/client.ml b/lib/client.ml new file mode 100644 index 0000000..80a11b7 --- /dev/null +++ b/lib/client.ml @@ -0,0 +1,4 @@ +type t = + { id : int + ; mov_limit : int + } @@ -1,3 +1,5 @@ (library (name rinha) - (libraries piaf routes yojson)) + (libraries piaf routes yojson caqti caqti-driver-postgresql caqti-eio ppx_rapper_eio) + (preprocess + (pps ppx_rapper))) diff --git a/lib/operation.ml b/lib/operation.ml new file mode 100644 index 0000000..1fb1dd5 --- /dev/null +++ b/lib/operation.ml @@ -0,0 +1,49 @@ +module TransactionType = struct + type t = + | Credit + | Debit + + let t = + let encode = function + | Credit -> "credit" + | Debit -> "debit" + in + let decode = function + | "credit" -> Ok Credit + | "debit" -> Ok Debit + | _ -> Error "Invalid transaction type" + in + Caqti_type.(enum ~encode ~decode "transaction_type") + ;; +end + +type t = + | Transaction of + { transaction_type : TransactionType.t + ; value : int + ; description : string + } + | Balance of { client_id : int } + +type transaction = + { id : int + ; client_id : int + ; value : int + ; transaction_type : TransactionType.t + ; description : string + ; created_at : Ptime.t + } + +let decoder = + let open Utils.Decoder in + let open Syntax in + let transaction_type_decoder = + literal "c" *> return TransactionType.Credit + <|> literal "d" *> return TransactionType.Debit + in + (fun value transaction_type description -> + Transaction { value; description; transaction_type }) + <$> ("valor" <: int) + <*> ("tipo" <: transaction_type_decoder) + <*> ("descricao" <: string) +;; diff --git a/lib/query.ml b/lib/query.ml new file mode 100644 index 0000000..0fb6f7e --- /dev/null +++ b/lib/query.ml @@ -0,0 +1,37 @@ +type pool = ((module Rapper_helper.CONNECTION), Caqti_error.t) Caqti_eio.Pool.t + +let transaction_query = + [%rapper + execute + {sql| + INSERT INTO transactions (client_id, value, type, description) + VALUES (%int{client_id}, %int{value}, %Operation.TransactionType{transaction_type}, %string{description}) + |sql}] +;; + +let client_query = + let open Client in + [%rapper + get_opt + {sql| + SELECT @int{id}, @int{mov_limit} FROM clients WHERE id = %int{id} + |sql} + record_out] +;; + +let execute_operation ~client_id ~(op : Operation.t) pool = + match op with + | Transaction data -> + Caqti_eio.Pool.use + (fun conn -> + transaction_query + ~client_id + ~value:data.value + ~transaction_type:data.transaction_type + ~description:data.description + conn) + pool + | _ -> failwith "TODO" +;; + +let find_client id pool = Caqti_eio.Pool.use (fun conn -> client_query ~id conn) pool diff --git a/lib/router.ml b/lib/router.ml index cbff9fb..35cef34 100644 --- a/lib/router.ml +++ b/lib/router.ml @@ -14,16 +14,35 @@ module R = Map.Make (struct ;; end) -let transactions_route = - let handler client_id (request : Request.t) = - let json : Yojson.Safe.t = `Assoc [ "limite", `Int 100000; "saldo", `Int (-9098) ] in - Response.of_string ~body:(Yojson.Safe.to_string json) `OK - in - (s "clientes" / int / s "transacoes" /? nil) @--> handler -;; +module Handler = struct + let transaction client_id (db_pool : Query.pool) (request : Request.t) = + let client_opt = + Option.join @@ Result.to_option @@ Query.find_client client_id db_pool + in + match client_opt with + | Some client -> + let insert_result = + let body = Result.to_option @@ Body.to_string request.body in + let json = Option.map Yojson.Safe.from_string body in + let decoded_op = Option.bind json (Utils.Decoder.decode Operation.decoder) in + match decoded_op with + | Some op -> + (match Query.execute_operation ~client_id ~op db_pool with + | Ok _ as ok -> ok + | Error e -> Error (`DB e)) + | None -> Error (`Decoder "Invalid operation") + in + (match insert_result with + | Ok () -> + let json : Yojson.Safe.t = + `Assoc [ "limite", `Int 100000; "saldo", `Int (-9098) ] + in + Response.of_string ~body:(Yojson.Safe.to_string json) `OK + | Error _ -> Response.create (`Code 422)) + | None -> Response.create `Not_found + ;; -let balance_route = - let handler client_id (request : Request.t) = + let balance client_id (db_pool : Query.pool) (request : Request.t) = let json : Yojson.Safe.t = let balance = let total = `Int (-9098) in @@ -35,15 +54,16 @@ let balance_route = `Assoc [ "saldo", balance; "ultimas_transacoes", last_transactions ] in Response.of_string ~body:(Yojson.Safe.to_string json) `OK - in - (s "clientes" / int / s "extrato" /? nil) @--> handler -;; + ;; +end let routes = List.fold_left ~f:(fun acc (v, r) -> R.add_to_list v r acc) - [ `GET, balance_route; `POST, transactions_route; `POST, balance_route ] ~init:R.empty + [ `GET, (s "clientes" / int / s "extrato" /? nil) @--> Handler.balance + ; `POST, (s "clientes" / int / s "transacoes" /? nil) @--> Handler.transaction + ] ;; let router = R.map one_of routes diff --git a/lib/router.mli b/lib/router.mli deleted file mode 100644 index c0b5915..0000000 --- a/lib/router.mli +++ /dev/null @@ -1 +0,0 @@ -val match_route : Piaf.Method.t -> string -> (Piaf.Request.t -> Piaf.Response.t) option diff --git a/lib/utils.ml b/lib/utils.ml new file mode 100644 index 0000000..00e003a --- /dev/null +++ b/lib/utils.ml @@ -0,0 +1,121 @@ +module Decoder : sig + type 'a decoder + + val decode : 'a decoder -> Yojson.Safe.t -> 'a option + val return : 'a -> 'a decoder + val fail : 'a decoder + val bind : 'a decoder -> ('a -> 'b decoder) -> 'b decoder + val map : ('a -> 'b) -> 'a decoder -> 'b decoder + val string : string decoder + val literal : string -> string decoder + val int : int decoder + val field : string -> 'a decoder -> 'a decoder + val list : 'a decoder -> 'a list decoder + val one_of : 'a decoder list -> 'a decoder + + module Syntax : sig + val ( *> ) : 'a decoder -> 'b decoder -> 'b decoder + val ( <* ) : 'a decoder -> 'b decoder -> 'a decoder + val ( >>= ) : 'a decoder -> ('a -> 'b decoder) -> 'b decoder + val ( >>| ) : 'a decoder -> ('a -> 'b) -> 'b decoder + val ( <*> ) : ('a -> 'b) decoder -> 'a decoder -> 'b decoder + val ( <$> ) : ('a -> 'b) -> 'a decoder -> 'b decoder + val ( <: ) : string -> 'a decoder -> 'a decoder + val ( <|> ) : 'a decoder -> 'a decoder -> 'a decoder + end +end = struct + type 'a decoder = Yojson.Safe.t -> 'a option + + let decode decoder json = decoder json + let return v _ = Some v + let fail _ = None + + let map f decoder json = + match decoder json with + | Some d -> Some (f d) + | None -> None + ;; + + let bind decoder f json = + match decoder json with + | Some x -> f x `Null + | None -> None + ;; + + let rec one_of decoders json = + match decoders with + | [] -> None + | decoder :: tl -> + (match decoder json with + | Some _ as x -> x + | None -> one_of tl json) + ;; + + let string = function + | `String str -> Some str + | _ -> None + ;; + + let literal str = function + | `String s when String.equal s str -> Some s + | _ -> None + ;; + + let int = function + | `Int int -> Some int + | _ -> None + ;; + + let field key decoder json = + try decoder @@ Yojson.Safe.Util.member key json with + | _ -> None + ;; + + let list decoder json = + let rec helper acc = function + | [] -> Some [] + | hd :: tl -> + (match decoder hd with + | Some _ as x -> helper (x :: acc) tl + | None -> None) + in + match json with + | `List list -> Option.map List.rev (helper [] list) + | _ -> None + ;; + + module Syntax = struct + let ( *> ) decoder_a decoder_b yojson = + match decoder_a yojson with + | Some _ -> decoder_b yojson + | None -> None + ;; + + let ( <* ) decoder_a decoder_b yojson = + match decoder_a yojson with + | Some value -> Option.map (fun _ -> value) (decoder_b yojson) + | None -> None + ;; + + let ( >>= ) = bind + let ( >>| ) decoder f yojson = map f decoder yojson + + let ( <*> ) decoder_a decoder_b input = + match decoder_a input with + | Some f -> + (match decoder_b input with + | Some x -> Some (f x) + | None -> None) + | None -> None + ;; + + let ( <$> ) f decoder = decoder >>| f + let ( <: ) = field + + let ( <|> ) decoder_a decoder_b input = + match decoder_a input with + | Some _ as value -> value + | None -> decoder_b input + ;; + end +end |