From acef1b34bcff3f7b2b1bf961dfa37e2cc938f13d Mon Sep 17 00:00:00 2001 From: Chad Miller Date: Thu, 23 Oct 2025 09:55:41 -0700 Subject: [PATCH] update deps gleam_httpc, gleam_http, gleam_json, gleam_stdlib --- .../mutation_function_generation.accepted | 41 ++--- ...query_with_inline_array_arguments.accepted | 41 ++--- ...uery_with_inline_object_arguments.accepted | 55 +++---- ...uery_with_inline_scalar_arguments.accepted | 41 ++--- ...e_root_fields_and_mixed_arguments.accepted | 106 ++++--------- ...uery_with_nested_types_generation.accepted | 43 ++---- ...ith_variables_function_generation.accepted | 44 ++---- .../simple_query_function_generation.accepted | 44 ++---- .../type_with_reserved_keywords.accepted | 54 +++---- example/gleam.toml | 10 +- example/manifest.toml | 24 +-- example/src/example.gleam | 12 +- example/src/example_with_vars.gleam | 11 +- example/src/graphql/get_character.gleam | 93 +++++------ example/src/graphql/get_characters.gleam | 79 ++++------ example/src/graphql/multi_query.gleam | 110 +++++-------- .../src/graphql/multi_query_with_vars.gleam | 146 ++++++------------ gleam.toml | 8 +- manifest.toml | 37 ++--- src/squall/internal/codegen.gleam | 97 +++++------- src/squall/internal/discovery.gleam | 2 +- src/squall/internal/schema.gleam | 136 ++++++++-------- 22 files changed, 491 insertions(+), 743 deletions(-) diff --git a/birdie_snapshots/mutation_function_generation.accepted b/birdie_snapshots/mutation_function_generation.accepted index 4956ff0..c4b5641 100644 --- a/birdie_snapshots/mutation_function_generation.accepted +++ b/birdie_snapshots/mutation_function_generation.accepted @@ -1,8 +1,8 @@ --- -version: 1.2.0 +version: 1.4.1 title: Mutation function generation --- -import gleam/dynamic +import gleam/dynamic/decode import gleam/http import gleam/http/request import gleam/httpc @@ -16,29 +16,19 @@ pub type User { User(id: String, name: String) } -pub fn user_decoder() -> dynamic.Decoder(User) { - fn(data: dynamic.Dynamic) -> Result(User, List(dynamic.DecodeError)) { - use id <- result.try(dynamic.field("id", dynamic.string)(data)) - use name <- result.try(dynamic.field("name", dynamic.string)(data)) - Ok(User(id: id, name: name)) - } +pub fn user_decoder() -> decode.Decoder(User) { + use id <- decode.field("id", decode.string) + use name <- decode.field("name", decode.string) + decode.success(User(id: id, name: name)) } pub type CreateUserResponse { CreateUserResponse(create_user: Option(User)) } -pub fn create_user_response_decoder() -> dynamic.Decoder(CreateUserResponse) { - fn(data: dynamic.Dynamic) -> Result( - CreateUserResponse, - List(dynamic.DecodeError), - ) { - use create_user <- result.try(dynamic.field( - "createUser", - dynamic.optional(user_decoder()), - )(data)) - Ok(CreateUserResponse(create_user: create_user)) - } +pub fn create_user_response_decoder() -> decode.Decoder(CreateUserResponse) { + use create_user <- decode.field("createUser", decode.optional(user_decoder())) + decode.success(CreateUserResponse(create_user: create_user)) } pub fn create_user(client: squall.Client, name: String) -> Result(CreateUserResponse, String) { @@ -66,13 +56,14 @@ pub fn create_user(client: squall.Client, name: String) -> Result(CreateUserResp |> result.map_error(fn(_) { "HTTP request failed" }), ) use json_value <- result.try( - json.decode(from: resp.body, using: dynamic.dynamic) + json.parse(from: resp.body, using: decode.dynamic) |> result.map_error(fn(_) { "Failed to decode JSON response" }), ) - use data_field <- result.try( - dynamic.field("data", dynamic.dynamic)(json_value) - |> result.map_error(fn(_) { "No data field in response" }), - ) - create_user_response_decoder()(data_field) + let data_and_response_decoder = { + use data <- decode.field("data", create_user_response_decoder()) + decode.success(data) + } + decode.run(json_value, data_and_response_decoder) |> result.map_error(fn(_) { "Failed to decode response data" }) } + diff --git a/birdie_snapshots/query_with_inline_array_arguments.accepted b/birdie_snapshots/query_with_inline_array_arguments.accepted index a22c159..8425879 100644 --- a/birdie_snapshots/query_with_inline_array_arguments.accepted +++ b/birdie_snapshots/query_with_inline_array_arguments.accepted @@ -1,8 +1,8 @@ --- -version: 1.2.0 +version: 1.4.1 title: Query with inline array arguments --- -import gleam/dynamic +import gleam/dynamic/decode import gleam/http import gleam/http/request import gleam/httpc @@ -16,29 +16,19 @@ pub type Episode { Episode(id: String, name: String) } -pub fn episode_decoder() -> dynamic.Decoder(Episode) { - fn(data: dynamic.Dynamic) -> Result(Episode, List(dynamic.DecodeError)) { - use id <- result.try(dynamic.field("id", dynamic.string)(data)) - use name <- result.try(dynamic.field("name", dynamic.string)(data)) - Ok(Episode(id: id, name: name)) - } +pub fn episode_decoder() -> decode.Decoder(Episode) { + use id <- decode.field("id", decode.string) + use name <- decode.field("name", decode.string) + decode.success(Episode(id: id, name: name)) } pub type GetEpisodesResponse { GetEpisodesResponse(episodes_by_ids: Option(List(Episode))) } -pub fn get_episodes_response_decoder() -> dynamic.Decoder(GetEpisodesResponse) { - fn(data: dynamic.Dynamic) -> Result( - GetEpisodesResponse, - List(dynamic.DecodeError), - ) { - use episodes_by_ids <- result.try(dynamic.field( - "episodesByIds", - dynamic.optional(dynamic.list(episode_decoder())), - )(data)) - Ok(GetEpisodesResponse(episodes_by_ids: episodes_by_ids)) - } +pub fn get_episodes_response_decoder() -> decode.Decoder(GetEpisodesResponse) { + use episodes_by_ids <- decode.field("episodesByIds", decode.optional(decode.list(episode_decoder()))) + decode.success(GetEpisodesResponse(episodes_by_ids: episodes_by_ids)) } pub fn get_episodes(client: squall.Client) -> Result(GetEpisodesResponse, String) { @@ -66,13 +56,14 @@ pub fn get_episodes(client: squall.Client) -> Result(GetEpisodesResponse, String |> result.map_error(fn(_) { "HTTP request failed" }), ) use json_value <- result.try( - json.decode(from: resp.body, using: dynamic.dynamic) + json.parse(from: resp.body, using: decode.dynamic) |> result.map_error(fn(_) { "Failed to decode JSON response" }), ) - use data_field <- result.try( - dynamic.field("data", dynamic.dynamic)(json_value) - |> result.map_error(fn(_) { "No data field in response" }), - ) - get_episodes_response_decoder()(data_field) + let data_and_response_decoder = { + use data <- decode.field("data", get_episodes_response_decoder()) + decode.success(data) + } + decode.run(json_value, data_and_response_decoder) |> result.map_error(fn(_) { "Failed to decode response data" }) } + diff --git a/birdie_snapshots/query_with_inline_object_arguments.accepted b/birdie_snapshots/query_with_inline_object_arguments.accepted index 06c3acc..2e998e2 100644 --- a/birdie_snapshots/query_with_inline_object_arguments.accepted +++ b/birdie_snapshots/query_with_inline_object_arguments.accepted @@ -1,8 +1,8 @@ --- -version: 1.2.0 +version: 1.4.1 title: Query with inline object arguments --- -import gleam/dynamic +import gleam/dynamic/decode import gleam/http import gleam/http/request import gleam/httpc @@ -16,46 +16,28 @@ pub type CharactersResult { CharactersResult(results: Option(List(Character))) } -pub fn characters_result_decoder() -> dynamic.Decoder(CharactersResult) { - fn(data: dynamic.Dynamic) -> Result( - CharactersResult, - List(dynamic.DecodeError), - ) { - use results <- result.try(dynamic.field( - "results", - dynamic.optional(dynamic.list(character_decoder())), - )(data)) - Ok(CharactersResult(results: results)) - } +pub fn characters_result_decoder() -> decode.Decoder(CharactersResult) { + use results <- decode.field("results", decode.optional(decode.list(character_decoder()))) + decode.success(CharactersResult(results: results)) } pub type Character { Character(id: String, name: String) } -pub fn character_decoder() -> dynamic.Decoder(Character) { - fn(data: dynamic.Dynamic) -> Result(Character, List(dynamic.DecodeError)) { - use id <- result.try(dynamic.field("id", dynamic.string)(data)) - use name <- result.try(dynamic.field("name", dynamic.string)(data)) - Ok(Character(id: id, name: name)) - } +pub fn character_decoder() -> decode.Decoder(Character) { + use id <- decode.field("id", decode.string) + use name <- decode.field("name", decode.string) + decode.success(Character(id: id, name: name)) } pub type GetCharactersResponse { GetCharactersResponse(characters: Option(CharactersResult)) } -pub fn get_characters_response_decoder() -> dynamic.Decoder(GetCharactersResponse) { - fn(data: dynamic.Dynamic) -> Result( - GetCharactersResponse, - List(dynamic.DecodeError), - ) { - use characters <- result.try(dynamic.field( - "characters", - dynamic.optional(characters_result_decoder()), - )(data)) - Ok(GetCharactersResponse(characters: characters)) - } +pub fn get_characters_response_decoder() -> decode.Decoder(GetCharactersResponse) { + use characters <- decode.field("characters", decode.optional(characters_result_decoder())) + decode.success(GetCharactersResponse(characters: characters)) } pub fn get_characters(client: squall.Client) -> Result(GetCharactersResponse, String) { @@ -83,13 +65,14 @@ pub fn get_characters(client: squall.Client) -> Result(GetCharactersResponse, St |> result.map_error(fn(_) { "HTTP request failed" }), ) use json_value <- result.try( - json.decode(from: resp.body, using: dynamic.dynamic) + json.parse(from: resp.body, using: decode.dynamic) |> result.map_error(fn(_) { "Failed to decode JSON response" }), ) - use data_field <- result.try( - dynamic.field("data", dynamic.dynamic)(json_value) - |> result.map_error(fn(_) { "No data field in response" }), - ) - get_characters_response_decoder()(data_field) + let data_and_response_decoder = { + use data <- decode.field("data", get_characters_response_decoder()) + decode.success(data) + } + decode.run(json_value, data_and_response_decoder) |> result.map_error(fn(_) { "Failed to decode response data" }) } + diff --git a/birdie_snapshots/query_with_inline_scalar_arguments.accepted b/birdie_snapshots/query_with_inline_scalar_arguments.accepted index 637f0d1..6bd5efa 100644 --- a/birdie_snapshots/query_with_inline_scalar_arguments.accepted +++ b/birdie_snapshots/query_with_inline_scalar_arguments.accepted @@ -1,8 +1,8 @@ --- -version: 1.2.0 +version: 1.4.1 title: Query with inline scalar arguments --- -import gleam/dynamic +import gleam/dynamic/decode import gleam/http import gleam/http/request import gleam/httpc @@ -16,29 +16,19 @@ pub type Character { Character(id: String, name: String) } -pub fn character_decoder() -> dynamic.Decoder(Character) { - fn(data: dynamic.Dynamic) -> Result(Character, List(dynamic.DecodeError)) { - use id <- result.try(dynamic.field("id", dynamic.string)(data)) - use name <- result.try(dynamic.field("name", dynamic.string)(data)) - Ok(Character(id: id, name: name)) - } +pub fn character_decoder() -> decode.Decoder(Character) { + use id <- decode.field("id", decode.string) + use name <- decode.field("name", decode.string) + decode.success(Character(id: id, name: name)) } pub type GetCharacterResponse { GetCharacterResponse(character: Option(Character)) } -pub fn get_character_response_decoder() -> dynamic.Decoder(GetCharacterResponse) { - fn(data: dynamic.Dynamic) -> Result( - GetCharacterResponse, - List(dynamic.DecodeError), - ) { - use character <- result.try(dynamic.field( - "character", - dynamic.optional(character_decoder()), - )(data)) - Ok(GetCharacterResponse(character: character)) - } +pub fn get_character_response_decoder() -> decode.Decoder(GetCharacterResponse) { + use character <- decode.field("character", decode.optional(character_decoder())) + decode.success(GetCharacterResponse(character: character)) } pub fn get_character(client: squall.Client) -> Result(GetCharacterResponse, String) { @@ -66,13 +56,14 @@ pub fn get_character(client: squall.Client) -> Result(GetCharacterResponse, Stri |> result.map_error(fn(_) { "HTTP request failed" }), ) use json_value <- result.try( - json.decode(from: resp.body, using: dynamic.dynamic) + json.parse(from: resp.body, using: decode.dynamic) |> result.map_error(fn(_) { "Failed to decode JSON response" }), ) - use data_field <- result.try( - dynamic.field("data", dynamic.dynamic)(json_value) - |> result.map_error(fn(_) { "No data field in response" }), - ) - get_character_response_decoder()(data_field) + let data_and_response_decoder = { + use data <- decode.field("data", get_character_response_decoder()) + decode.success(data) + } + decode.run(json_value, data_and_response_decoder) |> result.map_error(fn(_) { "Failed to decode response data" }) } + diff --git a/birdie_snapshots/query_with_multiple_root_fields_and_mixed_arguments.accepted b/birdie_snapshots/query_with_multiple_root_fields_and_mixed_arguments.accepted index b188262..a9f585e 100644 --- a/birdie_snapshots/query_with_multiple_root_fields_and_mixed_arguments.accepted +++ b/birdie_snapshots/query_with_multiple_root_fields_and_mixed_arguments.accepted @@ -1,8 +1,8 @@ --- -version: 1.2.0 +version: 1.4.1 title: Query with multiple root fields and mixed arguments --- -import gleam/dynamic +import gleam/dynamic/decode import gleam/http import gleam/http/request import gleam/httpc @@ -16,71 +16,46 @@ pub type CharactersResult { CharactersResult(info: Option(Info), results: Option(List(Character))) } -pub fn characters_result_decoder() -> dynamic.Decoder(CharactersResult) { - fn(data: dynamic.Dynamic) -> Result( - CharactersResult, - List(dynamic.DecodeError), - ) { - use info <- result.try(dynamic.field( - "info", - dynamic.optional(info_decoder()), - )(data)) - use results <- result.try(dynamic.field( - "results", - dynamic.optional(dynamic.list(character_decoder())), - )(data)) - Ok(CharactersResult(info: info, results: results)) - } +pub fn characters_result_decoder() -> decode.Decoder(CharactersResult) { + use info <- decode.field("info", decode.optional(info_decoder())) + use results <- decode.field("results", decode.optional(decode.list(character_decoder()))) + decode.success(CharactersResult(info: info, results: results)) } pub type Info { Info(count: Option(Int)) } -pub fn info_decoder() -> dynamic.Decoder(Info) { - fn(data: dynamic.Dynamic) -> Result(Info, List(dynamic.DecodeError)) { - use count <- result.try(dynamic.field( - "count", - dynamic.optional(dynamic.int), - )(data)) - Ok(Info(count: count)) - } +pub fn info_decoder() -> decode.Decoder(Info) { + use count <- decode.field("count", decode.optional(decode.int)) + decode.success(Info(count: count)) } pub type Character { Character(name: Option(String)) } -pub fn character_decoder() -> dynamic.Decoder(Character) { - fn(data: dynamic.Dynamic) -> Result(Character, List(dynamic.DecodeError)) { - use name <- result.try(dynamic.field( - "name", - dynamic.optional(dynamic.string), - )(data)) - Ok(Character(name: name)) - } +pub fn character_decoder() -> decode.Decoder(Character) { + use name <- decode.field("name", decode.optional(decode.string)) + decode.success(Character(name: name)) } pub type Location { Location(id: Option(String)) } -pub fn location_decoder() -> dynamic.Decoder(Location) { - fn(data: dynamic.Dynamic) -> Result(Location, List(dynamic.DecodeError)) { - use id <- result.try(dynamic.field("id", dynamic.optional(dynamic.string))(data)) - Ok(Location(id: id)) - } +pub fn location_decoder() -> decode.Decoder(Location) { + use id <- decode.field("id", decode.optional(decode.string)) + decode.success(Location(id: id)) } pub type Episode { Episode(id: Option(String)) } -pub fn episode_decoder() -> dynamic.Decoder(Episode) { - fn(data: dynamic.Dynamic) -> Result(Episode, List(dynamic.DecodeError)) { - use id <- result.try(dynamic.field("id", dynamic.optional(dynamic.string))(data)) - Ok(Episode(id: id)) - } +pub fn episode_decoder() -> decode.Decoder(Episode) { + use id <- decode.field("id", decode.optional(decode.string)) + decode.success(Episode(id: id)) } pub type MultiQueryResponse { @@ -91,29 +66,15 @@ pub type MultiQueryResponse { ) } -pub fn multi_query_response_decoder() -> dynamic.Decoder(MultiQueryResponse) { - fn(data: dynamic.Dynamic) -> Result( - MultiQueryResponse, - List(dynamic.DecodeError), - ) { - use characters <- result.try(dynamic.field( - "characters", - dynamic.optional(characters_result_decoder()), - )(data)) - use location <- result.try(dynamic.field( - "location", - dynamic.optional(location_decoder()), - )(data)) - use episodes_by_ids <- result.try(dynamic.field( - "episodesByIds", - dynamic.optional(dynamic.list(episode_decoder())), - )(data)) - Ok(MultiQueryResponse( - characters: characters, - location: location, - episodes_by_ids: episodes_by_ids, - )) - } +pub fn multi_query_response_decoder() -> decode.Decoder(MultiQueryResponse) { + use characters <- decode.field("characters", decode.optional(characters_result_decoder())) + use location <- decode.field("location", decode.optional(location_decoder())) + use episodes_by_ids <- decode.field("episodesByIds", decode.optional(decode.list(episode_decoder()))) + decode.success(MultiQueryResponse( + characters: characters, + location: location, + episodes_by_ids: episodes_by_ids, + )) } pub fn multi_query(client: squall.Client) -> Result(MultiQueryResponse, String) { @@ -141,13 +102,14 @@ pub fn multi_query(client: squall.Client) -> Result(MultiQueryResponse, String) |> result.map_error(fn(_) { "HTTP request failed" }), ) use json_value <- result.try( - json.decode(from: resp.body, using: dynamic.dynamic) + json.parse(from: resp.body, using: decode.dynamic) |> result.map_error(fn(_) { "Failed to decode JSON response" }), ) - use data_field <- result.try( - dynamic.field("data", dynamic.dynamic)(json_value) - |> result.map_error(fn(_) { "No data field in response" }), - ) - multi_query_response_decoder()(data_field) + let data_and_response_decoder = { + use data <- decode.field("data", multi_query_response_decoder()) + decode.success(data) + } + decode.run(json_value, data_and_response_decoder) |> result.map_error(fn(_) { "Failed to decode response data" }) } + diff --git a/birdie_snapshots/query_with_nested_types_generation.accepted b/birdie_snapshots/query_with_nested_types_generation.accepted index a7e3e00..cfd8dd0 100644 --- a/birdie_snapshots/query_with_nested_types_generation.accepted +++ b/birdie_snapshots/query_with_nested_types_generation.accepted @@ -1,8 +1,8 @@ --- -version: 1.2.0 +version: 1.4.1 title: Query with nested types generation --- -import gleam/dynamic +import gleam/dynamic/decode import gleam/http import gleam/http/request import gleam/httpc @@ -16,30 +16,20 @@ pub type Character { Character(id: String, name: String, status: String) } -pub fn character_decoder() -> dynamic.Decoder(Character) { - fn(data: dynamic.Dynamic) -> Result(Character, List(dynamic.DecodeError)) { - use id <- result.try(dynamic.field("id", dynamic.string)(data)) - use name <- result.try(dynamic.field("name", dynamic.string)(data)) - use status <- result.try(dynamic.field("status", dynamic.string)(data)) - Ok(Character(id: id, name: name, status: status)) - } +pub fn character_decoder() -> decode.Decoder(Character) { + use id <- decode.field("id", decode.string) + use name <- decode.field("name", decode.string) + use status <- decode.field("status", decode.string) + decode.success(Character(id: id, name: name, status: status)) } pub type GetCharacterResponse { GetCharacterResponse(character: Option(Character)) } -pub fn get_character_response_decoder() -> dynamic.Decoder(GetCharacterResponse) { - fn(data: dynamic.Dynamic) -> Result( - GetCharacterResponse, - List(dynamic.DecodeError), - ) { - use character <- result.try(dynamic.field( - "character", - dynamic.optional(character_decoder()), - )(data)) - Ok(GetCharacterResponse(character: character)) - } +pub fn get_character_response_decoder() -> decode.Decoder(GetCharacterResponse) { + use character <- decode.field("character", decode.optional(character_decoder())) + decode.success(GetCharacterResponse(character: character)) } pub fn get_character(client: squall.Client, id: String) -> Result(GetCharacterResponse, String) { @@ -67,13 +57,14 @@ pub fn get_character(client: squall.Client, id: String) -> Result(GetCharacterRe |> result.map_error(fn(_) { "HTTP request failed" }), ) use json_value <- result.try( - json.decode(from: resp.body, using: dynamic.dynamic) + json.parse(from: resp.body, using: decode.dynamic) |> result.map_error(fn(_) { "Failed to decode JSON response" }), ) - use data_field <- result.try( - dynamic.field("data", dynamic.dynamic)(json_value) - |> result.map_error(fn(_) { "No data field in response" }), - ) - get_character_response_decoder()(data_field) + let data_and_response_decoder = { + use data <- decode.field("data", get_character_response_decoder()) + decode.success(data) + } + decode.run(json_value, data_and_response_decoder) |> result.map_error(fn(_) { "Failed to decode response data" }) } + diff --git a/birdie_snapshots/query_with_variables_function_generation.accepted b/birdie_snapshots/query_with_variables_function_generation.accepted index 78b4fed..9747ec3 100644 --- a/birdie_snapshots/query_with_variables_function_generation.accepted +++ b/birdie_snapshots/query_with_variables_function_generation.accepted @@ -1,8 +1,8 @@ --- -version: 1.2.0 +version: 1.4.1 title: Query with variables function generation --- -import gleam/dynamic +import gleam/dynamic/decode import gleam/http import gleam/http/request import gleam/httpc @@ -16,32 +16,19 @@ pub type User { User(id: String, name: Option(String)) } -pub fn user_decoder() -> dynamic.Decoder(User) { - fn(data: dynamic.Dynamic) -> Result(User, List(dynamic.DecodeError)) { - use id <- result.try(dynamic.field("id", dynamic.string)(data)) - use name <- result.try(dynamic.field( - "name", - dynamic.optional(dynamic.string), - )(data)) - Ok(User(id: id, name: name)) - } +pub fn user_decoder() -> decode.Decoder(User) { + use id <- decode.field("id", decode.string) + use name <- decode.field("name", decode.optional(decode.string)) + decode.success(User(id: id, name: name)) } pub type GetUserResponse { GetUserResponse(user: Option(User)) } -pub fn get_user_response_decoder() -> dynamic.Decoder(GetUserResponse) { - fn(data: dynamic.Dynamic) -> Result( - GetUserResponse, - List(dynamic.DecodeError), - ) { - use user <- result.try(dynamic.field( - "user", - dynamic.optional(user_decoder()), - )(data)) - Ok(GetUserResponse(user: user)) - } +pub fn get_user_response_decoder() -> decode.Decoder(GetUserResponse) { + use user <- decode.field("user", decode.optional(user_decoder())) + decode.success(GetUserResponse(user: user)) } pub fn get_user(client: squall.Client, id: String) -> Result(GetUserResponse, String) { @@ -69,13 +56,14 @@ pub fn get_user(client: squall.Client, id: String) -> Result(GetUserResponse, St |> result.map_error(fn(_) { "HTTP request failed" }), ) use json_value <- result.try( - json.decode(from: resp.body, using: dynamic.dynamic) + json.parse(from: resp.body, using: decode.dynamic) |> result.map_error(fn(_) { "Failed to decode JSON response" }), ) - use data_field <- result.try( - dynamic.field("data", dynamic.dynamic)(json_value) - |> result.map_error(fn(_) { "No data field in response" }), - ) - get_user_response_decoder()(data_field) + let data_and_response_decoder = { + use data <- decode.field("data", get_user_response_decoder()) + decode.success(data) + } + decode.run(json_value, data_and_response_decoder) |> result.map_error(fn(_) { "Failed to decode response data" }) } + diff --git a/birdie_snapshots/simple_query_function_generation.accepted b/birdie_snapshots/simple_query_function_generation.accepted index 8e8f536..c74c87b 100644 --- a/birdie_snapshots/simple_query_function_generation.accepted +++ b/birdie_snapshots/simple_query_function_generation.accepted @@ -1,8 +1,8 @@ --- -version: 1.2.0 +version: 1.4.1 title: Simple query function generation --- -import gleam/dynamic +import gleam/dynamic/decode import gleam/http import gleam/http/request import gleam/httpc @@ -16,32 +16,19 @@ pub type User { User(id: String, name: Option(String)) } -pub fn user_decoder() -> dynamic.Decoder(User) { - fn(data: dynamic.Dynamic) -> Result(User, List(dynamic.DecodeError)) { - use id <- result.try(dynamic.field("id", dynamic.string)(data)) - use name <- result.try(dynamic.field( - "name", - dynamic.optional(dynamic.string), - )(data)) - Ok(User(id: id, name: name)) - } +pub fn user_decoder() -> decode.Decoder(User) { + use id <- decode.field("id", decode.string) + use name <- decode.field("name", decode.optional(decode.string)) + decode.success(User(id: id, name: name)) } pub type GetUserResponse { GetUserResponse(user: Option(User)) } -pub fn get_user_response_decoder() -> dynamic.Decoder(GetUserResponse) { - fn(data: dynamic.Dynamic) -> Result( - GetUserResponse, - List(dynamic.DecodeError), - ) { - use user <- result.try(dynamic.field( - "user", - dynamic.optional(user_decoder()), - )(data)) - Ok(GetUserResponse(user: user)) - } +pub fn get_user_response_decoder() -> decode.Decoder(GetUserResponse) { + use user <- decode.field("user", decode.optional(user_decoder())) + decode.success(GetUserResponse(user: user)) } pub fn get_user(client: squall.Client) -> Result(GetUserResponse, String) { @@ -69,13 +56,14 @@ pub fn get_user(client: squall.Client) -> Result(GetUserResponse, String) { |> result.map_error(fn(_) { "HTTP request failed" }), ) use json_value <- result.try( - json.decode(from: resp.body, using: dynamic.dynamic) + json.parse(from: resp.body, using: decode.dynamic) |> result.map_error(fn(_) { "Failed to decode JSON response" }), ) - use data_field <- result.try( - dynamic.field("data", dynamic.dynamic)(json_value) - |> result.map_error(fn(_) { "No data field in response" }), - ) - get_user_response_decoder()(data_field) + let data_and_response_decoder = { + use data <- decode.field("data", get_user_response_decoder()) + decode.success(data) + } + decode.run(json_value, data_and_response_decoder) |> result.map_error(fn(_) { "Failed to decode response data" }) } + diff --git a/birdie_snapshots/type_with_reserved_keywords.accepted b/birdie_snapshots/type_with_reserved_keywords.accepted index f0c122d..44b0e9d 100644 --- a/birdie_snapshots/type_with_reserved_keywords.accepted +++ b/birdie_snapshots/type_with_reserved_keywords.accepted @@ -1,8 +1,8 @@ --- -version: 1.2.0 +version: 1.4.1 title: Type with reserved keywords --- -import gleam/dynamic +import gleam/dynamic/decode import gleam/http import gleam/http/request import gleam/httpc @@ -21,40 +21,21 @@ pub type Item { ) } -pub fn item_decoder() -> dynamic.Decoder(Item) { - fn(data: dynamic.Dynamic) -> Result(Item, List(dynamic.DecodeError)) { - use id <- result.try(dynamic.field("id", dynamic.string)(data)) - use type_ <- result.try(dynamic.field( - "type", - dynamic.optional(dynamic.string), - )(data)) - use case_ <- result.try(dynamic.field( - "case", - dynamic.optional(dynamic.string), - )(data)) - use let_ <- result.try(dynamic.field( - "let", - dynamic.optional(dynamic.string), - )(data)) - Ok(Item(id: id, type_: type_, case_: case_, let_: let_)) - } +pub fn item_decoder() -> decode.Decoder(Item) { + use id <- decode.field("id", decode.string) + use type_ <- decode.field("type", decode.optional(decode.string)) + use case_ <- decode.field("case", decode.optional(decode.string)) + use let_ <- decode.field("let", decode.optional(decode.string)) + decode.success(Item(id: id, type_: type_, case_: case_, let_: let_)) } pub type GetItemResponse { GetItemResponse(item: Option(Item)) } -pub fn get_item_response_decoder() -> dynamic.Decoder(GetItemResponse) { - fn(data: dynamic.Dynamic) -> Result( - GetItemResponse, - List(dynamic.DecodeError), - ) { - use item <- result.try(dynamic.field( - "item", - dynamic.optional(item_decoder()), - )(data)) - Ok(GetItemResponse(item: item)) - } +pub fn get_item_response_decoder() -> decode.Decoder(GetItemResponse) { + use item <- decode.field("item", decode.optional(item_decoder())) + decode.success(GetItemResponse(item: item)) } pub fn get_item(client: squall.Client) -> Result(GetItemResponse, String) { @@ -82,13 +63,14 @@ pub fn get_item(client: squall.Client) -> Result(GetItemResponse, String) { |> result.map_error(fn(_) { "HTTP request failed" }), ) use json_value <- result.try( - json.decode(from: resp.body, using: dynamic.dynamic) + json.parse(from: resp.body, using: decode.dynamic) |> result.map_error(fn(_) { "Failed to decode JSON response" }), ) - use data_field <- result.try( - dynamic.field("data", dynamic.dynamic)(json_value) - |> result.map_error(fn(_) { "No data field in response" }), - ) - get_item_response_decoder()(data_field) + let data_and_response_decoder = { + use data <- decode.field("data", get_item_response_decoder()) + decode.success(data) + } + decode.run(json_value, data_and_response_decoder) |> result.map_error(fn(_) { "Failed to decode response data" }) } + diff --git a/example/gleam.toml b/example/gleam.toml index 6ebf01f..bfb4ead 100644 --- a/example/gleam.toml +++ b/example/gleam.toml @@ -3,10 +3,10 @@ version = "0.1.0" description = "Example project using Squall with Rick and Morty API" [dependencies] -gleam_stdlib = ">= 0.34.0 and < 0.40.0" -gleam_json = ">= 1.0.0 and < 2.0.0" -gleam_http = ">= 3.0.0 and < 4.0.0" -gleam_httpc = ">= 2.0.0 and < 3.0.0" +gleam_stdlib = ">= 0.65.0 and < 0.66.0" +gleam_json = ">= 3.0.0 and < 4.0.0" +gleam_http = ">= 4.3.0 and < 5.0.0" +gleam_httpc = ">= 5.0.0 and < 6.0.0" +squall = { path = "../" } [dev-dependencies] -squall = { path = "../" } diff --git a/example/manifest.toml b/example/manifest.toml index 5806133..0944aa4 100644 --- a/example/manifest.toml +++ b/example/manifest.toml @@ -3,20 +3,20 @@ packages = [ { name = "argv", version = "1.0.2", build_tools = ["gleam"], requirements = [], otp_app = "argv", source = "hex", outer_checksum = "BA1FF0929525DEBA1CE67256E5ADF77A7CDDFE729E3E3F57A5BDCAA031DED09D" }, - { name = "filepath", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "filepath", source = "hex", outer_checksum = "EFB6FF65C98B2A16378ABC3EE2B14124168C0CE5201553DE652E2644DCFDB594" }, - { name = "glam", version = "2.0.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "glam", source = "hex", outer_checksum = "4932A2D139AB0389E149396407F89654928D7B815E212BB02F13C66F53B1BBA1" }, - { name = "gleam_http", version = "3.7.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_http", source = "hex", outer_checksum = "A9EE0722106FCCAB8AD3BF9D0A3EFF92BFE8561D59B83BAE96EB0BE1938D4E0F" }, - { name = "gleam_httpc", version = "2.3.0", build_tools = ["gleam"], requirements = ["gleam_http", "gleam_stdlib"], otp_app = "gleam_httpc", source = "hex", outer_checksum = "CF6CDD88830CC9853F7638ECC0BE7D7CD9522640DA5FAB4C08CFAC8DEBD08028" }, - { name = "gleam_json", version = "1.0.1", build_tools = ["gleam"], requirements = ["gleam_stdlib", "thoas"], otp_app = "gleam_json", source = "hex", outer_checksum = "9063D14D25406326C0255BDA0021541E797D8A7A12573D849462CAFED459F6EB" }, - { name = "gleam_stdlib", version = "0.39.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "2D7DE885A6EA7F1D5015D1698920C9BAF7241102836CE0C3837A4F160128A9C4" }, + { name = "filepath", version = "1.1.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "filepath", source = "hex", outer_checksum = "B06A9AF0BF10E51401D64B98E4B627F1D2E48C154967DA7AF4D0914780A6D40A" }, + { name = "glam", version = "2.0.3", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "glam", source = "hex", outer_checksum = "237C2CE218A2A0A5D46D625F8EF5B78F964BC91018B78D692B17E1AB84295229" }, + { name = "gleam_erlang", version = "1.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_erlang", source = "hex", outer_checksum = "1124AD3AA21143E5AF0FC5CF3D9529F6DB8CA03E43A55711B60B6B7B3874375C" }, + { name = "gleam_http", version = "4.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_http", source = "hex", outer_checksum = "82EA6A717C842456188C190AFB372665EA56CE13D8559BF3B1DD9E40F619EE0C" }, + { name = "gleam_httpc", version = "5.0.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_http", "gleam_stdlib"], otp_app = "gleam_httpc", source = "hex", outer_checksum = "C545172618D07811494E97AAA4A0FB34DA6F6D0061FDC8041C2F8E3BE2B2E48F" }, + { name = "gleam_json", version = "3.0.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_json", source = "hex", outer_checksum = "874FA3C3BB6E22DD2BB111966BD40B3759E9094E05257899A7C08F5DE77EC049" }, + { name = "gleam_stdlib", version = "0.65.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "7C69C71D8C493AE11A5184828A77110EB05A7786EBF8B25B36A72F879C3EE107" }, { name = "simplifile", version = "2.3.0", build_tools = ["gleam"], requirements = ["filepath", "gleam_stdlib"], otp_app = "simplifile", source = "hex", outer_checksum = "0A868DAC6063D9E983477981839810DC2E553285AB4588B87E3E9C96A7FB4CB4" }, - { name = "squall", version = "1.0.0", build_tools = ["gleam"], requirements = ["argv", "filepath", "glam", "gleam_http", "gleam_httpc", "gleam_json", "gleam_stdlib", "simplifile"], source = "local", path = ".." }, - { name = "thoas", version = "1.2.1", build_tools = ["rebar3"], requirements = [], otp_app = "thoas", source = "hex", outer_checksum = "E38697EDFFD6E91BD12CEA41B155115282630075C2A727E7A6B2947F5408B86A" }, + { name = "squall", version = "0.1.0", build_tools = ["gleam"], requirements = ["argv", "filepath", "glam", "gleam_http", "gleam_httpc", "gleam_json", "gleam_stdlib", "simplifile"], source = "local", path = ".." }, ] [requirements] -gleam_http = { version = ">= 3.0.0 and < 4.0.0" } -gleam_httpc = { version = ">= 2.0.0 and < 3.0.0" } -gleam_json = { version = ">= 1.0.0 and < 2.0.0" } -gleam_stdlib = { version = ">= 0.34.0 and < 0.40.0" } +gleam_http = { version = ">= 4.3.0 and < 5.0.0" } +gleam_httpc = { version = ">= 5.0.0 and < 6.0.0" } +gleam_json = { version = ">= 3.0.0 and < 4.0.0" } +gleam_stdlib = { version = ">= 0.65.0 and < 0.66.0" } squall = { path = "../" } diff --git a/example/src/example.gleam b/example/src/example.gleam index 2ead574..0581efc 100644 --- a/example/src/example.gleam +++ b/example/src/example.gleam @@ -1,17 +1,21 @@ import gleam/io +import gleam/string import graphql/multi_query +import squall pub fn main() { io.println("Squall Multi-Field Query Example") io.println("=================================\n") - let result = - multi_query.multi_query("https://rickandmortyapi.com/graphql") + let client = + squall.new_client("https://rickandmortyapi.com/graphql", []) + + let result = multi_query.multi_query(client) case result { Ok(response) -> { - io.println("Response:") - io.debug(response) + io.println("Success! Received response from API") + io.println("Response data: " <> string.inspect(response)) Nil } Error(err) -> { diff --git a/example/src/example_with_vars.gleam b/example/src/example_with_vars.gleam index de99c89..270957e 100644 --- a/example/src/example_with_vars.gleam +++ b/example/src/example_with_vars.gleam @@ -1,5 +1,7 @@ import gleam/io +import gleam/string import graphql/multi_query_with_vars +import squall pub fn main() { io.println("Squall Multi-Field Query Example (with Variables)") @@ -11,9 +13,12 @@ pub fn main() { io.println(" locationId: \"1\"") io.println(" episodeIds: [1, 2]\n") + let client = + squall.new_client("https://rickandmortyapi.com/graphql", []) + let result = multi_query_with_vars.multi_query_with_vars( - "https://rickandmortyapi.com/graphql", + client, 2, "rick", "1", @@ -22,8 +27,8 @@ pub fn main() { case result { Ok(response) -> { - io.println("Response:") - io.debug(response) + io.println("Success! Received response from API") + io.println("Response data: " <> string.inspect(response)) Nil } Error(err) -> { diff --git a/example/src/graphql/get_character.gleam b/example/src/graphql/get_character.gleam index d47cf80..e55c033 100644 --- a/example/src/graphql/get_character.gleam +++ b/example/src/graphql/get_character.gleam @@ -1,10 +1,12 @@ -import gleam/dynamic +import gleam/dynamic/decode import gleam/http import gleam/http/request import gleam/httpc import gleam/json +import gleam/list import gleam/option.{type Option} import gleam/result +import squall pub type Character { Character( @@ -17,70 +19,41 @@ pub type Character { ) } -pub fn character_decoder() -> dynamic.Decoder(Character) { - fn(data: dynamic.Dynamic) -> Result(Character, List(dynamic.DecodeError)) { - use id <- result.try(dynamic.field("id", dynamic.optional(dynamic.string))( - data, - )) - use name <- result.try(dynamic.field( - "name", - dynamic.optional(dynamic.string), - )(data)) - use status <- result.try(dynamic.field( - "status", - dynamic.optional(dynamic.string), - )(data)) - use species <- result.try(dynamic.field( - "species", - dynamic.optional(dynamic.string), - )(data)) - use type_ <- result.try(dynamic.field( - "type", - dynamic.optional(dynamic.string), - )(data)) - use gender <- result.try(dynamic.field( - "gender", - dynamic.optional(dynamic.string), - )(data)) - Ok(Character( - id: id, - name: name, - status: status, - species: species, - type_: type_, - gender: gender, - )) - } +pub fn character_decoder() -> decode.Decoder(Character) { + use id <- decode.field("id", decode.optional(decode.string)) + use name <- decode.field("name", decode.optional(decode.string)) + use status <- decode.field("status", decode.optional(decode.string)) + use species <- decode.field("species", decode.optional(decode.string)) + use type_ <- decode.field("type", decode.optional(decode.string)) + use gender <- decode.field("gender", decode.optional(decode.string)) + decode.success(Character( + id: id, + name: name, + status: status, + species: species, + type_: type_, + gender: gender, + )) } pub type GetCharacterResponse { GetCharacterResponse(character: Option(Character)) } -pub fn get_character_response_decoder() -> dynamic.Decoder(GetCharacterResponse) { - fn(data: dynamic.Dynamic) -> Result( - GetCharacterResponse, - List(dynamic.DecodeError), - ) { - use character <- result.try(dynamic.field( - "character", - dynamic.optional(character_decoder()), - )(data)) - Ok(GetCharacterResponse(character: character)) - } +pub fn get_character_response_decoder() -> decode.Decoder(GetCharacterResponse) { + use character <- decode.field("character", decode.optional(character_decoder())) + decode.success(GetCharacterResponse(character: character)) } -pub fn get_character( - endpoint: String, - id: String, -) -> Result(GetCharacterResponse, String) { +pub fn get_character(client: squall.Client, id: String) -> Result(GetCharacterResponse, String) { let query = "query GetCharacter($id: ID!) { character(id: $id) { id name status species type gender } }" - let variables = json.object([#("id", json.string(id))]) + let variables = + json.object([#("id", json.string(id))]) let body = json.object([#("query", json.string(query)), #("variables", variables)]) use req <- result.try( - request.to(endpoint) + request.to(client.endpoint) |> result.map_error(fn(_) { "Invalid endpoint URL" }), ) let req = @@ -88,18 +61,22 @@ pub fn get_character( |> request.set_method(http.Post) |> request.set_body(json.to_string(body)) |> request.set_header("content-type", "application/json") + let req = + list.fold(client.headers, req, fn(r, header) { + request.set_header(r, header.0, header.1) + }) use resp <- result.try( httpc.send(req) |> result.map_error(fn(_) { "HTTP request failed" }), ) use json_value <- result.try( - json.decode(from: resp.body, using: dynamic.dynamic) + json.parse(from: resp.body, using: decode.dynamic) |> result.map_error(fn(_) { "Failed to decode JSON response" }), ) - use data_field <- result.try( - dynamic.field("data", dynamic.dynamic)(json_value) - |> result.map_error(fn(_) { "No data field in response" }), - ) - get_character_response_decoder()(data_field) + let data_and_response_decoder = { + use data <- decode.field("data", get_character_response_decoder()) + decode.success(data) + } + decode.run(json_value, data_and_response_decoder) |> result.map_error(fn(_) { "Failed to decode response data" }) } diff --git a/example/src/graphql/get_characters.gleam b/example/src/graphql/get_characters.gleam index 6b7bd2c..f8ff977 100644 --- a/example/src/graphql/get_characters.gleam +++ b/example/src/graphql/get_characters.gleam @@ -1,23 +1,20 @@ -import gleam/dynamic +import gleam/dynamic/decode import gleam/http import gleam/http/request import gleam/httpc import gleam/json +import gleam/list import gleam/option.{type Option} import gleam/result +import squall pub type Characters { Characters(results: Option(List(Character))) } -pub fn characters_decoder() -> dynamic.Decoder(Characters) { - fn(data: dynamic.Dynamic) -> Result(Characters, List(dynamic.DecodeError)) { - use results <- result.try(dynamic.field( - "results", - dynamic.optional(dynamic.list(character_decoder())), - )(data)) - Ok(Characters(results: results)) - } +pub fn characters_decoder() -> decode.Decoder(Characters) { + use results <- decode.field("results", decode.optional(decode.list(character_decoder()))) + decode.success(Characters(results: results)) } pub type Character { @@ -29,54 +26,32 @@ pub type Character { ) } -pub fn character_decoder() -> dynamic.Decoder(Character) { - fn(data: dynamic.Dynamic) -> Result(Character, List(dynamic.DecodeError)) { - use id <- result.try(dynamic.field("id", dynamic.optional(dynamic.string))( - data, - )) - use name <- result.try(dynamic.field( - "name", - dynamic.optional(dynamic.string), - )(data)) - use status <- result.try(dynamic.field( - "status", - dynamic.optional(dynamic.string), - )(data)) - use species <- result.try(dynamic.field( - "species", - dynamic.optional(dynamic.string), - )(data)) - Ok(Character(id: id, name: name, status: status, species: species)) - } +pub fn character_decoder() -> decode.Decoder(Character) { + use id <- decode.field("id", decode.optional(decode.string)) + use name <- decode.field("name", decode.optional(decode.string)) + use status <- decode.field("status", decode.optional(decode.string)) + use species <- decode.field("species", decode.optional(decode.string)) + decode.success(Character(id: id, name: name, status: status, species: species)) } pub type GetCharactersResponse { GetCharactersResponse(characters: Option(Characters)) } -pub fn get_characters_response_decoder() -> dynamic.Decoder( - GetCharactersResponse, -) { - fn(data: dynamic.Dynamic) -> Result( - GetCharactersResponse, - List(dynamic.DecodeError), - ) { - use characters <- result.try(dynamic.field( - "characters", - dynamic.optional(characters_decoder()), - )(data)) - Ok(GetCharactersResponse(characters: characters)) - } +pub fn get_characters_response_decoder() -> decode.Decoder(GetCharactersResponse) { + use characters <- decode.field("characters", decode.optional(characters_decoder())) + decode.success(GetCharactersResponse(characters: characters)) } -pub fn get_characters(endpoint: String) -> Result(GetCharactersResponse, String) { +pub fn get_characters(client: squall.Client) -> Result(GetCharactersResponse, String) { let query = "query GetCharacters { characters { results { id name status species } } }" - let variables = json.object([]) + let variables = + json.object([]) let body = json.object([#("query", json.string(query)), #("variables", variables)]) use req <- result.try( - request.to(endpoint) + request.to(client.endpoint) |> result.map_error(fn(_) { "Invalid endpoint URL" }), ) let req = @@ -84,18 +59,22 @@ pub fn get_characters(endpoint: String) -> Result(GetCharactersResponse, String) |> request.set_method(http.Post) |> request.set_body(json.to_string(body)) |> request.set_header("content-type", "application/json") + let req = + list.fold(client.headers, req, fn(r, header) { + request.set_header(r, header.0, header.1) + }) use resp <- result.try( httpc.send(req) |> result.map_error(fn(_) { "HTTP request failed" }), ) use json_value <- result.try( - json.decode(from: resp.body, using: dynamic.dynamic) + json.parse(from: resp.body, using: decode.dynamic) |> result.map_error(fn(_) { "Failed to decode JSON response" }), ) - use data_field <- result.try( - dynamic.field("data", dynamic.dynamic)(json_value) - |> result.map_error(fn(_) { "No data field in response" }), - ) - get_characters_response_decoder()(data_field) + let data_and_response_decoder = { + use data <- decode.field("data", get_characters_response_decoder()) + decode.success(data) + } + decode.run(json_value, data_and_response_decoder) |> result.map_error(fn(_) { "Failed to decode response data" }) } diff --git a/example/src/graphql/multi_query.gleam b/example/src/graphql/multi_query.gleam index 448dbce..648e3c0 100644 --- a/example/src/graphql/multi_query.gleam +++ b/example/src/graphql/multi_query.gleam @@ -1,77 +1,57 @@ -import gleam/dynamic +import gleam/dynamic/decode import gleam/http import gleam/http/request import gleam/httpc import gleam/json +import gleam/list import gleam/option.{type Option} import gleam/result +import squall pub type Characters { Characters(info: Option(Info), results: Option(List(Character))) } -pub fn characters_decoder() -> dynamic.Decoder(Characters) { - fn(data: dynamic.Dynamic) -> Result(Characters, List(dynamic.DecodeError)) { - use info <- result.try(dynamic.field( - "info", - dynamic.optional(info_decoder()), - )(data)) - use results <- result.try(dynamic.field( - "results", - dynamic.optional(dynamic.list(character_decoder())), - )(data)) - Ok(Characters(info: info, results: results)) - } +pub fn characters_decoder() -> decode.Decoder(Characters) { + use info <- decode.field("info", decode.optional(info_decoder())) + use results <- decode.field("results", decode.optional(decode.list(character_decoder()))) + decode.success(Characters(info: info, results: results)) } pub type Info { Info(count: Option(Int)) } -pub fn info_decoder() -> dynamic.Decoder(Info) { - fn(data: dynamic.Dynamic) -> Result(Info, List(dynamic.DecodeError)) { - use count <- result.try(dynamic.field( - "count", - dynamic.optional(dynamic.int), - )(data)) - Ok(Info(count: count)) - } +pub fn info_decoder() -> decode.Decoder(Info) { + use count <- decode.field("count", decode.optional(decode.int)) + decode.success(Info(count: count)) } pub type Character { Character(name: Option(String)) } -pub fn character_decoder() -> dynamic.Decoder(Character) { - fn(data: dynamic.Dynamic) -> Result(Character, List(dynamic.DecodeError)) { - use name <- result.try(dynamic.field( - "name", - dynamic.optional(dynamic.string), - )(data)) - Ok(Character(name: name)) - } +pub fn character_decoder() -> decode.Decoder(Character) { + use name <- decode.field("name", decode.optional(decode.string)) + decode.success(Character(name: name)) } pub type Location { Location(id: Option(String)) } -pub fn location_decoder() -> dynamic.Decoder(Location) { - fn(data: dynamic.Dynamic) -> Result(Location, List(dynamic.DecodeError)) { - use id <- result.try(dynamic.field("id", dynamic.optional(dynamic.string))(data)) - Ok(Location(id: id)) - } +pub fn location_decoder() -> decode.Decoder(Location) { + use id <- decode.field("id", decode.optional(decode.string)) + decode.success(Location(id: id)) } pub type Episode { Episode(id: Option(String)) } -pub fn episode_decoder() -> dynamic.Decoder(Episode) { - fn(data: dynamic.Dynamic) -> Result(Episode, List(dynamic.DecodeError)) { - use id <- result.try(dynamic.field("id", dynamic.optional(dynamic.string))(data)) - Ok(Episode(id: id)) - } +pub fn episode_decoder() -> decode.Decoder(Episode) { + use id <- decode.field("id", decode.optional(decode.string)) + decode.success(Episode(id: id)) } pub type MultiQueryResponse { @@ -82,32 +62,18 @@ pub type MultiQueryResponse { ) } -pub fn multi_query_response_decoder() -> dynamic.Decoder(MultiQueryResponse) { - fn(data: dynamic.Dynamic) -> Result( - MultiQueryResponse, - List(dynamic.DecodeError), - ) { - use characters <- result.try(dynamic.field( - "characters", - dynamic.optional(characters_decoder()), - )(data)) - use location <- result.try(dynamic.field( - "location", - dynamic.optional(location_decoder()), - )(data)) - use episodes_by_ids <- result.try(dynamic.field( - "episodesByIds", - dynamic.optional(dynamic.list(episode_decoder())), - )(data)) - Ok(MultiQueryResponse( - characters: characters, - location: location, - episodes_by_ids: episodes_by_ids, - )) - } +pub fn multi_query_response_decoder() -> decode.Decoder(MultiQueryResponse) { + use characters <- decode.field("characters", decode.optional(characters_decoder())) + use location <- decode.field("location", decode.optional(location_decoder())) + use episodes_by_ids <- decode.field("episodesByIds", decode.optional(decode.list(episode_decoder()))) + decode.success(MultiQueryResponse( + characters: characters, + location: location, + episodes_by_ids: episodes_by_ids, + )) } -pub fn multi_query(endpoint: String) -> Result(MultiQueryResponse, String) { +pub fn multi_query(client: squall.Client) -> Result(MultiQueryResponse, String) { let query = "query MultiQuery { characters(page: 2, filter: { name: \"rick\" }) { info { count } results { name } } location(id: 1) { id } episodesByIds(ids: [1, 2]) { id } }" let variables = @@ -115,7 +81,7 @@ pub fn multi_query(endpoint: String) -> Result(MultiQueryResponse, String) { let body = json.object([#("query", json.string(query)), #("variables", variables)]) use req <- result.try( - request.to(endpoint) + request.to(client.endpoint) |> result.map_error(fn(_) { "Invalid endpoint URL" }), ) let req = @@ -123,18 +89,22 @@ pub fn multi_query(endpoint: String) -> Result(MultiQueryResponse, String) { |> request.set_method(http.Post) |> request.set_body(json.to_string(body)) |> request.set_header("content-type", "application/json") + let req = + list.fold(client.headers, req, fn(r, header) { + request.set_header(r, header.0, header.1) + }) use resp <- result.try( httpc.send(req) |> result.map_error(fn(_) { "HTTP request failed" }), ) use json_value <- result.try( - json.decode(from: resp.body, using: dynamic.dynamic) + json.parse(from: resp.body, using: decode.dynamic) |> result.map_error(fn(_) { "Failed to decode JSON response" }), ) - use data_field <- result.try( - dynamic.field("data", dynamic.dynamic)(json_value) - |> result.map_error(fn(_) { "No data field in response" }), - ) - multi_query_response_decoder()(data_field) + let data_and_response_decoder = { + use data <- decode.field("data", multi_query_response_decoder()) + decode.success(data) + } + decode.run(json_value, data_and_response_decoder) |> result.map_error(fn(_) { "Failed to decode response data" }) } diff --git a/example/src/graphql/multi_query_with_vars.gleam b/example/src/graphql/multi_query_with_vars.gleam index ac8613e..2b58c72 100644 --- a/example/src/graphql/multi_query_with_vars.gleam +++ b/example/src/graphql/multi_query_with_vars.gleam @@ -1,89 +1,59 @@ -import gleam/dynamic +import gleam/dynamic/decode import gleam/http import gleam/http/request import gleam/httpc import gleam/json +import gleam/list import gleam/option.{type Option} import gleam/result +import squall pub type Characters { Characters(info: Option(Info), results: Option(List(Character))) } -pub fn characters_decoder() -> dynamic.Decoder(Characters) { - fn(data: dynamic.Dynamic) -> Result(Characters, List(dynamic.DecodeError)) { - use info <- result.try(dynamic.field( - "info", - dynamic.optional(info_decoder()), - )(data)) - use results <- result.try(dynamic.field( - "results", - dynamic.optional(dynamic.list(character_decoder())), - )(data)) - Ok(Characters(info: info, results: results)) - } +pub fn characters_decoder() -> decode.Decoder(Characters) { + use info <- decode.field("info", decode.optional(info_decoder())) + use results <- decode.field("results", decode.optional(decode.list(character_decoder()))) + decode.success(Characters(info: info, results: results)) } pub type Info { Info(count: Option(Int)) } -pub fn info_decoder() -> dynamic.Decoder(Info) { - fn(data: dynamic.Dynamic) -> Result(Info, List(dynamic.DecodeError)) { - use count <- result.try(dynamic.field( - "count", - dynamic.optional(dynamic.int), - )(data)) - Ok(Info(count: count)) - } +pub fn info_decoder() -> decode.Decoder(Info) { + use count <- decode.field("count", decode.optional(decode.int)) + decode.success(Info(count: count)) } pub type Character { Character(name: Option(String)) } -pub fn character_decoder() -> dynamic.Decoder(Character) { - fn(data: dynamic.Dynamic) -> Result(Character, List(dynamic.DecodeError)) { - use name <- result.try(dynamic.field( - "name", - dynamic.optional(dynamic.string), - )(data)) - Ok(Character(name: name)) - } +pub fn character_decoder() -> decode.Decoder(Character) { + use name <- decode.field("name", decode.optional(decode.string)) + decode.success(Character(name: name)) } pub type Location { Location(id: Option(String), name: Option(String)) } -pub fn location_decoder() -> dynamic.Decoder(Location) { - fn(data: dynamic.Dynamic) -> Result(Location, List(dynamic.DecodeError)) { - use id <- result.try(dynamic.field("id", dynamic.optional(dynamic.string))( - data, - )) - use name <- result.try(dynamic.field( - "name", - dynamic.optional(dynamic.string), - )(data)) - Ok(Location(id: id, name: name)) - } +pub fn location_decoder() -> decode.Decoder(Location) { + use id <- decode.field("id", decode.optional(decode.string)) + use name <- decode.field("name", decode.optional(decode.string)) + decode.success(Location(id: id, name: name)) } pub type Episode { Episode(id: Option(String), name: Option(String)) } -pub fn episode_decoder() -> dynamic.Decoder(Episode) { - fn(data: dynamic.Dynamic) -> Result(Episode, List(dynamic.DecodeError)) { - use id <- result.try(dynamic.field("id", dynamic.optional(dynamic.string))( - data, - )) - use name <- result.try(dynamic.field( - "name", - dynamic.optional(dynamic.string), - )(data)) - Ok(Episode(id: id, name: name)) - } +pub fn episode_decoder() -> decode.Decoder(Episode) { + use id <- decode.field("id", decode.optional(decode.string)) + use name <- decode.field("name", decode.optional(decode.string)) + decode.success(Episode(id: id, name: name)) } pub type MultiQueryWithVarsResponse { @@ -94,53 +64,33 @@ pub type MultiQueryWithVarsResponse { ) } -pub fn multi_query_with_vars_response_decoder() -> dynamic.Decoder( - MultiQueryWithVarsResponse, -) { - fn(data: dynamic.Dynamic) -> Result( - MultiQueryWithVarsResponse, - List(dynamic.DecodeError), - ) { - use characters <- result.try(dynamic.field( - "characters", - dynamic.optional(characters_decoder()), - )(data)) - use location <- result.try(dynamic.field( - "location", - dynamic.optional(location_decoder()), - )(data)) - use episodes_by_ids <- result.try(dynamic.field( - "episodesByIds", - dynamic.optional(dynamic.list(episode_decoder())), - )(data)) - Ok(MultiQueryWithVarsResponse( - characters: characters, - location: location, - episodes_by_ids: episodes_by_ids, - )) - } +pub fn multi_query_with_vars_response_decoder() -> decode.Decoder(MultiQueryWithVarsResponse) { + use characters <- decode.field("characters", decode.optional(characters_decoder())) + use location <- decode.field("location", decode.optional(location_decoder())) + use episodes_by_ids <- decode.field("episodesByIds", decode.optional(decode.list(episode_decoder()))) + decode.success(MultiQueryWithVarsResponse( + characters: characters, + location: location, + episodes_by_ids: episodes_by_ids, + )) } -pub fn multi_query_with_vars( - endpoint: String, - page: Int, - name: String, - location_id: String, - episode_ids: List(Int), -) -> Result(MultiQueryWithVarsResponse, String) { +pub fn multi_query_with_vars(client: squall.Client, page: Int, name: String, location_id: String, episode_ids: List(Int)) -> Result(MultiQueryWithVarsResponse, String) { let query = "query MultiQueryWithVars($page: Int, $name: String, $locationId: ID!, $episodeIds: [Int!]!) { characters(page: $page, filter: { name: $name }) { info { count } results { name } } location(id: $locationId) { id name } episodesByIds(ids: $episodeIds) { id name } }" let variables = - json.object([ - #("page", json.int(page)), - #("name", json.string(name)), - #("locationId", json.string(location_id)), - #("episodeIds", json.array(from: episode_ids, of: json.int)), - ]) + json.object( + [ + #("page", json.int(page)), + #("name", json.string(name)), + #("locationId", json.string(location_id)), + #("episodeIds", json.array(from: episode_ids, of: json.int)), + ], + ) let body = json.object([#("query", json.string(query)), #("variables", variables)]) use req <- result.try( - request.to(endpoint) + request.to(client.endpoint) |> result.map_error(fn(_) { "Invalid endpoint URL" }), ) let req = @@ -148,18 +98,22 @@ pub fn multi_query_with_vars( |> request.set_method(http.Post) |> request.set_body(json.to_string(body)) |> request.set_header("content-type", "application/json") + let req = + list.fold(client.headers, req, fn(r, header) { + request.set_header(r, header.0, header.1) + }) use resp <- result.try( httpc.send(req) |> result.map_error(fn(_) { "HTTP request failed" }), ) use json_value <- result.try( - json.decode(from: resp.body, using: dynamic.dynamic) + json.parse(from: resp.body, using: decode.dynamic) |> result.map_error(fn(_) { "Failed to decode JSON response" }), ) - use data_field <- result.try( - dynamic.field("data", dynamic.dynamic)(json_value) - |> result.map_error(fn(_) { "No data field in response" }), - ) - multi_query_with_vars_response_decoder()(data_field) + let data_and_response_decoder = { + use data <- decode.field("data", multi_query_with_vars_response_decoder()) + decode.success(data) + } + decode.run(json_value, data_and_response_decoder) |> result.map_error(fn(_) { "Failed to decode response data" }) } diff --git a/gleam.toml b/gleam.toml index 42c0f23..943385e 100644 --- a/gleam.toml +++ b/gleam.toml @@ -5,10 +5,10 @@ licences = ["Apache-2.0"] repository = { type = "github", user = "bigmoves", repo = "squall" } [dependencies] -gleam_stdlib = ">= 0.34.0 and < 0.40.0" -gleam_json = ">= 1.0.0 and < 2.0.0" -gleam_http = ">= 3.0.0 and < 4.0.0" -gleam_httpc = ">= 2.0.0 and < 3.0.0" +gleam_stdlib = ">= 0.65.0 and < 0.66.0" +gleam_json = ">= 3.0.0 and < 4.0.0" +gleam_http = ">= 4.3.0 and < 5.0.0" +gleam_httpc = ">= 5.0.0 and < 6.0.0" simplifile = ">= 2.0.0 and < 3.0.0" filepath = ">= 1.0.0 and < 2.0.0" glam = ">= 2.0.0 and < 3.0.0" diff --git a/manifest.toml b/manifest.toml index 8060255..0f3fcd2 100644 --- a/manifest.toml +++ b/manifest.toml @@ -3,25 +3,26 @@ packages = [ { name = "argv", version = "1.0.2", build_tools = ["gleam"], requirements = [], otp_app = "argv", source = "hex", outer_checksum = "BA1FF0929525DEBA1CE67256E5ADF77A7CDDFE729E3E3F57A5BDCAA031DED09D" }, - { name = "birdie", version = "1.2.0", build_tools = ["gleam"], requirements = ["argv", "edit_distance", "filepath", "glance", "gleam_community_ansi", "gleam_erlang", "gleam_stdlib", "justin", "rank", "simplifile", "trie_again"], otp_app = "birdie", source = "hex", outer_checksum = "7EE6286C5660650143A5AC69E1D900DA1B6EAF3C6EBB7E50AC3DBF06511279CC" }, - { name = "edit_distance", version = "2.0.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "edit_distance", source = "hex", outer_checksum = "A1E485C69A70210223E46E63985FA1008B8B2DDA9848B7897469171B29020C05" }, - { name = "filepath", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "filepath", source = "hex", outer_checksum = "EFB6FF65C98B2A16378ABC3EE2B14124168C0CE5201553DE652E2644DCFDB594" }, - { name = "glam", version = "2.0.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "glam", source = "hex", outer_checksum = "4932A2D139AB0389E149396407F89654928D7B815E212BB02F13C66F53B1BBA1" }, - { name = "glance", version = "0.11.0", build_tools = ["gleam"], requirements = ["gleam_stdlib", "glexer"], otp_app = "glance", source = "hex", outer_checksum = "8F3314D27773B7C3B9FB58D8C02C634290422CE531988C0394FA0DF8676B964D" }, + { name = "birdie", version = "1.4.1", build_tools = ["gleam"], requirements = ["argv", "edit_distance", "filepath", "glance", "gleam_community_ansi", "gleam_stdlib", "justin", "rank", "simplifile", "term_size", "trie_again"], otp_app = "birdie", source = "hex", outer_checksum = "18599E478C14BD9EBD2465F0561F96EB9B58A24DB44AF86F103EF81D4B9834BF" }, + { name = "edit_distance", version = "3.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "edit_distance", source = "hex", outer_checksum = "7DC465C34695F9E57D79FC65670C53C992CE342BF29E0AA41FF44F61AF62FC56" }, + { name = "filepath", version = "1.1.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "filepath", source = "hex", outer_checksum = "B06A9AF0BF10E51401D64B98E4B627F1D2E48C154967DA7AF4D0914780A6D40A" }, + { name = "glam", version = "2.0.3", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "glam", source = "hex", outer_checksum = "237C2CE218A2A0A5D46D625F8EF5B78F964BC91018B78D692B17E1AB84295229" }, + { name = "glance", version = "5.0.1", build_tools = ["gleam"], requirements = ["gleam_stdlib", "glexer"], otp_app = "glance", source = "hex", outer_checksum = "7F216D97935465FF4AC46699CD1C3E0FB19CB678B002E4ACAFCE256E96312F14" }, { name = "gleam_community_ansi", version = "1.4.3", build_tools = ["gleam"], requirements = ["gleam_community_colour", "gleam_regexp", "gleam_stdlib"], otp_app = "gleam_community_ansi", source = "hex", outer_checksum = "8A62AE9CC6EA65BEA630D95016D6C07E4F9973565FA3D0DE68DC4200D8E0DD27" }, - { name = "gleam_community_colour", version = "1.4.1", build_tools = ["gleam"], requirements = ["gleam_json", "gleam_stdlib"], otp_app = "gleam_community_colour", source = "hex", outer_checksum = "386CB9B01B33371538672EEA8A6375A0A0ADEF41F17C86DDCB81C92AD00DA610" }, - { name = "gleam_erlang", version = "0.33.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_erlang", source = "hex", outer_checksum = "A1D26B80F01901B59AABEE3475DD4C18D27D58FA5C897D922FCB9B099749C064" }, - { name = "gleam_http", version = "3.7.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_http", source = "hex", outer_checksum = "A9EE0722106FCCAB8AD3BF9D0A3EFF92BFE8561D59B83BAE96EB0BE1938D4E0F" }, - { name = "gleam_httpc", version = "2.3.0", build_tools = ["gleam"], requirements = ["gleam_http", "gleam_stdlib"], otp_app = "gleam_httpc", source = "hex", outer_checksum = "CF6CDD88830CC9853F7638ECC0BE7D7CD9522640DA5FAB4C08CFAC8DEBD08028" }, - { name = "gleam_json", version = "1.0.1", build_tools = ["gleam"], requirements = ["gleam_stdlib", "thoas"], otp_app = "gleam_json", source = "hex", outer_checksum = "9063D14D25406326C0255BDA0021541E797D8A7A12573D849462CAFED459F6EB" }, + { name = "gleam_community_colour", version = "2.0.2", build_tools = ["gleam"], requirements = ["gleam_json", "gleam_stdlib"], otp_app = "gleam_community_colour", source = "hex", outer_checksum = "E34DD2C896AC3792151EDA939DA435FF3B69922F33415ED3C4406C932FBE9634" }, + { name = "gleam_erlang", version = "1.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_erlang", source = "hex", outer_checksum = "1124AD3AA21143E5AF0FC5CF3D9529F6DB8CA03E43A55711B60B6B7B3874375C" }, + { name = "gleam_http", version = "4.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_http", source = "hex", outer_checksum = "82EA6A717C842456188C190AFB372665EA56CE13D8559BF3B1DD9E40F619EE0C" }, + { name = "gleam_httpc", version = "5.0.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_http", "gleam_stdlib"], otp_app = "gleam_httpc", source = "hex", outer_checksum = "C545172618D07811494E97AAA4A0FB34DA6F6D0061FDC8041C2F8E3BE2B2E48F" }, + { name = "gleam_json", version = "3.0.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_json", source = "hex", outer_checksum = "874FA3C3BB6E22DD2BB111966BD40B3759E9094E05257899A7C08F5DE77EC049" }, { name = "gleam_regexp", version = "1.1.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_regexp", source = "hex", outer_checksum = "9C215C6CA84A5B35BB934A9B61A9A306EC743153BE2B0425A0D032E477B062A9" }, - { name = "gleam_stdlib", version = "0.39.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "2D7DE885A6EA7F1D5015D1698920C9BAF7241102836CE0C3837A4F160128A9C4" }, - { name = "gleeunit", version = "1.3.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "A7DD6C07B7DA49A6E28796058AA89E651D233B357D5607006D70619CD89DAAAB" }, - { name = "glexer", version = "1.0.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "glexer", source = "hex", outer_checksum = "BD477AD657C2B637FEF75F2405FAEFFA533F277A74EF1A5E17B55B1178C228FB" }, + { name = "gleam_stdlib", version = "0.65.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "7C69C71D8C493AE11A5184828A77110EB05A7786EBF8B25B36A72F879C3EE107" }, + { name = "gleeunit", version = "1.6.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "FDC68A8C492B1E9B429249062CD9BAC9B5538C6FBF584817205D0998C42E1DAC" }, + { name = "glexer", version = "2.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib", "splitter"], otp_app = "glexer", source = "hex", outer_checksum = "40A1FB0919FA080AD6C5809B4C7DBA545841CAAC8168FACDFA0B0667C22475CC" }, { name = "justin", version = "1.0.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "justin", source = "hex", outer_checksum = "7FA0C6DB78640C6DC5FBFD59BF3456009F3F8B485BF6825E97E1EB44E9A1E2CD" }, { name = "rank", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "rank", source = "hex", outer_checksum = "5660E361F0E49CBB714CC57CC4C89C63415D8986F05B2DA0C719D5642FAD91C9" }, { name = "simplifile", version = "2.3.0", build_tools = ["gleam"], requirements = ["filepath", "gleam_stdlib"], otp_app = "simplifile", source = "hex", outer_checksum = "0A868DAC6063D9E983477981839810DC2E553285AB4588B87E3E9C96A7FB4CB4" }, - { name = "thoas", version = "1.2.1", build_tools = ["rebar3"], requirements = [], otp_app = "thoas", source = "hex", outer_checksum = "E38697EDFFD6E91BD12CEA41B155115282630075C2A727E7A6B2947F5408B86A" }, + { name = "splitter", version = "1.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "splitter", source = "hex", outer_checksum = "05564A381580395DCDEFF4F88A64B021E8DAFA6540AE99B4623962F52976AA9D" }, + { name = "term_size", version = "1.0.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "term_size", source = "hex", outer_checksum = "D00BD2BC8FB3EBB7E6AE076F3F1FF2AC9D5ED1805F004D0896C784D06C6645F1" }, { name = "trie_again", version = "1.1.4", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "trie_again", source = "hex", outer_checksum = "E3BD66B4E126EF567EA8C4944EAB216413392ADF6C16C36047AF79EE5EF13466" }, ] @@ -30,9 +31,9 @@ argv = { version = ">= 1.0.0 and < 2.0.0" } birdie = { version = ">= 1.0.0 and < 2.0.0" } filepath = { version = ">= 1.0.0 and < 2.0.0" } glam = { version = ">= 2.0.0 and < 3.0.0" } -gleam_http = { version = ">= 3.0.0 and < 4.0.0" } -gleam_httpc = { version = ">= 2.0.0 and < 3.0.0" } -gleam_json = { version = ">= 1.0.0 and < 2.0.0" } -gleam_stdlib = { version = ">= 0.34.0 and < 0.40.0" } +gleam_http = { version = ">= 4.3.0 and < 5.0.0" } +gleam_httpc = { version = ">= 5.0.0 and < 6.0.0" } +gleam_json = { version = ">= 3.0.0 and < 4.0.0" } +gleam_stdlib = { version = ">= 0.65.0 and < 0.66.0" } gleeunit = { version = ">= 1.0.0 and < 2.0.0" } simplifile = { version = ">= 2.0.0 and < 3.0.0" } diff --git a/src/squall/internal/codegen.gleam b/src/squall/internal/codegen.gleam index 790c66b..7d8a128 100644 --- a/src/squall/internal/codegen.gleam +++ b/src/squall/internal/codegen.gleam @@ -114,7 +114,7 @@ fn sanitize_field_name(name: String) -> String { /// Generate imports section fn imports_doc() -> Document { let import_lines = [ - "import gleam/dynamic", + "import gleam/dynamic/decode", "import gleam/http", "import gleam/http/request", "import gleam/httpc", @@ -377,20 +377,17 @@ fn generate_decoder_with_schema( use gleam_type <- result.try(type_mapping.graphql_to_gleam_nullable( type_ref, )) - let decoder_call = - call_doc("dynamic.field", [ + let field_decoder = + doc.concat([ + doc.from_string("use " <> sanitized_name <> " <- decode.field("), string_doc(name), + doc.from_string(", "), doc.from_string(generate_field_decoder_with_schema( gleam_type, type_ref, schema_types, )), - ]) - let field_decoder = - doc.concat([ - doc.from_string("use " <> sanitized_name <> " <- result.try("), - decoder_call, - doc.from_string("(data))"), + doc.from_string(")"), ]) Ok(field_decoder) }) @@ -403,32 +400,19 @@ fn generate_decoder_with_schema( doc.from_string(sanitized <> ": " <> sanitized) }) - // Don't use call_doc for Ok(...) to prevent breaking between Ok( and Constructor let success_line = doc.concat([ - doc.from_string("Ok("), + doc.from_string("decode.success("), call_doc(type_name, constructor_args), doc.from_string(")"), ]) - let inner_fn_body = list.append(field_decoder_docs, [success_line]) - - // Use call_doc for the return type to allow it to break when needed - let inner_fn = - doc.concat([ - doc.from_string("fn(data: dynamic.Dynamic) -> "), - call_doc("Result", [ - doc.from_string(type_name), - doc.from_string("List(dynamic.DecodeError)"), - ]), - doc.from_string(" "), - block(inner_fn_body), - ]) + let decoder_body = list.append(field_decoder_docs, [success_line]) doc.concat([ - doc.from_string("pub fn " <> decoder_name <> "() -> dynamic.Decoder("), + doc.from_string("pub fn " <> decoder_name <> "() -> decode.Decoder("), doc.from_string(type_name <> ") "), - block([inner_fn]), + block(decoder_body), ]) } @@ -439,26 +423,26 @@ fn generate_field_decoder_with_schema( schema_types: dict.Dict(String, schema.Type), ) -> String { case gleam_type { - type_mapping.StringType -> "dynamic.string" - type_mapping.IntType -> "dynamic.int" - type_mapping.FloatType -> "dynamic.float" - type_mapping.BoolType -> "dynamic.bool" + type_mapping.StringType -> "decode.string" + type_mapping.IntType -> "decode.int" + type_mapping.FloatType -> "decode.float" + type_mapping.BoolType -> "decode.bool" type_mapping.ListType(inner) -> { let inner_decoder = generate_field_decoder_with_schema_inner(inner, type_ref, schema_types) - "dynamic.list(" <> inner_decoder <> ")" + "decode.list(" <> inner_decoder <> ")" } type_mapping.OptionType(inner) -> { let inner_decoder = generate_field_decoder_with_schema_inner(inner, type_ref, schema_types) - "dynamic.optional(" <> inner_decoder <> ")" + "decode.optional(" <> inner_decoder <> ")" } type_mapping.CustomType(name) -> { // Check if this is an object type that has a decoder case dict.get(schema_types, name) { Ok(schema.ObjectType(_, _, _)) -> snake_case(name) <> "_decoder()" - _ -> "dynamic.dynamic" + _ -> "decode.dynamic" } } } @@ -476,18 +460,18 @@ fn generate_field_decoder_with_schema_inner( type_mapping.ListType(inner) -> { let inner_decoder = generate_field_decoder_with_schema_inner(inner, type_ref, schema_types) - "dynamic.list(" <> inner_decoder <> ")" + "decode.list(" <> inner_decoder <> ")" } type_mapping.OptionType(inner) -> { let inner_decoder = generate_field_decoder_with_schema_inner(inner, type_ref, schema_types) - "dynamic.optional(" <> inner_decoder <> ")" + "decode.optional(" <> inner_decoder <> ")" } type_mapping.CustomType(_) -> case dict.get(schema_types, base_type_name) { Ok(schema.ObjectType(_, _, _)) -> snake_case(base_type_name) <> "_decoder()" - _ -> "dynamic.dynamic" + _ -> "decode.dynamic" } _ -> generate_field_decoder(gleam_type) } @@ -496,15 +480,15 @@ fn generate_field_decoder_with_schema_inner( // Generate field decoder string fn generate_field_decoder(gleam_type: type_mapping.GleamType) -> String { case gleam_type { - type_mapping.StringType -> "dynamic.string" - type_mapping.IntType -> "dynamic.int" - type_mapping.FloatType -> "dynamic.float" - type_mapping.BoolType -> "dynamic.bool" + type_mapping.StringType -> "decode.string" + type_mapping.IntType -> "decode.int" + type_mapping.FloatType -> "decode.float" + type_mapping.BoolType -> "decode.bool" type_mapping.ListType(inner) -> - "dynamic.list(" <> generate_field_decoder(inner) <> ")" + "decode.list(" <> generate_field_decoder(inner) <> ")" type_mapping.OptionType(inner) -> - "dynamic.optional(" <> generate_field_decoder(inner) <> ")" - type_mapping.CustomType(_name) -> "dynamic.dynamic" + "decode.optional(" <> generate_field_decoder(inner) <> ")" + type_mapping.CustomType(_name) -> "decode.dynamic" } } @@ -526,7 +510,7 @@ fn generate_function( |> list.map(fn(var) { use gleam_type <- result.try( type_mapping.parser_type_to_schema_type(var.type_ref) - |> result.then(type_mapping.graphql_to_gleam), + |> result.try(type_mapping.graphql_to_gleam), ) let param_name = snake_case(var.name) Ok( @@ -550,7 +534,7 @@ fn generate_function( |> list.map(fn(var) { use gleam_type <- result.try( type_mapping.parser_type_to_schema_type(var.type_ref) - |> result.then(type_mapping.graphql_to_gleam), + |> result.try(type_mapping.graphql_to_gleam), ) let param_name = snake_case(var.name) let value_encoder = encode_variable_value(param_name, gleam_type) @@ -633,9 +617,9 @@ fn generate_function( doc.from_string("use json_value <- result.try("), doc.concat([ doc.line, - call_doc("json.decode", [ + call_doc("json.parse", [ doc.from_string("from: resp.body"), - doc.from_string("using: dynamic.dynamic"), + doc.from_string("using: decode.dynamic"), ]), doc.line, doc.from_string( @@ -647,23 +631,18 @@ fn generate_function( doc.from_string(")"), ]), doc.concat([ - doc.from_string("use data_field <- result.try("), + doc.from_string("let data_and_response_decoder = {"), + doc.line |> doc.nest(by: indent), doc.concat([ + doc.from_string("use data <- decode.field(\"data\", " <> snake_case(response_type_name) <> "_decoder())"), doc.line, - call_doc("dynamic.field", [ - string_doc("data"), - doc.from_string("dynamic.dynamic"), - ]), - doc.from_string("(json_value)"), - doc.line, - doc.from_string("|> result.map_error(fn(_) { \"No data field in response\" }),"), - ]) - |> doc.nest(by: indent), + doc.from_string("decode.success(data)"), + ]) |> doc.nest(by: indent), doc.line, - doc.from_string(")"), + doc.from_string("}"), ]), doc.concat([ - doc.from_string(snake_case(response_type_name) <> "_decoder()(data_field)"), + doc.from_string("decode.run(json_value, data_and_response_decoder)"), doc.line, doc.from_string("|> result.map_error(fn(_) { \"Failed to decode response data\" })"), ]), diff --git a/src/squall/internal/discovery.gleam b/src/squall/internal/discovery.gleam index 395c4c1..b556b7f 100644 --- a/src/squall/internal/discovery.gleam +++ b/src/squall/internal/discovery.gleam @@ -50,7 +50,7 @@ pub fn extract_operation_name(path: String) -> Result(String, Error) { // Remove .gql extension let name = case string.ends_with(filename, ".gql") { - True -> string.drop_right(filename, 4) + True -> string.drop_end(filename, 4) False -> filename } diff --git a/src/squall/internal/schema.gleam b/src/squall/internal/schema.gleam index d7fc349..fdd971a 100644 --- a/src/squall/internal/schema.gleam +++ b/src/squall/internal/schema.gleam @@ -1,5 +1,6 @@ import gleam/dict.{type Dict} -import gleam/dynamic.{type Dynamic, type DecodeError} +import gleam/dynamic.{type Dynamic} +import gleam/dynamic/decode import gleam/json import gleam/list import gleam/option.{type Option, None, Some} @@ -72,10 +73,44 @@ pub type Schema { ) } +// Helper functions for decoding with new API +fn field( + dyn: Dynamic, + name: String, + decoder: decode.Decoder(t), +) -> Result(t, List(decode.DecodeError)) { + let field_decoder = { + use value <- decode.field(name, decoder) + decode.success(value) + } + decode.run(dyn, field_decoder) +} + +fn optional_field( + dyn: Dynamic, + name: String, + decoder: decode.Decoder(t), +) -> Option(t) { + field(dyn, name, decoder) + |> result.map(Some) + |> result.unwrap(None) +} + +fn decode_errors_to_string(errs: List(decode.DecodeError)) -> String { + errs + |> list.map(fn(_err) { "Decode error" }) + |> list.reduce(fn(a, b) { a <> ", " <> b }) + |> result.unwrap("unknown error") +} + // Parse introspection response JSON into Schema pub fn parse_introspection_response(json_str: String) -> Result(Schema, Error) { - case json.decode(from: json_str, using: dynamic.dynamic) { + case json.parse(from: json_str, using: decode.dynamic) { Ok(dyn) -> decode_schema(dyn) + Error(json.UnableToDecode(errs)) -> + Error(error.InvalidSchemaResponse( + "Decode error: " <> decode_errors_to_string(errs), + )) Error(_) -> Error(error.InvalidSchemaResponse("Invalid JSON")) } } @@ -83,42 +118,35 @@ pub fn parse_introspection_response(json_str: String) -> Result(Schema, Error) { fn decode_schema(dyn: Dynamic) -> Result(Schema, Error) { // Extract data.__schema use data <- result.try( - dynamic.field("data", dynamic.dynamic)(dyn) + field(dyn, "data", decode.dynamic) |> result.map_error(to_schema_error), ) use schema_obj <- result.try( - dynamic.field("__schema", dynamic.dynamic)(data) + field(data, "__schema", decode.dynamic) |> result.map_error(to_schema_error), ) // Parse optional query type - let query_type = - dynamic.field("queryType", dynamic.dynamic)(schema_obj) - |> result.then(dynamic.field("name", dynamic.string)) - |> result.map(Some) - |> result.unwrap(None) + let query_type = case field(schema_obj, "queryType", decode.dynamic) { + Ok(qt) -> optional_field(qt, "name", decode.string) + Error(_) -> None + } // Parse optional mutation type - let mutation_type = - dynamic.field("mutationType", dynamic.dynamic)(schema_obj) - |> result.then(dynamic.field("name", dynamic.string)) - |> result.map(Some) - |> result.unwrap(None) + let mutation_type = case field(schema_obj, "mutationType", decode.dynamic) { + Ok(mt) -> optional_field(mt, "name", decode.string) + Error(_) -> None + } // Parse optional subscription type - let subscription_type = - dynamic.field("subscriptionType", dynamic.dynamic)(schema_obj) - |> result.then(dynamic.field("name", dynamic.string)) - |> result.map(Some) - |> result.unwrap(None) + let subscription_type = case field(schema_obj, "subscriptionType", decode.dynamic) { + Ok(st) -> optional_field(st, "name", decode.string) + Error(_) -> None + } // Parse types array - use types_dynamic <- result.try( - dynamic.field("types", dynamic.dynamic)(schema_obj) - |> result.map_error(to_schema_error), - ) use types_list_dyn <- result.try( - dynamic.list(dynamic.dynamic)(types_dynamic) + field(schema_obj, "types", decode.list(of: decode.dynamic)) |> result.map_error(to_schema_error), ) @@ -140,24 +168,21 @@ fn decode_schema(dyn: Dynamic) -> Result(Schema, Error) { fn decode_type(dyn: Dynamic) -> Result(Type, Error) { use name <- result.try( - dynamic.field("name", dynamic.string)(dyn) + field(dyn, "name", decode.string) |> result.map_error(to_schema_error), ) use kind <- result.try( - dynamic.field("kind", dynamic.string)(dyn) + field(dyn, "kind", decode.string) |> result.map_error(to_schema_error), ) - let description = - dynamic.field("description", dynamic.string)(dyn) - |> result.map(Some) - |> result.unwrap(None) + let description = optional_field(dyn, "description", decode.string) case kind { "SCALAR" -> Ok(ScalarType(name, description)) "OBJECT" | "INTERFACE" -> { - let fields = case dynamic.field("fields", dynamic.list(dynamic.dynamic))(dyn) { + let fields = case field(dyn, "fields", decode.list(of: decode.dynamic)) { Ok(fields_dyn) -> { list.try_map(fields_dyn, decode_field) |> result.unwrap([]) @@ -172,10 +197,10 @@ fn decode_type(dyn: Dynamic) -> Result(Type, Error) { } "UNION" -> { - let possible_types = case dynamic.field("possibleTypes", dynamic.list(dynamic.dynamic))(dyn) { + let possible_types = case field(dyn, "possibleTypes", decode.list(of: decode.dynamic)) { Ok(types_dyn) -> { list.try_map(types_dyn, fn(d) { - dynamic.field("name", dynamic.string)(d) + field(d, "name", decode.string) |> result.map_error(to_schema_error) }) |> result.unwrap([]) @@ -187,10 +212,10 @@ fn decode_type(dyn: Dynamic) -> Result(Type, Error) { } "ENUM" -> { - let enum_values = case dynamic.field("enumValues", dynamic.list(dynamic.dynamic))(dyn) { + let enum_values = case field(dyn, "enumValues", decode.list(of: decode.dynamic)) { Ok(values_dyn) -> { list.try_map(values_dyn, fn(d) { - dynamic.field("name", dynamic.string)(d) + field(d, "name", decode.string) |> result.map_error(to_schema_error) }) |> result.unwrap([]) @@ -202,7 +227,7 @@ fn decode_type(dyn: Dynamic) -> Result(Type, Error) { } "INPUT_OBJECT" -> { - let input_fields = case dynamic.field("inputFields", dynamic.list(dynamic.dynamic))(dyn) { + let input_fields = case field(dyn, "inputFields", decode.list(of: decode.dynamic)) { Ok(fields_dyn) -> { list.try_map(fields_dyn, decode_input_value) |> result.unwrap([]) @@ -219,16 +244,16 @@ fn decode_type(dyn: Dynamic) -> Result(Type, Error) { fn decode_field(dyn: Dynamic) -> Result(Field, Error) { use name <- result.try( - dynamic.field("name", dynamic.string)(dyn) + field(dyn, "name", decode.string) |> result.map_error(to_schema_error), ) use type_dyn <- result.try( - dynamic.field("type", dynamic.dynamic)(dyn) + field(dyn, "type", decode.dynamic) |> result.map_error(to_schema_error), ) use type_ref <- result.try(decode_type_ref(type_dyn)) - let args = case dynamic.field("args", dynamic.list(dynamic.dynamic))(dyn) { + let args = case field(dyn, "args", decode.list(of: decode.dynamic)) { Ok(args_dyn) -> { list.try_map(args_dyn, decode_input_value) |> result.unwrap([]) @@ -236,43 +261,37 @@ fn decode_field(dyn: Dynamic) -> Result(Field, Error) { Error(_) -> [] } - let description = - dynamic.field("description", dynamic.string)(dyn) - |> result.map(Some) - |> result.unwrap(None) + let description = optional_field(dyn, "description", decode.string) Ok(Field(name, type_ref, args, description)) } fn decode_input_value(dyn: Dynamic) -> Result(InputValue, Error) { use name <- result.try( - dynamic.field("name", dynamic.string)(dyn) + field(dyn, "name", decode.string) |> result.map_error(to_schema_error), ) use type_dyn <- result.try( - dynamic.field("type", dynamic.dynamic)(dyn) + field(dyn, "type", decode.dynamic) |> result.map_error(to_schema_error), ) use type_ref <- result.try(decode_type_ref(type_dyn)) - let description = - dynamic.field("description", dynamic.string)(dyn) - |> result.map(Some) - |> result.unwrap(None) + let description = optional_field(dyn, "description", decode.string) Ok(InputValue(name, type_ref, description)) } fn decode_type_ref(dyn: Dynamic) -> Result(TypeRef, Error) { use kind <- result.try( - dynamic.field("kind", dynamic.string)(dyn) + field(dyn, "kind", decode.string) |> result.map_error(to_schema_error), ) case kind { "NON_NULL" -> { use of_type_dyn <- result.try( - dynamic.field("ofType", dynamic.dynamic)(dyn) + field(dyn, "ofType", decode.dynamic) |> result.map_error(to_schema_error), ) use of_type <- result.try(decode_type_ref(of_type_dyn)) @@ -281,7 +300,7 @@ fn decode_type_ref(dyn: Dynamic) -> Result(TypeRef, Error) { "LIST" -> { use of_type_dyn <- result.try( - dynamic.field("ofType", dynamic.dynamic)(dyn) + field(dyn, "ofType", decode.dynamic) |> result.map_error(to_schema_error), ) use of_type <- result.try(decode_type_ref(of_type_dyn)) @@ -290,7 +309,7 @@ fn decode_type_ref(dyn: Dynamic) -> Result(TypeRef, Error) { _ -> { use name <- result.try( - dynamic.field("name", dynamic.string)(dyn) + field(dyn, "name", decode.string) |> result.map_error(to_schema_error), ) Ok(NamedType(name, kind_from_string(kind))) @@ -312,15 +331,8 @@ fn kind_from_string(s: String) -> TypeKind { } } -fn to_schema_error(errs: List(DecodeError)) -> Error { - error.InvalidSchemaResponse("Decode error: " <> errors_to_string(errs)) -} - -fn errors_to_string(errs: List(DecodeError)) -> String { - errs - |> list.map(fn(err) { "Expected " <> err.expected }) - |> list.reduce(fn(a, b) { a <> ", " <> b }) - |> result.unwrap("unknown error") +fn to_schema_error(errs: List(decode.DecodeError)) -> Error { + error.InvalidSchemaResponse("Decode error: " <> decode_errors_to_string(errs)) } // Helper functions for tests -- 2.51.2