Skip to content

Commit cccb882

Browse files
authored
Merge pull request mouredev#3643 from luishendrix92/main
#20 - OCaml
2 parents 66ba132 + 60a114e commit cccb882

File tree

1 file changed

+188
-0
lines changed

1 file changed

+188
-0
lines changed
Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
open Lwt
2+
open Cohttp
3+
open Cohttp_lwt_unix
4+
open Yojson.Safe
5+
open Printf
6+
7+
(*****************************************************************************)
8+
(* *)
9+
(* HTTP Requests *)
10+
(* *)
11+
(* HTTP in OCaml is very complex without a library because it has to be *)
12+
(* done with the [Unix] networking module, plus it requires heavy know- *)
13+
(* ledge of sockets, protocols, parsing, etc. Thankfully, there are many *)
14+
(* well maintained libraries out there, [CoHttp] being the most popular *)
15+
(* and the one I used in this exercise. It's based on both [Async] and *)
16+
(* [Lwt] (lightweight threads, promise-based). *)
17+
(* *)
18+
(* I chose to use monadic operators (>>=, >|=) instead of specialized *)
19+
(* syntax for self-learning purposes but there is a way to use custom let *)
20+
(* bindings like [let%lwt] and [let*] that make the code look more like *)
21+
(* async/await in other languages. *)
22+
(* *)
23+
(*****************************************************************************)
24+
25+
let http_get_string url =
26+
Client.get (Uri.of_string url)
27+
>>= fun (response, body) ->
28+
let status_code = Code.code_of_status (Response.status response) in
29+
Cohttp_lwt.Body.to_string body
30+
>|= fun str_body ->
31+
if status_code >= 200 && status_code < 400
32+
then Result.Ok (status_code, str_body)
33+
else Result.Error status_code
34+
;;
35+
36+
let _ =
37+
let bacon_ipsum =
38+
"https://baconipsum.com/api/?type=all-meat&paras=1&start-with-lorem=1&format=html"
39+
in
40+
Lwt_main.run
41+
begin
42+
http_get_string bacon_ipsum
43+
>|= fun res ->
44+
match res with
45+
| Ok (code, body) ->
46+
printf "HTTP Call Successful With Code [%d] | HTML:\n" code;
47+
print_endline body
48+
| Error code -> printf "HTTP Call Failed With Status Code [%d]!\n" code
49+
end
50+
;;
51+
52+
(*****************************************************************************)
53+
(* *)
54+
(* Dificultad Extra (Opcional) *)
55+
(* *)
56+
(* Utilizando la PokéAPI (https://pokeapi.co), crea un programa por *)
57+
(* terminal al que le puedes solicitar información de un Pokémon concreto *)
58+
(* utilizando su nombre o id numérico. *)
59+
(* *)
60+
(* - Muestra el nombre, id, peso, altura, y tipo(s) del Pokémon. *)
61+
(* - Muestra el nombre de su cadena de evoluciones *)
62+
(* - Muestra los juegos en los que aparece *)
63+
(* - Controla posibles errores *)
64+
(* *)
65+
(*****************************************************************************)
66+
67+
let http_get_json url =
68+
http_get_string url
69+
>|= Result.map (fun (code, str_body) -> code, from_string str_body)
70+
;;
71+
72+
let path_exn path json = Yojson.Safe.Util.path path json |> Option.get
73+
let ( >>>= ) = Lwt_result.bind
74+
75+
module Pokedex = struct
76+
open Yojson.Safe.Util
77+
78+
module Pokemon = struct
79+
type t =
80+
{ id : int
81+
; name : string
82+
; height : int
83+
; weight : int
84+
; types : string list
85+
; games : string list
86+
; evolution_chain : string
87+
}
88+
89+
let rec deserialize_ev_chain json =
90+
let name = json |> path_exn [ "species"; "name" ] |> to_string in
91+
let evolves_to = json |> member "evolves_to" |> to_list in
92+
match evolves_to with
93+
| [] -> name
94+
| evolutions ->
95+
let branches =
96+
List.map deserialize_ev_chain evolutions |> String.concat ", "
97+
in
98+
sprintf "%s->[%s]" name branches
99+
;;
100+
101+
let of_json pk_info_json ev_chain_json =
102+
let name = pk_info_json |> member "name" |> to_string in
103+
let id = pk_info_json |> member "id" |> to_int in
104+
let weight = pk_info_json |> member "weight" |> to_int in
105+
let height = pk_info_json |> member "height" |> to_int in
106+
let games =
107+
pk_info_json
108+
|> member "game_indices"
109+
|> to_list
110+
|> List.map (fun t -> t |> path_exn [ "version"; "name" ] |> to_string)
111+
in
112+
let types =
113+
pk_info_json
114+
|> member "types"
115+
|> to_list
116+
|> List.map (fun t -> t |> path_exn [ "type"; "name" ] |> to_string)
117+
in
118+
let evolution_chain =
119+
ev_chain_json |> member "chain" |> deserialize_ev_chain
120+
in
121+
{ id; name; weight; height; types; games; evolution_chain }
122+
;;
123+
end
124+
125+
let api_base_url = "https://pokeapi.co/api/v2"
126+
127+
let display (p : Pokemon.t) =
128+
printf "Name: %s (#%d)\n" p.name p.id;
129+
printf "Height: %d | Weight: %d\n" p.height p.weight;
130+
printf "Type(s): %s\n" (String.concat ", " p.types);
131+
printf "Game(s): %s\n" (String.concat ", " p.games);
132+
printf "Evolution Chain: %s\n" p.evolution_chain
133+
;;
134+
135+
let find_by_name name =
136+
let name = Core.String.lowercase name in
137+
let pk_info_url = sprintf "%s/pokemon/%s" api_base_url name in
138+
let pk_species_url = sprintf "%s/pokemon-species/%s" api_base_url name in
139+
http_get_json pk_species_url
140+
>>>= fun (_, species_json) ->
141+
let ev_chain_url =
142+
species_json |> path_exn [ "evolution_chain"; "url" ] |> to_string
143+
in
144+
http_get_json ev_chain_url
145+
>>>= fun (_, ev_chain_json) ->
146+
http_get_json pk_info_url
147+
>>>= fun (_, pk_info_json) ->
148+
Lwt_result.return (Pokemon.of_json pk_info_json ev_chain_json)
149+
;;
150+
end
151+
152+
let _ =
153+
Moure.Io.prompt_string "Name of the pokemon: "
154+
|> Pokedex.find_by_name
155+
|> Lwt_main.run
156+
|> function
157+
| Ok pokemon -> Pokedex.display pokemon
158+
| Error code ->
159+
printf "Failed to fetch data from the PokéAPI (Status Code: %d)\n" code
160+
;;
161+
162+
(* Output of running [dune exec reto20]:
163+
-------------------------------------
164+
165+
HTTP Call Successful With Code [200] | HTML:
166+
<p>Bacon ipsum dolor amet beef capicola short loin porchetta, swine andouille buffalo cow boudin leberkas ham venison bacon. Landjaeger tail tenderloin shank, bresaola meatball andouille kielbasa boudin ball tip salami flank swine turkey. Meatloaf corned beef pork pig kielbasa biltong, sirloin chicken alcatra cow porchetta pork belly ball tip ham. Hamburger kevin ground round, flank cow biltong pastrami chislic sausage ham capicola meatloaf filet mignon corned beef cupim. Cupim leberkas frankfurter, filet mignon sirloin venison fatback.</p>
167+
168+
Name of the pokemon: wurmple
169+
Name: wurmple (#265)
170+
Height: 3 | Weight: 36
171+
Type(s): bug
172+
Game(s): ruby, sapphire, emerald, firered, leafgreen, diamond, pearl, platinum, heartgold, soulsilver, black, white, black-2, white-2
173+
Evolution Chain: wurmple->[silcoon->[beautifly], cascoon->[dustox]]
174+
175+
--------------------------------------------------------------------
176+
NOTE: The evolution chain of {b wurmple} has 2 branches:
177+
1. From wurmple to silcoon (which in turn evolves to beautifly)
178+
2. From wurmple to cascoon (which in turn evolves to dustox)
179+
Therefore, the chain expressed as a flat string can also be seen as:
180+
181+
+---------+ +-----------+
182+
+----->| Silcoon |---->| Beautifly |
183+
+---------+ | +---------+ +-----------+
184+
| Wurmple |----->o
185+
+---------+ | +---------+ +--------+
186+
+----->| Cascoon |---->| Dustox |
187+
+---------+ +--------+
188+
*)

0 commit comments

Comments
 (0)