|
| 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¶s=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