-module(jokeapi@api). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]). -export([api_decoder/1, display_joke/1, get_any/0, get_joke_from_category/1]). -export_type([joke_response/0, flags/0, api_error/0]). -if(?OTP_RELEASE >= 27). -define(MODULEDOC(Str), -moduledoc(Str)). -define(DOC(Str), -doc(Str)). -else. -define(MODULEDOC(Str), -compile([])). -define(DOC(Str), -compile([])). -endif. -type joke_response() :: {joke_response, integer(), binary(), flags(), boolean(), binary(), binary(), binary(), binary(), boolean()}. -type flags() :: {flags, boolean(), boolean(), boolean(), boolean(), boolean(), boolean()}. -type api_error() :: {decode_error, binary()} | {generic_error, binary()} | {request_error, binary()}. -file("src/jokeapi/api.gleam", 55). ?DOC( " Decodes the flags associated with a joke from the JokeAPI.\n" " This function creates a decoder for the Flags type, which includes fields\n" " for various sensitivities such as NSFW, religious, political, racist,\n" " sexist, and explicit content. Each field is decoded as a boolean value.\n" ). -spec flags_decoder() -> gleam@dynamic@decode:decoder(flags()). flags_decoder() -> gleam@dynamic@decode:field( <<"nsfw"/utf8>>, {decoder, fun gleam@dynamic@decode:decode_bool/1}, fun(Nsfw) -> gleam@dynamic@decode:field( <<"religious"/utf8>>, {decoder, fun gleam@dynamic@decode:decode_bool/1}, fun(Religious) -> gleam@dynamic@decode:field( <<"political"/utf8>>, {decoder, fun gleam@dynamic@decode:decode_bool/1}, fun(Political) -> gleam@dynamic@decode:field( <<"racist"/utf8>>, {decoder, fun gleam@dynamic@decode:decode_bool/1}, fun(Racist) -> gleam@dynamic@decode:field( <<"sexist"/utf8>>, {decoder, fun gleam@dynamic@decode:decode_bool/1}, fun(Sexist) -> gleam@dynamic@decode:field( <<"explicit"/utf8>>, {decoder, fun gleam@dynamic@decode:decode_bool/1}, fun(Explicit) -> gleam@dynamic@decode:success( {flags, Nsfw, Religious, Political, Racist, Sexist, Explicit} ) end ) end ) end ) end ) end ) end ). -file("src/jokeapi/api.gleam", 83). ?DOC( " Decodes a JSON string into a JokeResponse, handling potential decoding errors.\n" "\n" " This function attempts to parse a JSON string and convert it into a JokeResponse\n" " using a predefined decoder. It maps any JSON decoding errors to APIError variants.\n" "\n" " # Arguments\n" " - in: A JSON string representing a joke response\n" "\n" " # Returns\n" " A Result containing either a successfully decoded JokeResponse or an APIError\n" " if decoding fails\n" ). -spec api_decoder(binary()) -> {ok, joke_response()} | {error, api_error()}. api_decoder(In) -> To_apierror = fun(In@1) -> case In@1 of unexpected_end_of_input -> {decode_error, <<"Unexpected end of input"/utf8>>}; {unexpected_byte, B} -> {decode_error, <<"Unexpected byte: "/utf8, B/binary>>}; {unexpected_sequence, S} -> {decode_error, <<"Unexpected sequence: "/utf8, S/binary>>}; {unable_to_decode, _} -> {decode_error, <<"Unable to decode"/utf8>>} end end, Main_decoder = begin gleam@dynamic@decode:field( <<"id"/utf8>>, {decoder, fun gleam@dynamic@decode:decode_int/1}, fun(Id) -> gleam@dynamic@decode:field( <<"category"/utf8>>, {decoder, fun gleam@dynamic@decode:decode_string/1}, fun(Category) -> gleam@dynamic@decode:field( <<"flags"/utf8>>, flags_decoder(), fun(Flags) -> gleam@dynamic@decode:field( <<"safe"/utf8>>, {decoder, fun gleam@dynamic@decode:decode_bool/1}, fun(Safe) -> gleam@dynamic@decode:field( <<"lang"/utf8>>, {decoder, fun gleam@dynamic@decode:decode_string/1}, fun(Lang) -> gleam@dynamic@decode:field( <<"type"/utf8>>, {decoder, fun gleam@dynamic@decode:decode_string/1}, fun(Type_) -> gleam@dynamic@decode:field( <<"setup"/utf8>>, {decoder, fun gleam@dynamic@decode:decode_string/1}, fun(Setup) -> gleam@dynamic@decode:field( <<"delivery"/utf8>>, {decoder, fun gleam@dynamic@decode:decode_string/1}, fun( Delivery ) -> gleam@dynamic@decode:field( <<"error"/utf8>>, {decoder, fun gleam@dynamic@decode:decode_bool/1}, fun( Error ) -> gleam@dynamic@decode:success( {joke_response, Id, Category, Flags, Safe, Lang, Type_, Setup, Delivery, Error} ) end ) end ) end ) end ) end ) end ) end ) end ) end ) end, _pipe = gleam@json:parse(In, Main_decoder), gleam@result:map_error(_pipe, To_apierror). -file("src/jokeapi/api.gleam", 122). ?DOC( " Converts a Hackney error into an APIError.\n" " This function maps specific Hackney error types to corresponding APIError variants,\n" " allowing for more meaningful error handling in the context of the JokeAPI.\n" ). -spec hackney_error_to_apierror(gleam@hackney:error()) -> api_error(). hackney_error_to_apierror(In) -> case In of invalid_utf8_response -> {request_error, <<"Received incompatible data"/utf8>>}; {other, _} -> {request_error, <<"Generic error from Hackney"/utf8>>} end. -file("src/jokeapi/api.gleam", 133). ?DOC( " Retrieves a joke from the JokeAPI using the provided URI.\n" " This function constructs an HTTP request to the JokeAPI,\n" " sends the request, and decodes the response into a JokeResponse.\n" " If any step fails, it returns an APIError.\n" ). -spec fetch_joke(gleam@uri:uri()) -> {ok, joke_response()} | {error, api_error()}. fetch_joke(In) -> gleam@result:'try'( begin _pipe = gleam@http@request:from_uri(In), _pipe@1 = gleam@result:map( _pipe, fun(_capture) -> gleam@http@request:set_header( _capture, <<"User-Agent"/utf8>>, <<"Joker"/utf8>> ) end ), gleam@result:map_error( _pipe@1, fun(_) -> {request_error, <<"Could not create request"/utf8>>} end ) end, fun(Request) -> gleam@result:'try'( begin _pipe@2 = gleam@hackney:send(Request), gleam@result:map_error( _pipe@2, fun hackney_error_to_apierror/1 ) end, fun(_use0) -> {response, _, _, Data} = _use0, api_decoder(Data) end ) end ). -file("src/jokeapi/api.gleam", 211). ?DOC(" Adds a flag to the list of flags if the corresponding condition is active.\n"). -spec add_flag_if_active(list(binary()), binary(), boolean()) -> list(binary()). add_flag_if_active(Flags, Flag_name, Is_active) -> case Is_active of true -> [Flag_name | Flags]; false -> Flags end. -file("src/jokeapi/api.gleam", 200). ?DOC(" Retrieves the active flags from a Flags type.\n"). -spec get_active_flags(flags()) -> list(binary()). get_active_flags(Flags) -> _pipe = [], _pipe@1 = add_flag_if_active( _pipe, <<"NSFW"/utf8>>, erlang:element(2, Flags) ), _pipe@2 = add_flag_if_active( _pipe@1, <<"Religious"/utf8>>, erlang:element(3, Flags) ), _pipe@3 = add_flag_if_active( _pipe@2, <<"Political"/utf8>>, erlang:element(4, Flags) ), _pipe@4 = add_flag_if_active( _pipe@3, <<"Racist"/utf8>>, erlang:element(5, Flags) ), _pipe@5 = add_flag_if_active( _pipe@4, <<"Sexist"/utf8>>, erlang:element(6, Flags) ), add_flag_if_active(_pipe@5, <<"Explicit"/utf8>>, erlang:element(7, Flags)). -file("src/jokeapi/api.gleam", 186). ?DOC(" Displays metadata about the joke, including language and safety status.\n"). -spec display_metadata(flags(), binary(), boolean()) -> nil. display_metadata(Flags, Lang, Safe) -> Safe_status = case Safe of true -> <<"✅ Safe"/utf8>>; false -> <<"⚠️ Not Safe"/utf8>> end, gleam_stdlib:println( <<<<<<"🏷️ Language: "/utf8, (string:uppercase(Lang))/binary>>/binary, " | "/utf8>>/binary, Safe_status/binary>> ), Active_flags = get_active_flags(Flags), case gleam@list:is_empty(Active_flags) of false -> gleam_stdlib:println( <<"🚩 Flags: "/utf8, (gleam@string:join(Active_flags, <<", "/utf8>>))/binary>> ); true -> nil end. -file("src/jokeapi/api.gleam", 173). ?DOC(" Displays a joke in a formatted manner.\n"). -spec display_joke(joke_response()) -> nil. display_joke(Joke) -> gleam_stdlib:println( <<<<<<"🎯 "/utf8, (string:uppercase(erlang:element(3, Joke)))/binary>>/binary, " JOKE #"/utf8>>/binary, (erlang:integer_to_binary(erlang:element(2, Joke)))/binary>> ), gleam_stdlib:println(<<"❓ "/utf8, (erlang:element(8, Joke))/binary>>), gleam_stdlib:println(<<"💡 "/utf8, (erlang:element(9, Joke))/binary>>), display_metadata( erlang:element(4, Joke), erlang:element(6, Joke), erlang:element(5, Joke) ). -file("src/jokeapi/api.gleam", 151). ?DOC( " Fetches a random joke from the JokeAPI.\n" " This function constructs a URI for the \"Any\" endpoint of the JokeAPI,\n" " sends a request to that endpoint, and returns a JokeResponse.\n" ). -spec get_any() -> {ok, joke_response()} | {error, api_error()}. get_any() -> gleam@result:'try'( begin _pipe = gleam_stdlib:uri_parse( <<"https://v2.jokeapi.dev/joke/"/utf8, "Any"/utf8>> ), gleam@result:map_error( _pipe, fun(_) -> {request_error, <<"Could not build URI"/utf8>>} end ) end, fun(Uri) -> fetch_joke(Uri) end ). -file("src/jokeapi/api.gleam", 162). ?DOC( " Fetches a joke in a specific category from the JokeAPI.\n" " This function constructs a URI for the \"Programming\" endpoint of the JokeAPI,\n" " sends a request to that endpoint, and returns a JokeResponse.\n" ). -spec get_joke_from_category(binary()) -> {ok, joke_response()} | {error, api_error()}. get_joke_from_category(Category) -> gleam@result:'try'( begin _pipe = gleam_stdlib:uri_parse( <<"https://v2.jokeapi.dev/joke/"/utf8, Category/binary>> ), gleam@result:map_error( _pipe, fun(_) -> {request_error, <<"Could not build URI"/utf8>>} end ) end, fun(Uri) -> fetch_joke(Uri) end ).