Type-safe GraphQL client generator for Gleam

support multiple fields in a combined response and passed variables

Changed files
+1976 -161
birdie_snapshots
example
build
dev
erlang
example
squall
lsp
packages
src
src
squall
test
+7 -4
birdie_snapshots/mutation_function_generation.accepted
··· 1 1 --- 2 2 version: 1.2.0 3 3 title: Mutation function generation 4 + file: ./test/codegen_test.gleam 5 + test_name: generate_mutation_test 4 6 --- 5 7 import gleam/dynamic 6 8 import gleam/http ··· 23 25 } 24 26 25 27 pub type CreateUserResponse { 26 - CreateUserResponse(createUser: Option(User)) 28 + CreateUserResponse(create_user: Option(User)) 27 29 } 28 30 29 31 pub fn create_user_response_decoder() -> dynamic.Decoder(CreateUserResponse) { 30 32 fn(data: dynamic.Dynamic) -> Result(CreateUserResponse, List(dynamic.DecodeError)) { 31 - use createUser <- result.try(dynamic.field( 33 + use create_user <- result.try(dynamic.field( 32 34 "createUser", 33 35 dynamic.optional(user_decoder()), 34 36 )(data)) 35 - Ok(CreateUserResponse(createUser: createUser)) 37 + Ok(CreateUserResponse(create_user: create_user)) 36 38 } 37 39 } 38 40 ··· 40 42 CreateUserResponse, 41 43 String, 42 44 ) { 43 - let query = "mutation CreateUser($name: String!) { createUser { id name } }" 45 + let query = 46 + "mutation CreateUser($name: String!) { createUser(name: $name) { id name } }" 44 47 let variables = json.object([#("name", json.string(name))]) 45 48 let body = 46 49 json.object([#("query", json.string(query)), #("variables", variables)])
+68
birdie_snapshots/query_with_inline_array_arguments.accepted
··· 1 + --- 2 + version: 1.2.0 3 + title: Query with inline array arguments 4 + file: ./test/codegen_test.gleam 5 + test_name: generate_inline_array_arguments_test 6 + --- 7 + import gleam/dynamic 8 + import gleam/http 9 + import gleam/http/request 10 + import gleam/httpc 11 + import gleam/json 12 + import gleam/option.{type Option} 13 + import gleam/result 14 + 15 + pub type Episode { 16 + Episode(id: String, name: String) 17 + } 18 + 19 + pub fn episode_decoder() -> dynamic.Decoder(Episode) { 20 + fn(data: dynamic.Dynamic) -> Result(Episode, List(dynamic.DecodeError)) { 21 + use id <- result.try(dynamic.field("id", dynamic.string)(data)) 22 + use name <- result.try(dynamic.field("name", dynamic.string)(data)) 23 + Ok(Episode(id: id, name: name)) 24 + } 25 + } 26 + 27 + pub type GetEpisodesResponse { 28 + GetEpisodesResponse(episodes_by_ids: Option(List(Episode))) 29 + } 30 + 31 + pub fn get_episodes_response_decoder() -> dynamic.Decoder(GetEpisodesResponse) { 32 + fn(data: dynamic.Dynamic) -> Result(GetEpisodesResponse, List(dynamic.DecodeError)) { 33 + use episodes_by_ids <- result.try(dynamic.field( 34 + "episodesByIds", 35 + dynamic.optional(dynamic.list(episode_decoder())), 36 + )(data)) 37 + Ok(GetEpisodesResponse(episodes_by_ids: episodes_by_ids)) 38 + } 39 + } 40 + 41 + pub fn get_episodes(endpoint: String) -> Result(GetEpisodesResponse, String) { 42 + let query = "query GetEpisodes { episodesByIds(ids: [1, 2, 3]) { id name } }" 43 + let variables = json.object([]) 44 + let body = 45 + json.object([#("query", json.string(query)), #("variables", variables)]) 46 + use req <- result.try( 47 + request.to(endpoint) 48 + |> result.map_error(fn(_) { "Invalid endpoint URL" }), 49 + ) 50 + let req = req 51 + |> request.set_method(http.Post) 52 + |> request.set_body(json.to_string(body)) 53 + |> request.set_header("content-type", "application/json") 54 + use resp <- result.try( 55 + httpc.send(req) 56 + |> result.map_error(fn(_) { "HTTP request failed" }), 57 + ) 58 + use json_value <- result.try( 59 + json.decode(from: resp.body, using: dynamic.dynamic) 60 + |> result.map_error(fn(_) { "Failed to decode JSON response" }), 61 + ) 62 + use data_field <- result.try( 63 + dynamic.field("data", dynamic.dynamic)(json_value) 64 + |> result.map_error(fn(_) { "No data field in response" }), 65 + ) 66 + get_episodes_response_decoder()(data_field) 67 + |> result.map_error(fn(_) { "Failed to decode response data" }) 68 + }
+81
birdie_snapshots/query_with_inline_object_arguments.accepted
··· 1 + --- 2 + version: 1.2.0 3 + title: Query with inline object arguments 4 + --- 5 + import gleam/dynamic 6 + import gleam/http 7 + import gleam/http/request 8 + import gleam/httpc 9 + import gleam/json 10 + import gleam/option.{type Option} 11 + import gleam/result 12 + 13 + pub type CharactersResult { 14 + CharactersResult(results: Option(List(Character))) 15 + } 16 + 17 + pub fn characters_result_decoder() -> dynamic.Decoder(CharactersResult) { 18 + fn(data: dynamic.Dynamic) -> Result(CharactersResult, List(dynamic.DecodeError)) { 19 + use results <- result.try(dynamic.field( 20 + "results", 21 + dynamic.optional(dynamic.list(character_decoder())), 22 + )(data)) 23 + Ok(CharactersResult(results: results)) 24 + } 25 + } 26 + 27 + pub type Character { 28 + Character(id: String, name: String) 29 + } 30 + 31 + pub fn character_decoder() -> dynamic.Decoder(Character) { 32 + fn(data: dynamic.Dynamic) -> Result(Character, List(dynamic.DecodeError)) { 33 + use id <- result.try(dynamic.field("id", dynamic.string)(data)) 34 + use name <- result.try(dynamic.field("name", dynamic.string)(data)) 35 + Ok(Character(id: id, name: name)) 36 + } 37 + } 38 + 39 + pub type GetCharactersResponse { 40 + GetCharactersResponse(characters: Option(CharactersResult)) 41 + } 42 + 43 + pub fn get_characters_response_decoder() -> dynamic.Decoder(GetCharactersResponse) { 44 + fn(data: dynamic.Dynamic) -> Result(GetCharactersResponse, List(dynamic.DecodeError)) { 45 + use characters <- result.try(dynamic.field( 46 + "characters", 47 + dynamic.optional(characters_result_decoder()), 48 + )(data)) 49 + Ok(GetCharactersResponse(characters: characters)) 50 + } 51 + } 52 + 53 + pub fn get_characters(endpoint: String) -> Result(GetCharactersResponse, String) { 54 + let query = 55 + "query GetCharacters { characters(filter: { name: \"rick\", status: \"alive\" }) { results { id name } } }" 56 + let variables = json.object([]) 57 + let body = 58 + json.object([#("query", json.string(query)), #("variables", variables)]) 59 + use req <- result.try( 60 + request.to(endpoint) 61 + |> result.map_error(fn(_) { "Invalid endpoint URL" }), 62 + ) 63 + let req = req 64 + |> request.set_method(http.Post) 65 + |> request.set_body(json.to_string(body)) 66 + |> request.set_header("content-type", "application/json") 67 + use resp <- result.try( 68 + httpc.send(req) 69 + |> result.map_error(fn(_) { "HTTP request failed" }), 70 + ) 71 + use json_value <- result.try( 72 + json.decode(from: resp.body, using: dynamic.dynamic) 73 + |> result.map_error(fn(_) { "Failed to decode JSON response" }), 74 + ) 75 + use data_field <- result.try( 76 + dynamic.field("data", dynamic.dynamic)(json_value) 77 + |> result.map_error(fn(_) { "No data field in response" }), 78 + ) 79 + get_characters_response_decoder()(data_field) 80 + |> result.map_error(fn(_) { "Failed to decode response data" }) 81 + }
+66
birdie_snapshots/query_with_inline_scalar_arguments.accepted
··· 1 + --- 2 + version: 1.2.0 3 + title: Query with inline scalar arguments 4 + --- 5 + import gleam/dynamic 6 + import gleam/http 7 + import gleam/http/request 8 + import gleam/httpc 9 + import gleam/json 10 + import gleam/option.{type Option} 11 + import gleam/result 12 + 13 + pub type Character { 14 + Character(id: String, name: String) 15 + } 16 + 17 + pub fn character_decoder() -> dynamic.Decoder(Character) { 18 + fn(data: dynamic.Dynamic) -> Result(Character, List(dynamic.DecodeError)) { 19 + use id <- result.try(dynamic.field("id", dynamic.string)(data)) 20 + use name <- result.try(dynamic.field("name", dynamic.string)(data)) 21 + Ok(Character(id: id, name: name)) 22 + } 23 + } 24 + 25 + pub type GetCharacterResponse { 26 + GetCharacterResponse(character: Option(Character)) 27 + } 28 + 29 + pub fn get_character_response_decoder() -> dynamic.Decoder(GetCharacterResponse) { 30 + fn(data: dynamic.Dynamic) -> Result(GetCharacterResponse, List(dynamic.DecodeError)) { 31 + use character <- result.try(dynamic.field( 32 + "character", 33 + dynamic.optional(character_decoder()), 34 + )(data)) 35 + Ok(GetCharacterResponse(character: character)) 36 + } 37 + } 38 + 39 + pub fn get_character(endpoint: String) -> Result(GetCharacterResponse, String) { 40 + let query = "query GetCharacter { character(id: 1) { id name } }" 41 + let variables = json.object([]) 42 + let body = 43 + json.object([#("query", json.string(query)), #("variables", variables)]) 44 + use req <- result.try( 45 + request.to(endpoint) 46 + |> result.map_error(fn(_) { "Invalid endpoint URL" }), 47 + ) 48 + let req = req 49 + |> request.set_method(http.Post) 50 + |> request.set_body(json.to_string(body)) 51 + |> request.set_header("content-type", "application/json") 52 + use resp <- result.try( 53 + httpc.send(req) 54 + |> result.map_error(fn(_) { "HTTP request failed" }), 55 + ) 56 + use json_value <- result.try( 57 + json.decode(from: resp.body, using: dynamic.dynamic) 58 + |> result.map_error(fn(_) { "Failed to decode JSON response" }), 59 + ) 60 + use data_field <- result.try( 61 + dynamic.field("data", dynamic.dynamic)(json_value) 62 + |> result.map_error(fn(_) { "No data field in response" }), 63 + ) 64 + get_character_response_decoder()(data_field) 65 + |> result.map_error(fn(_) { "Failed to decode response data" }) 66 + }
+143
birdie_snapshots/query_with_multiple_root_fields_and_mixed_arguments.accepted
··· 1 + --- 2 + version: 1.2.0 3 + title: Query with multiple root fields and mixed arguments 4 + file: ./test/codegen_test.gleam 5 + test_name: generate_multiple_root_fields_test 6 + --- 7 + import gleam/dynamic 8 + import gleam/http 9 + import gleam/http/request 10 + import gleam/httpc 11 + import gleam/json 12 + import gleam/option.{type Option} 13 + import gleam/result 14 + 15 + pub type CharactersResult { 16 + CharactersResult(info: Option(Info), results: Option(List(Character))) 17 + } 18 + 19 + pub fn characters_result_decoder() -> dynamic.Decoder(CharactersResult) { 20 + fn(data: dynamic.Dynamic) -> Result(CharactersResult, List(dynamic.DecodeError)) { 21 + use info <- result.try(dynamic.field( 22 + "info", 23 + dynamic.optional(info_decoder()), 24 + )(data)) 25 + use results <- result.try(dynamic.field( 26 + "results", 27 + dynamic.optional(dynamic.list(character_decoder())), 28 + )(data)) 29 + Ok(CharactersResult(info: info, results: results)) 30 + } 31 + } 32 + 33 + pub type Info { 34 + Info(count: Option(Int)) 35 + } 36 + 37 + pub fn info_decoder() -> dynamic.Decoder(Info) { 38 + fn(data: dynamic.Dynamic) -> Result(Info, List(dynamic.DecodeError)) { 39 + use count <- result.try(dynamic.field( 40 + "count", 41 + dynamic.optional(dynamic.int), 42 + )(data)) 43 + Ok(Info(count: count)) 44 + } 45 + } 46 + 47 + pub type Character { 48 + Character(name: Option(String)) 49 + } 50 + 51 + pub fn character_decoder() -> dynamic.Decoder(Character) { 52 + fn(data: dynamic.Dynamic) -> Result(Character, List(dynamic.DecodeError)) { 53 + use name <- result.try(dynamic.field( 54 + "name", 55 + dynamic.optional(dynamic.string), 56 + )(data)) 57 + Ok(Character(name: name)) 58 + } 59 + } 60 + 61 + pub type Location { 62 + Location(id: Option(String)) 63 + } 64 + 65 + pub fn location_decoder() -> dynamic.Decoder(Location) { 66 + fn(data: dynamic.Dynamic) -> Result(Location, List(dynamic.DecodeError)) { 67 + use id <- result.try(dynamic.field("id", dynamic.optional(dynamic.string))(data)) 68 + Ok(Location(id: id)) 69 + } 70 + } 71 + 72 + pub type Episode { 73 + Episode(id: Option(String)) 74 + } 75 + 76 + pub fn episode_decoder() -> dynamic.Decoder(Episode) { 77 + fn(data: dynamic.Dynamic) -> Result(Episode, List(dynamic.DecodeError)) { 78 + use id <- result.try(dynamic.field("id", dynamic.optional(dynamic.string))(data)) 79 + Ok(Episode(id: id)) 80 + } 81 + } 82 + 83 + pub type MultiQueryResponse { 84 + MultiQueryResponse( 85 + characters: Option(CharactersResult), 86 + location: Option(Location), 87 + episodes_by_ids: Option(List(Episode)), 88 + ) 89 + } 90 + 91 + pub fn multi_query_response_decoder() -> dynamic.Decoder(MultiQueryResponse) { 92 + fn(data: dynamic.Dynamic) -> Result(MultiQueryResponse, List(dynamic.DecodeError)) { 93 + use characters <- result.try(dynamic.field( 94 + "characters", 95 + dynamic.optional(characters_result_decoder()), 96 + )(data)) 97 + use location <- result.try(dynamic.field( 98 + "location", 99 + dynamic.optional(location_decoder()), 100 + )(data)) 101 + use episodes_by_ids <- result.try(dynamic.field( 102 + "episodesByIds", 103 + dynamic.optional(dynamic.list(episode_decoder())), 104 + )(data)) 105 + Ok( 106 + MultiQueryResponse( 107 + characters: characters, 108 + location: location, 109 + episodes_by_ids: episodes_by_ids, 110 + ), 111 + ) 112 + } 113 + } 114 + 115 + pub fn multi_query(endpoint: String) -> Result(MultiQueryResponse, String) { 116 + let query = 117 + "query MultiQuery { characters(page: 2, filter: { name: \"rick\" }) { info { count } results { name } } location(id: 1) { id } episodesByIds(ids: [1, 2]) { id } }" 118 + let variables = json.object([]) 119 + let body = 120 + json.object([#("query", json.string(query)), #("variables", variables)]) 121 + use req <- result.try( 122 + request.to(endpoint) 123 + |> result.map_error(fn(_) { "Invalid endpoint URL" }), 124 + ) 125 + let req = req 126 + |> request.set_method(http.Post) 127 + |> request.set_body(json.to_string(body)) 128 + |> request.set_header("content-type", "application/json") 129 + use resp <- result.try( 130 + httpc.send(req) 131 + |> result.map_error(fn(_) { "HTTP request failed" }), 132 + ) 133 + use json_value <- result.try( 134 + json.decode(from: resp.body, using: dynamic.dynamic) 135 + |> result.map_error(fn(_) { "Failed to decode JSON response" }), 136 + ) 137 + use data_field <- result.try( 138 + dynamic.field("data", dynamic.dynamic)(json_value) 139 + |> result.map_error(fn(_) { "No data field in response" }), 140 + ) 141 + multi_query_response_decoder()(data_field) 142 + |> result.map_error(fn(_) { "Failed to decode response data" }) 143 + }
+2 -1
birdie_snapshots/query_with_nested_types_generation.accepted
··· 41 41 GetCharacterResponse, 42 42 String, 43 43 ) { 44 - let query = "query GetCharacter($id: ID!) { character { id name status } }" 44 + let query = 45 + "query GetCharacter($id: ID!) { character(id: $id) { id name status } }" 45 46 let variables = json.object([#("id", json.string(id))]) 46 47 let body = 47 48 json.object([#("query", json.string(query)), #("variables", variables)])
+1 -1
birdie_snapshots/query_with_variables_function_generation.accepted
··· 40 40 } 41 41 42 42 pub fn get_user(endpoint: String, id: String) -> Result(GetUserResponse, String) { 43 - let query = "query GetUser($id: ID!) { user { id name } }" 43 + let query = "query GetUser($id: ID!) { user(id: $id) { id name } }" 44 44 let variables = json.object([#("id", json.string(id))]) 45 45 let body = 46 46 json.object([#("query", json.string(query)), #("variables", variables)])
example/build/dev/erlang/example/_gleam_artefacts/example.cache

This is a binary file and will not be displayed.

example/build/dev/erlang/example/_gleam_artefacts/example.cache_meta

This is a binary file and will not be displayed.

+6 -3
example/build/dev/erlang/example/_gleam_artefacts/example.erl
··· 6 6 -file("src/example.gleam", 4). 7 7 -spec main() -> nil. 8 8 main() -> 9 - Result = graphql@get_characters:get_characters( 9 + gleam@io:println(<<"Squall Multi-Field Query Example"/utf8>>), 10 + gleam@io:println(<<"=================================\n"/utf8>>), 11 + Result = graphql@multi_query:multi_query( 10 12 <<"https://rickandmortyapi.com/graphql"/utf8>> 11 13 ), 12 14 case Result of 13 15 {ok, Response} -> 14 - gleam@io:println(<<"Successfully fetched characters!"/utf8>>), 16 + gleam@io:println(<<"Response:"/utf8>>), 15 17 gleam@io:debug(Response), 16 18 nil; 17 19 18 20 {error, Err} -> 19 - gleam@io:println(<<"Error: "/utf8, Err/binary>>) 21 + gleam@io:println(<<"Error: "/utf8, Err/binary>>), 22 + nil 20 23 end.
example/build/dev/erlang/example/_gleam_artefacts/example_with_vars.cache

This is a binary file and will not be displayed.

example/build/dev/erlang/example/_gleam_artefacts/example_with_vars.cache_inline

This is a binary file and will not be displayed.

example/build/dev/erlang/example/_gleam_artefacts/example_with_vars.cache_meta

This is a binary file and will not be displayed.

example/build/dev/erlang/example/_gleam_artefacts/example_with_vars.cache_warnings

This is a binary file and will not be displayed.

+36
example/build/dev/erlang/example/_gleam_artefacts/example_with_vars.erl
··· 1 + -module(example_with_vars). 2 + -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). 3 + -define(FILEPATH, "src/example_with_vars.gleam"). 4 + -export([main/0]). 5 + 6 + -file("src/example_with_vars.gleam", 4). 7 + -spec main() -> nil. 8 + main() -> 9 + gleam@io:println( 10 + <<"Squall Multi-Field Query Example (with Variables)"/utf8>> 11 + ), 12 + gleam@io:println( 13 + <<"==================================================\n"/utf8>> 14 + ), 15 + gleam@io:println(<<"Calling multi_query_with_vars with:"/utf8>>), 16 + gleam@io:println(<<" page: 2"/utf8>>), 17 + gleam@io:println(<<" name: \"rick\""/utf8>>), 18 + gleam@io:println(<<" locationId: \"1\""/utf8>>), 19 + gleam@io:println(<<" episodeIds: [1, 2]\n"/utf8>>), 20 + Result = graphql@multi_query_with_vars:multi_query_with_vars( 21 + <<"https://rickandmortyapi.com/graphql"/utf8>>, 22 + 2, 23 + <<"rick"/utf8>>, 24 + <<"1"/utf8>>, 25 + [1, 2] 26 + ), 27 + case Result of 28 + {ok, Response} -> 29 + gleam@io:println(<<"Response:"/utf8>>), 30 + gleam@io:debug(Response), 31 + nil; 32 + 33 + {error, Err} -> 34 + gleam@io:println(<<"Error: "/utf8, Err/binary>>), 35 + nil 36 + end.
example/build/dev/erlang/example/_gleam_artefacts/graphql@get_character.cache

This is a binary file and will not be displayed.

example/build/dev/erlang/example/_gleam_artefacts/graphql@get_character.cache_meta

This is a binary file and will not be displayed.

+2 -2
example/build/dev/erlang/example/_gleam_artefacts/graphql@get_character.erl
··· 101 101 ) 102 102 end. 103 103 104 - -file("src/graphql/get_character.gleam", 73). 104 + -file("src/graphql/get_character.gleam", 70). 105 105 -spec get_character(binary(), binary()) -> {ok, get_character_response()} | 106 106 {error, binary()}. 107 107 get_character(Endpoint, Id) -> 108 - Query = <<"query GetCharacter($id: ID!) { character { id name status species type gender } }"/utf8>>, 108 + Query = <<"query GetCharacter($id: ID!) { character(id: $id) { id name status species type gender } }"/utf8>>, 109 109 Variables = gleam@json:object([{<<"id"/utf8>>, gleam@json:string(Id)}]), 110 110 Body = gleam@json:object( 111 111 [{<<"query"/utf8>>, gleam@json:string(Query)},
example/build/dev/erlang/example/_gleam_artefacts/graphql@get_characters.cache

This is a binary file and will not be displayed.

example/build/dev/erlang/example/_gleam_artefacts/graphql@get_characters.cache_meta

This is a binary file and will not be displayed.

+2 -2
example/build/dev/erlang/example/_gleam_artefacts/graphql@get_characters.erl
··· 79 79 ) 80 80 end. 81 81 82 - -file("src/graphql/get_characters.gleam", 57). 82 + -file("src/graphql/get_characters.gleam", 55). 83 83 -spec get_characters_response_decoder() -> fun((gleam@dynamic:dynamic_()) -> {ok, 84 84 get_characters_response()} | 85 85 {error, list(gleam@dynamic:decode_error())}). ··· 94 94 ) 95 95 end. 96 96 97 - -file("src/graphql/get_characters.gleam", 72). 97 + -file("src/graphql/get_characters.gleam", 65). 98 98 -spec get_characters(binary()) -> {ok, get_characters_response()} | 99 99 {error, binary()}. 100 100 get_characters(Endpoint) ->
example/build/dev/erlang/example/_gleam_artefacts/graphql@multi_query.cache

This is a binary file and will not be displayed.

example/build/dev/erlang/example/_gleam_artefacts/graphql@multi_query.cache_inline

This is a binary file and will not be displayed.

example/build/dev/erlang/example/_gleam_artefacts/graphql@multi_query.cache_meta

This is a binary file and will not be displayed.

example/build/dev/erlang/example/_gleam_artefacts/graphql@multi_query.cache_warnings

This is a binary file and will not be displayed.

+228
example/build/dev/erlang/example/_gleam_artefacts/graphql@multi_query.erl
··· 1 + -module(graphql@multi_query). 2 + -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). 3 + -define(FILEPATH, "src/graphql/multi_query.gleam"). 4 + -export([info_decoder/0, character_decoder/0, characters_decoder/0, location_decoder/0, episode_decoder/0, multi_query_response_decoder/0, multi_query/1]). 5 + -export_type([characters/0, info/0, character/0, location/0, episode/0, multi_query_response/0]). 6 + 7 + -type characters() :: {characters, 8 + gleam@option:option(info()), 9 + gleam@option:option(list(character()))}. 10 + 11 + -type info() :: {info, gleam@option:option(integer())}. 12 + 13 + -type character() :: {character, gleam@option:option(binary())}. 14 + 15 + -type location() :: {location, gleam@option:option(binary())}. 16 + 17 + -type episode() :: {episode, gleam@option:option(binary())}. 18 + 19 + -type multi_query_response() :: {multi_query_response, 20 + gleam@option:option(characters()), 21 + gleam@option:option(location()), 22 + gleam@option:option(list(episode()))}. 23 + 24 + -file("src/graphql/multi_query.gleam", 31). 25 + -spec info_decoder() -> fun((gleam@dynamic:dynamic_()) -> {ok, info()} | 26 + {error, list(gleam@dynamic:decode_error())}). 27 + info_decoder() -> 28 + fun(Data) -> 29 + gleam@result:'try'( 30 + (gleam@dynamic:field( 31 + <<"count"/utf8>>, 32 + gleam@dynamic:optional(fun gleam@dynamic:int/1) 33 + ))(Data), 34 + fun(Count) -> {ok, {info, Count}} end 35 + ) 36 + end. 37 + 38 + -file("src/graphql/multi_query.gleam", 45). 39 + -spec character_decoder() -> fun((gleam@dynamic:dynamic_()) -> {ok, character()} | 40 + {error, list(gleam@dynamic:decode_error())}). 41 + character_decoder() -> 42 + fun(Data) -> 43 + gleam@result:'try'( 44 + (gleam@dynamic:field( 45 + <<"name"/utf8>>, 46 + gleam@dynamic:optional(fun gleam@dynamic:string/1) 47 + ))(Data), 48 + fun(Name) -> {ok, {character, Name}} end 49 + ) 50 + end. 51 + 52 + -file("src/graphql/multi_query.gleam", 13). 53 + -spec characters_decoder() -> fun((gleam@dynamic:dynamic_()) -> {ok, 54 + characters()} | 55 + {error, list(gleam@dynamic:decode_error())}). 56 + characters_decoder() -> 57 + fun(Data) -> 58 + gleam@result:'try'( 59 + (gleam@dynamic:field( 60 + <<"info"/utf8>>, 61 + gleam@dynamic:optional(info_decoder()) 62 + ))(Data), 63 + fun(Info) -> 64 + gleam@result:'try'( 65 + (gleam@dynamic:field( 66 + <<"results"/utf8>>, 67 + gleam@dynamic:optional( 68 + gleam@dynamic:list(character_decoder()) 69 + ) 70 + ))(Data), 71 + fun(Results) -> {ok, {characters, Info, Results}} end 72 + ) 73 + end 74 + ) 75 + end. 76 + 77 + -file("src/graphql/multi_query.gleam", 59). 78 + -spec location_decoder() -> fun((gleam@dynamic:dynamic_()) -> {ok, location()} | 79 + {error, list(gleam@dynamic:decode_error())}). 80 + location_decoder() -> 81 + fun(Data) -> 82 + gleam@result:'try'( 83 + (gleam@dynamic:field( 84 + <<"id"/utf8>>, 85 + gleam@dynamic:optional(fun gleam@dynamic:string/1) 86 + ))(Data), 87 + fun(Id) -> {ok, {location, Id}} end 88 + ) 89 + end. 90 + 91 + -file("src/graphql/multi_query.gleam", 70). 92 + -spec episode_decoder() -> fun((gleam@dynamic:dynamic_()) -> {ok, episode()} | 93 + {error, list(gleam@dynamic:decode_error())}). 94 + episode_decoder() -> 95 + fun(Data) -> 96 + gleam@result:'try'( 97 + (gleam@dynamic:field( 98 + <<"id"/utf8>>, 99 + gleam@dynamic:optional(fun gleam@dynamic:string/1) 100 + ))(Data), 101 + fun(Id) -> {ok, {episode, Id}} end 102 + ) 103 + end. 104 + 105 + -file("src/graphql/multi_query.gleam", 85). 106 + -spec multi_query_response_decoder() -> fun((gleam@dynamic:dynamic_()) -> {ok, 107 + multi_query_response()} | 108 + {error, list(gleam@dynamic:decode_error())}). 109 + multi_query_response_decoder() -> 110 + fun(Data) -> 111 + gleam@result:'try'( 112 + (gleam@dynamic:field( 113 + <<"characters"/utf8>>, 114 + gleam@dynamic:optional(characters_decoder()) 115 + ))(Data), 116 + fun(Characters) -> 117 + gleam@result:'try'( 118 + (gleam@dynamic:field( 119 + <<"location"/utf8>>, 120 + gleam@dynamic:optional(location_decoder()) 121 + ))(Data), 122 + fun(Location) -> 123 + gleam@result:'try'( 124 + (gleam@dynamic:field( 125 + <<"episodesByIds"/utf8>>, 126 + gleam@dynamic:optional( 127 + gleam@dynamic:list(episode_decoder()) 128 + ) 129 + ))(Data), 130 + fun(Episodes_by_ids) -> 131 + {ok, 132 + {multi_query_response, 133 + Characters, 134 + Location, 135 + Episodes_by_ids}} 136 + end 137 + ) 138 + end 139 + ) 140 + end 141 + ) 142 + end. 143 + 144 + -file("src/graphql/multi_query.gleam", 109). 145 + -spec multi_query(binary()) -> {ok, multi_query_response()} | {error, binary()}. 146 + multi_query(Endpoint) -> 147 + Query = <<"query MultiQuery { characters(page: 2, filter: { name: \"rick\" }) { info { count } results { name } } location(id: 1) { id } episodesByIds(ids: [1, 2]) { id } }"/utf8>>, 148 + Variables = gleam@json:object([]), 149 + Body = gleam@json:object( 150 + [{<<"query"/utf8>>, gleam@json:string(Query)}, 151 + {<<"variables"/utf8>>, Variables}] 152 + ), 153 + gleam@result:'try'( 154 + begin 155 + _pipe = gleam@http@request:to(Endpoint), 156 + gleam@result:map_error( 157 + _pipe, 158 + fun(_) -> <<"Invalid endpoint URL"/utf8>> end 159 + ) 160 + end, 161 + fun(Req) -> 162 + Req@1 = begin 163 + _pipe@1 = Req, 164 + _pipe@2 = gleam@http@request:set_method(_pipe@1, post), 165 + _pipe@3 = gleam@http@request:set_body( 166 + _pipe@2, 167 + gleam@json:to_string(Body) 168 + ), 169 + gleam@http@request:set_header( 170 + _pipe@3, 171 + <<"content-type"/utf8>>, 172 + <<"application/json"/utf8>> 173 + ) 174 + end, 175 + gleam@result:'try'( 176 + begin 177 + _pipe@4 = gleam@httpc:send(Req@1), 178 + gleam@result:map_error( 179 + _pipe@4, 180 + fun(_) -> <<"HTTP request failed"/utf8>> end 181 + ) 182 + end, 183 + fun(Resp) -> 184 + gleam@result:'try'( 185 + begin 186 + _pipe@5 = gleam@json:decode( 187 + erlang:element(4, Resp), 188 + fun gleam@dynamic:dynamic/1 189 + ), 190 + gleam@result:map_error( 191 + _pipe@5, 192 + fun(_) -> 193 + <<"Failed to decode JSON response"/utf8>> 194 + end 195 + ) 196 + end, 197 + fun(Json_value) -> 198 + gleam@result:'try'( 199 + begin 200 + _pipe@6 = (gleam@dynamic:field( 201 + <<"data"/utf8>>, 202 + fun gleam@dynamic:dynamic/1 203 + ))(Json_value), 204 + gleam@result:map_error( 205 + _pipe@6, 206 + fun(_) -> 207 + <<"No data field in response"/utf8>> 208 + end 209 + ) 210 + end, 211 + fun(Data_field) -> 212 + _pipe@7 = (multi_query_response_decoder())( 213 + Data_field 214 + ), 215 + gleam@result:map_error( 216 + _pipe@7, 217 + fun(_) -> 218 + <<"Failed to decode response data"/utf8>> 219 + end 220 + ) 221 + end 222 + ) 223 + end 224 + ) 225 + end 226 + ) 227 + end 228 + ).
example/build/dev/erlang/example/_gleam_artefacts/graphql@multi_query_with_vars.cache

This is a binary file and will not be displayed.

example/build/dev/erlang/example/_gleam_artefacts/graphql@multi_query_with_vars.cache_inline

This is a binary file and will not be displayed.

example/build/dev/erlang/example/_gleam_artefacts/graphql@multi_query_with_vars.cache_meta

This is a binary file and will not be displayed.

example/build/dev/erlang/example/_gleam_artefacts/graphql@multi_query_with_vars.cache_warnings

This is a binary file and will not be displayed.

+260
example/build/dev/erlang/example/_gleam_artefacts/graphql@multi_query_with_vars.erl
··· 1 + -module(graphql@multi_query_with_vars). 2 + -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). 3 + -define(FILEPATH, "src/graphql/multi_query_with_vars.gleam"). 4 + -export([info_decoder/0, character_decoder/0, characters_decoder/0, location_decoder/0, episode_decoder/0, multi_query_with_vars_response_decoder/0, multi_query_with_vars/5]). 5 + -export_type([characters/0, info/0, character/0, location/0, episode/0, multi_query_with_vars_response/0]). 6 + 7 + -type characters() :: {characters, 8 + gleam@option:option(info()), 9 + gleam@option:option(list(character()))}. 10 + 11 + -type info() :: {info, gleam@option:option(integer())}. 12 + 13 + -type character() :: {character, gleam@option:option(binary())}. 14 + 15 + -type location() :: {location, 16 + gleam@option:option(binary()), 17 + gleam@option:option(binary())}. 18 + 19 + -type episode() :: {episode, 20 + gleam@option:option(binary()), 21 + gleam@option:option(binary())}. 22 + 23 + -type multi_query_with_vars_response() :: {multi_query_with_vars_response, 24 + gleam@option:option(characters()), 25 + gleam@option:option(location()), 26 + gleam@option:option(list(episode()))}. 27 + 28 + -file("src/graphql/multi_query_with_vars.gleam", 31). 29 + -spec info_decoder() -> fun((gleam@dynamic:dynamic_()) -> {ok, info()} | 30 + {error, list(gleam@dynamic:decode_error())}). 31 + info_decoder() -> 32 + fun(Data) -> 33 + gleam@result:'try'( 34 + (gleam@dynamic:field( 35 + <<"count"/utf8>>, 36 + gleam@dynamic:optional(fun gleam@dynamic:int/1) 37 + ))(Data), 38 + fun(Count) -> {ok, {info, Count}} end 39 + ) 40 + end. 41 + 42 + -file("src/graphql/multi_query_with_vars.gleam", 45). 43 + -spec character_decoder() -> fun((gleam@dynamic:dynamic_()) -> {ok, character()} | 44 + {error, list(gleam@dynamic:decode_error())}). 45 + character_decoder() -> 46 + fun(Data) -> 47 + gleam@result:'try'( 48 + (gleam@dynamic:field( 49 + <<"name"/utf8>>, 50 + gleam@dynamic:optional(fun gleam@dynamic:string/1) 51 + ))(Data), 52 + fun(Name) -> {ok, {character, Name}} end 53 + ) 54 + end. 55 + 56 + -file("src/graphql/multi_query_with_vars.gleam", 13). 57 + -spec characters_decoder() -> fun((gleam@dynamic:dynamic_()) -> {ok, 58 + characters()} | 59 + {error, list(gleam@dynamic:decode_error())}). 60 + characters_decoder() -> 61 + fun(Data) -> 62 + gleam@result:'try'( 63 + (gleam@dynamic:field( 64 + <<"info"/utf8>>, 65 + gleam@dynamic:optional(info_decoder()) 66 + ))(Data), 67 + fun(Info) -> 68 + gleam@result:'try'( 69 + (gleam@dynamic:field( 70 + <<"results"/utf8>>, 71 + gleam@dynamic:optional( 72 + gleam@dynamic:list(character_decoder()) 73 + ) 74 + ))(Data), 75 + fun(Results) -> {ok, {characters, Info, Results}} end 76 + ) 77 + end 78 + ) 79 + end. 80 + 81 + -file("src/graphql/multi_query_with_vars.gleam", 59). 82 + -spec location_decoder() -> fun((gleam@dynamic:dynamic_()) -> {ok, location()} | 83 + {error, list(gleam@dynamic:decode_error())}). 84 + location_decoder() -> 85 + fun(Data) -> 86 + gleam@result:'try'( 87 + (gleam@dynamic:field( 88 + <<"id"/utf8>>, 89 + gleam@dynamic:optional(fun gleam@dynamic:string/1) 90 + ))(Data), 91 + fun(Id) -> 92 + gleam@result:'try'( 93 + (gleam@dynamic:field( 94 + <<"name"/utf8>>, 95 + gleam@dynamic:optional(fun gleam@dynamic:string/1) 96 + ))(Data), 97 + fun(Name) -> {ok, {location, Id, Name}} end 98 + ) 99 + end 100 + ) 101 + end. 102 + 103 + -file("src/graphql/multi_query_with_vars.gleam", 76). 104 + -spec episode_decoder() -> fun((gleam@dynamic:dynamic_()) -> {ok, episode()} | 105 + {error, list(gleam@dynamic:decode_error())}). 106 + episode_decoder() -> 107 + fun(Data) -> 108 + gleam@result:'try'( 109 + (gleam@dynamic:field( 110 + <<"id"/utf8>>, 111 + gleam@dynamic:optional(fun gleam@dynamic:string/1) 112 + ))(Data), 113 + fun(Id) -> 114 + gleam@result:'try'( 115 + (gleam@dynamic:field( 116 + <<"name"/utf8>>, 117 + gleam@dynamic:optional(fun gleam@dynamic:string/1) 118 + ))(Data), 119 + fun(Name) -> {ok, {episode, Id, Name}} end 120 + ) 121 + end 122 + ) 123 + end. 124 + 125 + -file("src/graphql/multi_query_with_vars.gleam", 97). 126 + -spec multi_query_with_vars_response_decoder() -> fun((gleam@dynamic:dynamic_()) -> {ok, 127 + multi_query_with_vars_response()} | 128 + {error, list(gleam@dynamic:decode_error())}). 129 + multi_query_with_vars_response_decoder() -> 130 + fun(Data) -> 131 + gleam@result:'try'( 132 + (gleam@dynamic:field( 133 + <<"characters"/utf8>>, 134 + gleam@dynamic:optional(characters_decoder()) 135 + ))(Data), 136 + fun(Characters) -> 137 + gleam@result:'try'( 138 + (gleam@dynamic:field( 139 + <<"location"/utf8>>, 140 + gleam@dynamic:optional(location_decoder()) 141 + ))(Data), 142 + fun(Location) -> 143 + gleam@result:'try'( 144 + (gleam@dynamic:field( 145 + <<"episodesByIds"/utf8>>, 146 + gleam@dynamic:optional( 147 + gleam@dynamic:list(episode_decoder()) 148 + ) 149 + ))(Data), 150 + fun(Episodes_by_ids) -> 151 + {ok, 152 + {multi_query_with_vars_response, 153 + Characters, 154 + Location, 155 + Episodes_by_ids}} 156 + end 157 + ) 158 + end 159 + ) 160 + end 161 + ) 162 + end. 163 + 164 + -file("src/graphql/multi_query_with_vars.gleam", 124). 165 + -spec multi_query_with_vars( 166 + binary(), 167 + integer(), 168 + binary(), 169 + binary(), 170 + list(integer()) 171 + ) -> {ok, multi_query_with_vars_response()} | {error, binary()}. 172 + multi_query_with_vars(Endpoint, Page, Name, Location_id, Episode_ids) -> 173 + 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 } }"/utf8>>, 174 + Variables = gleam@json:object( 175 + [{<<"page"/utf8>>, gleam@json:int(Page)}, 176 + {<<"name"/utf8>>, gleam@json:string(Name)}, 177 + {<<"locationId"/utf8>>, gleam@json:string(Location_id)}, 178 + {<<"episodeIds"/utf8>>, 179 + gleam@json:array(Episode_ids, fun gleam@json:int/1)}] 180 + ), 181 + Body = gleam@json:object( 182 + [{<<"query"/utf8>>, gleam@json:string(Query)}, 183 + {<<"variables"/utf8>>, Variables}] 184 + ), 185 + gleam@result:'try'( 186 + begin 187 + _pipe = gleam@http@request:to(Endpoint), 188 + gleam@result:map_error( 189 + _pipe, 190 + fun(_) -> <<"Invalid endpoint URL"/utf8>> end 191 + ) 192 + end, 193 + fun(Req) -> 194 + Req@1 = begin 195 + _pipe@1 = Req, 196 + _pipe@2 = gleam@http@request:set_method(_pipe@1, post), 197 + _pipe@3 = gleam@http@request:set_body( 198 + _pipe@2, 199 + gleam@json:to_string(Body) 200 + ), 201 + gleam@http@request:set_header( 202 + _pipe@3, 203 + <<"content-type"/utf8>>, 204 + <<"application/json"/utf8>> 205 + ) 206 + end, 207 + gleam@result:'try'( 208 + begin 209 + _pipe@4 = gleam@httpc:send(Req@1), 210 + gleam@result:map_error( 211 + _pipe@4, 212 + fun(_) -> <<"HTTP request failed"/utf8>> end 213 + ) 214 + end, 215 + fun(Resp) -> 216 + gleam@result:'try'( 217 + begin 218 + _pipe@5 = gleam@json:decode( 219 + erlang:element(4, Resp), 220 + fun gleam@dynamic:dynamic/1 221 + ), 222 + gleam@result:map_error( 223 + _pipe@5, 224 + fun(_) -> 225 + <<"Failed to decode JSON response"/utf8>> 226 + end 227 + ) 228 + end, 229 + fun(Json_value) -> 230 + gleam@result:'try'( 231 + begin 232 + _pipe@6 = (gleam@dynamic:field( 233 + <<"data"/utf8>>, 234 + fun gleam@dynamic:dynamic/1 235 + ))(Json_value), 236 + gleam@result:map_error( 237 + _pipe@6, 238 + fun(_) -> 239 + <<"No data field in response"/utf8>> 240 + end 241 + ) 242 + end, 243 + fun(Data_field) -> 244 + _pipe@7 = (multi_query_with_vars_response_decoder( 245 + 246 + ))(Data_field), 247 + gleam@result:map_error( 248 + _pipe@7, 249 + fun(_) -> 250 + <<"Failed to decode response data"/utf8>> 251 + end 252 + ) 253 + end 254 + ) 255 + end 256 + ) 257 + end 258 + ) 259 + end 260 + ).
+2 -3
example/build/dev/erlang/example/ebin/example.app
··· 6 6 gleam_stdlib, 7 7 squall]}, 8 8 {description, "Example project using Squall with Rick and Morty API"}, 9 - {modules, [example, 10 - graphql@get_character, 11 - graphql@get_characters]}, 9 + {modules, [example_with_vars, 10 + graphql@multi_query_with_vars]}, 12 11 {registered, []} 13 12 ]}.
+1
example/build/dev/erlang/example/include/graphql@multi_query_Character.hrl
··· 1 + -record(character, {name :: gleam@option:option(binary())}).
+4
example/build/dev/erlang/example/include/graphql@multi_query_Characters.hrl
··· 1 + -record(characters, { 2 + info :: gleam@option:option(graphql@multi_query:info()), 3 + results :: gleam@option:option(list(graphql@multi_query:character())) 4 + }).
+1
example/build/dev/erlang/example/include/graphql@multi_query_Episode.hrl
··· 1 + -record(episode, {id :: gleam@option:option(binary())}).
+1
example/build/dev/erlang/example/include/graphql@multi_query_Info.hrl
··· 1 + -record(info, {count :: gleam@option:option(integer())}).
+1
example/build/dev/erlang/example/include/graphql@multi_query_Location.hrl
··· 1 + -record(location, {id :: gleam@option:option(binary())}).
+5
example/build/dev/erlang/example/include/graphql@multi_query_MultiQueryResponse.hrl
··· 1 + -record(multi_query_response, { 2 + characters :: gleam@option:option(graphql@multi_query:characters()), 3 + location :: gleam@option:option(graphql@multi_query:location()), 4 + episodes_by_ids :: gleam@option:option(list(graphql@multi_query:episode())) 5 + }).
+1
example/build/dev/erlang/example/include/graphql@multi_query_with_vars_Character.hrl
··· 1 + -record(character, {name :: gleam@option:option(binary())}).
+4
example/build/dev/erlang/example/include/graphql@multi_query_with_vars_Characters.hrl
··· 1 + -record(characters, { 2 + info :: gleam@option:option(graphql@multi_query_with_vars:info()), 3 + results :: gleam@option:option(list(graphql@multi_query_with_vars:character())) 4 + }).
+4
example/build/dev/erlang/example/include/graphql@multi_query_with_vars_Episode.hrl
··· 1 + -record(episode, { 2 + id :: gleam@option:option(binary()), 3 + name :: gleam@option:option(binary()) 4 + }).
+1
example/build/dev/erlang/example/include/graphql@multi_query_with_vars_Info.hrl
··· 1 + -record(info, {count :: gleam@option:option(integer())}).
+4
example/build/dev/erlang/example/include/graphql@multi_query_with_vars_Location.hrl
··· 1 + -record(location, { 2 + id :: gleam@option:option(binary()), 3 + name :: gleam@option:option(binary()) 4 + }).
+5
example/build/dev/erlang/example/include/graphql@multi_query_with_vars_MultiQueryWithVarsResponse.hrl
··· 1 + -record(multi_query_with_vars_response, { 2 + characters :: gleam@option:option(graphql@multi_query_with_vars:characters()), 3 + location :: gleam@option:option(graphql@multi_query_with_vars:location()), 4 + episodes_by_ids :: gleam@option:option(list(graphql@multi_query_with_vars:episode())) 5 + }).
example/build/dev/erlang/squall/_gleam_artefacts/squall.cache

This is a binary file and will not be displayed.

example/build/dev/erlang/squall/_gleam_artefacts/squall.cache_meta

This is a binary file and will not be displayed.

example/build/dev/erlang/squall/_gleam_artefacts/squall@internal@codegen.cache

This is a binary file and will not be displayed.

example/build/dev/erlang/squall/_gleam_artefacts/squall@internal@codegen.cache_meta

This is a binary file and will not be displayed.

+167 -71
example/build/dev/erlang/squall/_gleam_artefacts/squall@internal@codegen.erl
··· 24 24 list({binary(), squall@internal@schema:type_ref()}), 25 25 gleam@dict:dict(binary(), squall@internal@schema:type())}. 26 26 27 - -file("src/squall/internal/codegen.gleam", 83). 27 + -file("src/squall/internal/codegen.gleam", 85). 28 28 ?DOC(false). 29 29 -spec let_var(binary(), glam@doc:document()) -> glam@doc:document(). 30 30 let_var(Name, Body) -> ··· 36 36 _pipe@1 = glam@doc:concat(_pipe), 37 37 glam@doc:group(_pipe@1). 38 38 39 - -file("src/squall/internal/codegen.gleam", 90). 39 + -file("src/squall/internal/codegen.gleam", 92). 40 40 ?DOC(false). 41 41 -spec string_doc(binary()) -> glam@doc:document(). 42 42 string_doc(Content) -> ··· 52 52 glam@doc:from_string(<<"\""/utf8>>)], 53 53 glam@doc:concat(_pipe@4). 54 54 55 - -file("src/squall/internal/codegen.gleam", 111). 55 + -file("src/squall/internal/codegen.gleam", 114). 56 56 ?DOC(false). 57 57 -spec imports_doc() -> glam@doc:document(). 58 58 imports_doc() -> ··· 67 67 _pipe@1 = gleam@list:map(_pipe, fun glam@doc:from_string/1), 68 68 glam@doc:join(_pipe@1, {line, 1}). 69 69 70 - -file("src/squall/internal/codegen.gleam", 221). 70 + -file("src/squall/internal/codegen.gleam", 224). 71 71 ?DOC(false). 72 72 -spec collect_field_types( 73 73 list(squall@internal@parser:selection()), ··· 104 104 ) 105 105 end end). 106 106 107 - -file("src/squall/internal/codegen.gleam", 313). 107 + -file("src/squall/internal/codegen.gleam", 316). 108 108 ?DOC(false). 109 109 -spec get_base_type_name(squall@internal@schema:type_ref()) -> binary(). 110 110 get_base_type_name(Type_ref) -> ··· 119 119 get_base_type_name(Inner@1) 120 120 end. 121 121 122 - -file("src/squall/internal/codegen.gleam", 249). 122 + -file("src/squall/internal/codegen.gleam", 252). 123 123 ?DOC(false). 124 124 -spec collect_nested_types( 125 125 list(squall@internal@parser:selection()), ··· 207 207 end end), 208 208 gleam@result:map(_pipe@4, fun gleam@list:flatten/1). 209 209 210 - -file("src/squall/internal/codegen.gleam", 480). 210 + -file("src/squall/internal/codegen.gleam", 483). 211 211 ?DOC(false). 212 212 -spec generate_field_decoder(squall@internal@type_mapping:gleam_type()) -> binary(). 213 213 generate_field_decoder(Gleam_type) -> ··· 237 237 <<"dynamic.dynamic"/utf8>> 238 238 end. 239 239 240 - -file("src/squall/internal/codegen.gleam", 718). 240 + -file("src/squall/internal/codegen.gleam", 750). 241 + ?DOC(false). 242 + -spec type_ref_to_string(squall@internal@parser:type_ref()) -> binary(). 243 + type_ref_to_string(Type_ref) -> 244 + case Type_ref of 245 + {named_type_ref, Name} -> 246 + Name; 247 + 248 + {list_type_ref, Inner} -> 249 + <<<<"["/utf8, (type_ref_to_string(Inner))/binary>>/binary, 250 + "]"/utf8>>; 251 + 252 + {non_null_type_ref, Inner@1} -> 253 + <<(type_ref_to_string(Inner@1))/binary, "!"/utf8>> 254 + end. 255 + 256 + -file("src/squall/internal/codegen.gleam", 760). 257 + ?DOC(false). 258 + -spec capitalize(binary()) -> binary(). 259 + capitalize(S) -> 260 + case gleam@string:pop_grapheme(S) of 261 + {ok, {First, Rest}} -> 262 + <<(gleam@string:uppercase(First))/binary, Rest/binary>>; 263 + 264 + {error, _} -> 265 + S 266 + end. 267 + 268 + -file("src/squall/internal/codegen.gleam", 767). 269 + ?DOC(false). 270 + -spec to_pascal_case(binary()) -> binary(). 271 + to_pascal_case(S) -> 272 + _pipe = S, 273 + _pipe@1 = gleam@string:split(_pipe, <<"_"/utf8>>), 274 + _pipe@2 = gleam@list:map(_pipe@1, fun capitalize/1), 275 + gleam@string:join(_pipe@2, <<""/utf8>>). 276 + 277 + -file("src/squall/internal/codegen.gleam", 774). 278 + ?DOC(false). 279 + -spec format_value(squall@internal@parser:value()) -> binary(). 280 + format_value(Value) -> 281 + case Value of 282 + {int_value, I} -> 283 + gleam@int:to_string(I); 284 + 285 + {float_value, F} -> 286 + gleam@float:to_string(F); 287 + 288 + {string_value, S} -> 289 + <<<<"\""/utf8, S/binary>>/binary, "\""/utf8>>; 290 + 291 + {boolean_value, true} -> 292 + <<"true"/utf8>>; 293 + 294 + {boolean_value, false} -> 295 + <<"false"/utf8>>; 296 + 297 + null_value -> 298 + <<"null"/utf8>>; 299 + 300 + {variable_value, Name} -> 301 + <<"$"/utf8, Name/binary>>; 302 + 303 + {list_value, Values} -> 304 + Formatted_values = begin 305 + _pipe = Values, 306 + _pipe@1 = gleam@list:map(_pipe, fun format_value/1), 307 + gleam@string:join(_pipe@1, <<", "/utf8>>) 308 + end, 309 + <<<<"["/utf8, Formatted_values/binary>>/binary, "]"/utf8>>; 310 + 311 + {object_value, Fields} -> 312 + Formatted_fields = begin 313 + _pipe@2 = Fields, 314 + _pipe@3 = gleam@list:map( 315 + _pipe@2, 316 + fun(Field) -> 317 + {Name@1, Value@1} = Field, 318 + <<<<Name@1/binary, ": "/utf8>>/binary, 319 + (format_value(Value@1))/binary>> 320 + end 321 + ), 322 + gleam@string:join(_pipe@3, <<", "/utf8>>) 323 + end, 324 + <<<<"{ "/utf8, Formatted_fields/binary>>/binary, " }"/utf8>> 325 + end. 326 + 327 + -file("src/squall/internal/codegen.gleam", 803). 328 + ?DOC(false). 329 + -spec format_arguments(list(squall@internal@parser:argument())) -> binary(). 330 + format_arguments(Arguments) -> 331 + case Arguments of 332 + [] -> 333 + <<""/utf8>>; 334 + 335 + Args -> 336 + Formatted_args = begin 337 + _pipe = Args, 338 + _pipe@1 = gleam@list:map( 339 + _pipe, 340 + fun(Arg) -> 341 + {argument, Name, Value} = Arg, 342 + <<<<Name/binary, ": "/utf8>>/binary, 343 + (format_value(Value))/binary>> 344 + end 345 + ), 346 + gleam@string:join(_pipe@1, <<", "/utf8>>) 347 + end, 348 + <<<<"("/utf8, Formatted_args/binary>>/binary, ")"/utf8>> 349 + end. 350 + 351 + -file("src/squall/internal/codegen.gleam", 731). 241 352 ?DOC(false). 242 353 -spec build_selection_set(list(squall@internal@parser:selection())) -> binary(). 243 354 build_selection_set(Selections) -> 244 355 Fields = begin 245 356 _pipe = Selections, 246 357 _pipe@1 = gleam@list:map(_pipe, fun(Selection) -> case Selection of 247 - {field_selection, Name, _, _, Nested} -> 358 + {field_selection, Name, _, Args, Nested} -> 359 + Args_str = format_arguments(Args), 248 360 case Nested of 249 361 [] -> 250 - Name; 362 + <<Name/binary, Args_str/binary>>; 251 363 252 364 Subs -> 253 - <<<<Name/binary, " "/utf8>>/binary, 365 + <<<<<<Name/binary, Args_str/binary>>/binary, 366 + " "/utf8>>/binary, 254 367 (build_selection_set(Subs))/binary>> 255 368 end 256 369 end end), ··· 258 371 end, 259 372 <<<<"{ "/utf8, Fields/binary>>/binary, " }"/utf8>>. 260 373 261 - -file("src/squall/internal/codegen.gleam", 736). 262 - ?DOC(false). 263 - -spec type_ref_to_string(squall@internal@parser:type_ref()) -> binary(). 264 - type_ref_to_string(Type_ref) -> 265 - case Type_ref of 266 - {named_type_ref, Name} -> 267 - Name; 268 - 269 - {list_type_ref, Inner} -> 270 - <<<<"["/utf8, (type_ref_to_string(Inner))/binary>>/binary, 271 - "]"/utf8>>; 272 - 273 - {non_null_type_ref, Inner@1} -> 274 - <<(type_ref_to_string(Inner@1))/binary, "!"/utf8>> 275 - end. 276 - 277 - -file("src/squall/internal/codegen.gleam", 686). 374 + -file("src/squall/internal/codegen.gleam", 699). 278 375 ?DOC(false). 279 376 -spec build_query_string(squall@internal@parser:operation()) -> binary(). 280 377 build_query_string(Operation) -> ··· 321 418 " "/utf8>>/binary, 322 419 Selection_set/binary>>. 323 420 324 - -file("src/squall/internal/codegen.gleam", 746). 325 - ?DOC(false). 326 - -spec capitalize(binary()) -> binary(). 327 - capitalize(S) -> 328 - case gleam@string:pop_grapheme(S) of 329 - {ok, {First, Rest}} -> 330 - <<(gleam@string:uppercase(First))/binary, Rest/binary>>; 331 - 332 - {error, _} -> 333 - S 334 - end. 335 - 336 - -file("src/squall/internal/codegen.gleam", 753). 337 - ?DOC(false). 338 - -spec to_pascal_case(binary()) -> binary(). 339 - to_pascal_case(S) -> 340 - _pipe = S, 341 - _pipe@1 = gleam@string:split(_pipe, <<"_"/utf8>>), 342 - _pipe@2 = gleam@list:map(_pipe@1, fun capitalize/1), 343 - gleam@string:join(_pipe@2, <<""/utf8>>). 344 - 345 - -file("src/squall/internal/codegen.gleam", 760). 421 + -file("src/squall/internal/codegen.gleam", 819). 346 422 ?DOC(false). 347 423 -spec snake_case(binary()) -> binary(). 348 424 snake_case(S) -> ··· 371 447 end 372 448 ). 373 449 374 - -file("src/squall/internal/codegen.gleam", 451). 450 + -file("src/squall/internal/codegen.gleam", 454). 375 451 ?DOC(false). 376 452 -spec generate_field_decoder_with_schema_inner( 377 453 squall@internal@type_mapping:gleam_type(), ··· 411 487 generate_field_decoder(Gleam_type) 412 488 end. 413 489 414 - -file("src/squall/internal/codegen.gleam", 419). 490 + -file("src/squall/internal/codegen.gleam", 422). 415 491 ?DOC(false). 416 492 -spec generate_field_decoder_with_schema( 417 493 squall@internal@type_mapping:gleam_type(), ··· 459 535 end 460 536 end. 461 537 462 - -file("src/squall/internal/codegen.gleam", 49). 538 + -file("src/squall/internal/codegen.gleam", 51). 463 539 ?DOC(false). 464 540 -spec comma_list(binary(), list(glam@doc:document()), binary()) -> glam@doc:document(). 465 541 comma_list(Open, Content, Close) -> ··· 483 559 glam@doc:concat(_pipe@2) 484 560 end. 485 561 486 - -file("src/squall/internal/codegen.gleam", 42). 562 + -file("src/squall/internal/codegen.gleam", 44). 487 563 ?DOC(false). 488 564 -spec call_doc(binary(), list(glam@doc:document())) -> glam@doc:document(). 489 565 call_doc(Function, Args) -> ··· 495 571 _pipe@2 = glam@doc:concat(_pipe@1), 496 572 glam@doc:group(_pipe@2). 497 573 498 - -file("src/squall/internal/codegen.gleam", 69). 574 + -file("src/squall/internal/codegen.gleam", 71). 499 575 ?DOC(false). 500 576 -spec block(list(glam@doc:document())) -> glam@doc:document(). 501 577 block(Body) -> ··· 513 589 glam@doc:from_string(<<"}"/utf8>>)], 514 590 glam@doc:concat(_pipe@3). 515 591 516 - -file("src/squall/internal/codegen.gleam", 660). 592 + -file("src/squall/internal/codegen.gleam", 665). 517 593 ?DOC(false). 518 594 -spec encode_variable_value(binary(), squall@internal@type_mapping:gleam_type()) -> glam@doc:document(). 519 595 encode_variable_value(Var_name, Gleam_type) -> ··· 530 606 bool_type -> 531 607 call_doc(<<"json.bool"/utf8>>, [glam@doc:from_string(Var_name)]); 532 608 533 - {list_type, _} -> 609 + {list_type, Inner} -> 610 + Encoder = case Inner of 611 + string_type -> 612 + <<"json.string"/utf8>>; 613 + 614 + int_type -> 615 + <<"json.int"/utf8>>; 616 + 617 + float_type -> 618 + <<"json.float"/utf8>>; 619 + 620 + bool_type -> 621 + <<"json.bool"/utf8>>; 622 + 623 + _ -> 624 + <<"json.string"/utf8>> 625 + end, 534 626 call_doc( 535 627 <<"json.array"/utf8>>, 536 628 [glam@doc:from_string(<<"from: "/utf8, Var_name/binary>>), 537 - glam@doc:from_string(<<"of: json.string"/utf8>>)] 629 + glam@doc:from_string(<<"of: "/utf8, Encoder/binary>>)] 538 630 ); 539 631 540 - {option_type, Inner} -> 632 + {option_type, Inner@1} -> 541 633 call_doc( 542 634 <<"json.nullable"/utf8>>, 543 635 [glam@doc:from_string(Var_name), 544 - encode_variable_value(<<"value"/utf8>>, Inner)] 636 + encode_variable_value(<<"value"/utf8>>, Inner@1)] 545 637 ); 546 638 547 639 {custom_type, _} -> 548 640 call_doc(<<"json.string"/utf8>>, [glam@doc:from_string(Var_name)]) 549 641 end. 550 642 551 - -file("src/squall/internal/codegen.gleam", 495). 643 + -file("src/squall/internal/codegen.gleam", 498). 552 644 ?DOC(false). 553 645 -spec generate_function( 554 646 binary(), ··· 579 671 ) 580 672 end, 581 673 fun(Gleam_type) -> 674 + Param_name = snake_case(erlang:element(2, Var)), 582 675 {ok, 583 676 glam@doc:from_string( 584 - <<<<(erlang:element(2, Var))/binary, 585 - ": "/utf8>>/binary, 677 + <<<<Param_name/binary, ": "/utf8>>/binary, 586 678 (squall@internal@type_mapping:to_gleam_type_string( 587 679 Gleam_type 588 680 ))/binary>> ··· 619 711 ) 620 712 end, 621 713 fun(Gleam_type@1) -> 714 + Param_name@1 = snake_case( 715 + erlang:element(2, Var@1) 716 + ), 622 717 Value_encoder = encode_variable_value( 623 - erlang:element(2, Var@1), 718 + Param_name@1, 624 719 Gleam_type@1 625 720 ), 626 721 {ok, ··· 787 882 block(Body_docs)] 788 883 ). 789 884 790 - -file("src/squall/internal/codegen.gleam", 103). 885 + -file("src/squall/internal/codegen.gleam", 105). 791 886 ?DOC(false). 792 887 -spec sanitize_field_name(binary()) -> binary(). 793 888 sanitize_field_name(Name) -> 889 + Snake_cased = snake_case(Name), 794 890 case gleam@list:contains( 795 891 [<<"as"/utf8>>, 796 892 <<"assert"/utf8>>, ··· 807 903 <<"try"/utf8>>, 808 904 <<"type"/utf8>>, 809 905 <<"use"/utf8>>], 810 - Name 906 + Snake_cased 811 907 ) of 812 908 true -> 813 - <<Name/binary, "_"/utf8>>; 909 + <<Snake_cased/binary, "_"/utf8>>; 814 910 815 911 false -> 816 - Name 912 + Snake_cased 817 913 end. 818 914 819 - -file("src/squall/internal/codegen.gleam", 322). 915 + -file("src/squall/internal/codegen.gleam", 325). 820 916 ?DOC(false). 821 917 -spec generate_type_definition( 822 918 binary(), ··· 865 961 _pipe@5 = glam@doc:concat(_pipe@4), 866 962 glam@doc:group(_pipe@5). 867 963 868 - -file("src/squall/internal/codegen.gleam", 359). 964 + -file("src/squall/internal/codegen.gleam", 362). 869 965 ?DOC(false). 870 966 -spec generate_decoder_with_schema( 871 967 binary(), ··· 945 1041 block([Inner_fn])] 946 1042 ). 947 1043 948 - -file("src/squall/internal/codegen.gleam", 130). 1044 + -file("src/squall/internal/codegen.gleam", 133). 949 1045 ?DOC(false). 950 1046 -spec generate_operation( 951 1047 binary(),
example/build/dev/erlang/squall/_gleam_artefacts/squall@internal@parser.cache

This is a binary file and will not be displayed.

example/build/dev/erlang/squall/_gleam_artefacts/squall@internal@parser.cache_meta

This is a binary file and will not be displayed.

+14 -8
example/build/dev/erlang/squall/_gleam_artefacts/squall@internal@parser.erl
··· 675 675 _pipe = tokenize_helper(Source, 1, 1, []), 676 676 gleam@result:map(_pipe, fun lists:reverse/1). 677 677 678 - -file("src/squall/internal/parser.gleam", 709). 678 + -file("src/squall/internal/parser.gleam", 711). 679 679 ?DOC(false). 680 680 -spec peek_token(parser_state()) -> {ok, token_position()} | 681 681 {error, squall@internal@error:error()}. ··· 692 692 <<"Unexpected end of input"/utf8>>}} 693 693 end. 694 694 695 - -file("src/squall/internal/parser.gleam", 716). 695 + -file("src/squall/internal/parser.gleam", 718). 696 696 ?DOC(false). 697 697 -spec advance(parser_state()) -> parser_state(). 698 698 advance(State) -> ··· 738 738 {none, State} 739 739 end. 740 740 741 - -file("src/squall/internal/parser.gleam", 693). 741 + -file("src/squall/internal/parser.gleam", 695). 742 742 ?DOC(false). 743 743 -spec parse_name(parser_state()) -> {ok, {binary(), parser_state()}} | 744 744 {error, squall@internal@error:error()}. ··· 758 758 end end 759 759 ). 760 760 761 - -file("src/squall/internal/parser.gleam", 738). 761 + -file("src/squall/internal/parser.gleam", 740). 762 762 ?DOC(false). 763 763 -spec tokens_equal(token(), token()) -> boolean(). 764 764 tokens_equal(A, B) -> ··· 806 806 false 807 807 end. 808 808 809 - -file("src/squall/internal/parser.gleam", 757). 809 + -file("src/squall/internal/parser.gleam", 759). 810 810 ?DOC(false). 811 811 -spec token_to_string(token()) -> binary(). 812 812 token_to_string(Token) -> ··· 863 863 <<"end of input"/utf8>> 864 864 end. 865 865 866 - -file("src/squall/internal/parser.gleam", 720). 866 + -file("src/squall/internal/parser.gleam", 722). 867 867 ?DOC(false). 868 868 -spec expect_token(parser_state(), token(), binary()) -> {ok, parser_state()} | 869 869 {error, squall@internal@error:error()}. ··· 1021 1021 {ok, {[], State}} 1022 1022 end. 1023 1023 1024 - -file("src/squall/internal/parser.gleam", 779). 1024 + -file("src/squall/internal/parser.gleam", 781). 1025 1025 ?DOC(false). 1026 1026 -spec skip_insignificant(parser_state()) -> parser_state(). 1027 1027 skip_insignificant(State) -> ··· 1173 1173 fun(Token_pos) -> case erlang:element(2, Token_pos) of 1174 1174 right_bracket -> 1175 1175 {ok, {{list_value, lists:reverse(Acc)}, advance(State)}}; 1176 + 1177 + comma -> 1178 + parse_list_value(advance(State), Acc); 1176 1179 1177 1180 _ -> 1178 1181 gleam@result:'try'( ··· 1310 1313 {ok, {[], State}} 1311 1314 end. 1312 1315 1313 - -file("src/squall/internal/parser.gleam", 670). 1316 + -file("src/squall/internal/parser.gleam", 671). 1314 1317 ?DOC(false). 1315 1318 -spec parse_object_value(parser_state(), list({binary(), value()})) -> {ok, 1316 1319 {value(), parser_state()}} | ··· 1321 1324 fun(Token_pos) -> case erlang:element(2, Token_pos) of 1322 1325 right_brace -> 1323 1326 {ok, {{object_value, lists:reverse(Acc)}, advance(State)}}; 1327 + 1328 + comma -> 1329 + parse_object_value(advance(State), Acc); 1324 1330 1325 1331 {name, _} -> 1326 1332 gleam@result:'try'(
example/build/dev/erlang/squall/_gleam_artefacts/squall@internal@type_mapping.cache

This is a binary file and will not be displayed.

+1 -1
example/build/dev/erlang/squall/ebin/squall.app
··· 1 1 {application, squall, [ 2 - {vsn, "1.0.0"}, 2 + {vsn, "0.1.0"}, 3 3 {applications, [argv, 4 4 filepath, 5 5 glam,
example/build/lsp/erlang/example/_gleam_artefacts/example.cache

This is a binary file and will not be displayed.

example/build/lsp/erlang/example/_gleam_artefacts/example.cache_meta

This is a binary file and will not be displayed.

example/build/lsp/erlang/example/_gleam_artefacts/example_with_vars.cache

This is a binary file and will not be displayed.

example/build/lsp/erlang/example/_gleam_artefacts/example_with_vars.cache_inline

This is a binary file and will not be displayed.

example/build/lsp/erlang/example/_gleam_artefacts/example_with_vars.cache_meta

This is a binary file and will not be displayed.

example/build/lsp/erlang/example/_gleam_artefacts/example_with_vars.cache_warnings

This is a binary file and will not be displayed.

example/build/lsp/erlang/example/_gleam_artefacts/graphql@get_character.cache

This is a binary file and will not be displayed.

example/build/lsp/erlang/example/_gleam_artefacts/graphql@get_character.cache_meta

This is a binary file and will not be displayed.

example/build/lsp/erlang/example/_gleam_artefacts/graphql@get_characters.cache

This is a binary file and will not be displayed.

example/build/lsp/erlang/example/_gleam_artefacts/graphql@get_characters.cache_meta

This is a binary file and will not be displayed.

example/build/lsp/erlang/example/_gleam_artefacts/graphql@multi_query.cache

This is a binary file and will not be displayed.

example/build/lsp/erlang/example/_gleam_artefacts/graphql@multi_query.cache_inline

This is a binary file and will not be displayed.

example/build/lsp/erlang/example/_gleam_artefacts/graphql@multi_query.cache_meta

This is a binary file and will not be displayed.

example/build/lsp/erlang/example/_gleam_artefacts/graphql@multi_query.cache_warnings

This is a binary file and will not be displayed.

example/build/lsp/erlang/example/_gleam_artefacts/graphql@multi_query_with_vars.cache

This is a binary file and will not be displayed.

example/build/lsp/erlang/example/_gleam_artefacts/graphql@multi_query_with_vars.cache_inline

This is a binary file and will not be displayed.

example/build/lsp/erlang/example/_gleam_artefacts/graphql@multi_query_with_vars.cache_meta

This is a binary file and will not be displayed.

example/build/lsp/erlang/example/_gleam_artefacts/graphql@multi_query_with_vars.cache_warnings

This is a binary file and will not be displayed.

example/build/lsp/erlang/squall/_gleam_artefacts/squall.cache

This is a binary file and will not be displayed.

example/build/lsp/erlang/squall/_gleam_artefacts/squall.cache_meta

This is a binary file and will not be displayed.

example/build/lsp/erlang/squall/_gleam_artefacts/squall@internal@codegen.cache

This is a binary file and will not be displayed.

example/build/lsp/erlang/squall/_gleam_artefacts/squall@internal@codegen.cache_meta

This is a binary file and will not be displayed.

example/build/lsp/erlang/squall/_gleam_artefacts/squall@internal@parser.cache

This is a binary file and will not be displayed.

example/build/lsp/erlang/squall/_gleam_artefacts/squall@internal@parser.cache_meta

This is a binary file and will not be displayed.

example/build/lsp/erlang/squall/_gleam_artefacts/squall@internal@type_mapping.cache

This is a binary file and will not be displayed.

+7 -7
example/build/packages/packages.toml
··· 1 1 [packages] 2 - gleam_http = "3.7.1" 3 - thoas = "1.2.1" 4 - squall = "1.0.0" 5 - simplifile = "2.3.0" 6 - filepath = "1.0.0" 2 + gleam_stdlib = "0.39.0" 3 + argv = "1.0.2" 7 4 gleam_httpc = "2.3.0" 8 5 glam = "2.0.2" 6 + squall = "1.0.0" 7 + thoas = "1.2.1" 8 + filepath = "1.0.0" 9 9 gleam_json = "1.0.1" 10 - gleam_stdlib = "0.39.0" 11 - argv = "1.0.2" 10 + simplifile = "2.3.0" 11 + gleam_http = "3.7.1"
+7 -4
example/src/example.gleam
··· 1 1 import gleam/io 2 - import graphql/get_characters 2 + import graphql/multi_query 3 3 4 4 pub fn main() { 5 + io.println("Squall Multi-Field Query Example") 6 + io.println("=================================\n") 7 + 5 8 let result = 6 - get_characters.get_characters("https://rickandmortyapi.com/graphql") 9 + multi_query.multi_query("https://rickandmortyapi.com/graphql") 7 10 8 11 case result { 9 12 Ok(response) -> { 10 - // Handle response with list of characters 11 - io.println("Successfully fetched characters!") 13 + io.println("Response:") 12 14 io.debug(response) 13 15 Nil 14 16 } 15 17 Error(err) -> { 16 18 io.println("Error: " <> err) 19 + Nil 17 20 } 18 21 } 19 22 }
+34
example/src/example_with_vars.gleam
··· 1 + import gleam/io 2 + import graphql/multi_query_with_vars 3 + 4 + pub fn main() { 5 + io.println("Squall Multi-Field Query Example (with Variables)") 6 + io.println("==================================================\n") 7 + 8 + io.println("Calling multi_query_with_vars with:") 9 + io.println(" page: 2") 10 + io.println(" name: \"rick\"") 11 + io.println(" locationId: \"1\"") 12 + io.println(" episodeIds: [1, 2]\n") 13 + 14 + let result = 15 + multi_query_with_vars.multi_query_with_vars( 16 + "https://rickandmortyapi.com/graphql", 17 + 2, 18 + "rick", 19 + "1", 20 + [1, 2], 21 + ) 22 + 23 + case result { 24 + Ok(response) -> { 25 + io.println("Response:") 26 + io.debug(response) 27 + Nil 28 + } 29 + Error(err) -> { 30 + io.println("Error: " <> err) 31 + Nil 32 + } 33 + } 34 + }
+22 -26
example/src/graphql/get_character.gleam
··· 19 19 20 20 pub fn character_decoder() -> dynamic.Decoder(Character) { 21 21 fn(data: dynamic.Dynamic) -> Result(Character, List(dynamic.DecodeError)) { 22 - use id <- result.try(dynamic.field("id", dynamic.optional(dynamic.string))( 23 - data, 24 - )) 22 + use id <- result.try(dynamic.field("id", dynamic.optional(dynamic.string))(data)) 25 23 use name <- result.try(dynamic.field( 26 24 "name", 27 25 dynamic.optional(dynamic.string), ··· 42 40 "gender", 43 41 dynamic.optional(dynamic.string), 44 42 )(data)) 45 - Ok(Character( 46 - id: id, 47 - name: name, 48 - status: status, 49 - species: species, 50 - type_: type_, 51 - gender: gender, 52 - )) 43 + Ok( 44 + Character( 45 + id: id, 46 + name: name, 47 + status: status, 48 + species: species, 49 + type_: type_, 50 + gender: gender, 51 + ), 52 + ) 53 53 } 54 54 } 55 55 ··· 58 58 } 59 59 60 60 pub fn get_character_response_decoder() -> dynamic.Decoder(GetCharacterResponse) { 61 - fn(data: dynamic.Dynamic) -> Result( 62 - GetCharacterResponse, 63 - List(dynamic.DecodeError), 64 - ) { 61 + fn(data: dynamic.Dynamic) -> Result(GetCharacterResponse, List(dynamic.DecodeError)) { 65 62 use character <- result.try(dynamic.field( 66 63 "character", 67 64 dynamic.optional(character_decoder()), ··· 70 67 } 71 68 } 72 69 73 - pub fn get_character( 74 - endpoint: String, 75 - id: String, 76 - ) -> Result(GetCharacterResponse, String) { 70 + pub fn get_character(endpoint: String, id: String) -> Result( 71 + GetCharacterResponse, 72 + String, 73 + ) { 77 74 let query = 78 - "query GetCharacter($id: ID!) { character { id name status species type gender } }" 75 + "query GetCharacter($id: ID!) { character(id: $id) { id name status species type gender } }" 79 76 let variables = json.object([#("id", json.string(id))]) 80 77 let body = 81 - json.object([#("query", json.string(query)), #("variables", variables)]) 78 + json.object([#("query", json.string(query)), #("variables", variables)]) 82 79 use req <- result.try( 83 80 request.to(endpoint) 84 81 |> result.map_error(fn(_) { "Invalid endpoint URL" }), 85 82 ) 86 - let req = 87 - req 88 - |> request.set_method(http.Post) 89 - |> request.set_body(json.to_string(body)) 90 - |> request.set_header("content-type", "application/json") 83 + let req = req 84 + |> request.set_method(http.Post) 85 + |> request.set_body(json.to_string(body)) 86 + |> request.set_header("content-type", "application/json") 91 87 use resp <- result.try( 92 88 httpc.send(req) 93 89 |> result.map_error(fn(_) { "HTTP request failed" }),
+9 -17
example/src/graphql/get_characters.gleam
··· 31 31 32 32 pub fn character_decoder() -> dynamic.Decoder(Character) { 33 33 fn(data: dynamic.Dynamic) -> Result(Character, List(dynamic.DecodeError)) { 34 - use id <- result.try(dynamic.field("id", dynamic.optional(dynamic.string))( 35 - data, 36 - )) 34 + use id <- result.try(dynamic.field("id", dynamic.optional(dynamic.string))(data)) 37 35 use name <- result.try(dynamic.field( 38 36 "name", 39 37 dynamic.optional(dynamic.string), ··· 54 52 GetCharactersResponse(characters: Option(Characters)) 55 53 } 56 54 57 - pub fn get_characters_response_decoder() -> dynamic.Decoder( 58 - GetCharactersResponse, 59 - ) { 60 - fn(data: dynamic.Dynamic) -> Result( 61 - GetCharactersResponse, 62 - List(dynamic.DecodeError), 63 - ) { 55 + pub fn get_characters_response_decoder() -> dynamic.Decoder(GetCharactersResponse) { 56 + fn(data: dynamic.Dynamic) -> Result(GetCharactersResponse, List(dynamic.DecodeError)) { 64 57 use characters <- result.try(dynamic.field( 65 58 "characters", 66 59 dynamic.optional(characters_decoder()), ··· 71 64 72 65 pub fn get_characters(endpoint: String) -> Result(GetCharactersResponse, String) { 73 66 let query = 74 - "query GetCharacters { characters { results { id name status species } } }" 67 + "query GetCharacters { characters { results { id name status species } } }" 75 68 let variables = json.object([]) 76 69 let body = 77 - json.object([#("query", json.string(query)), #("variables", variables)]) 70 + json.object([#("query", json.string(query)), #("variables", variables)]) 78 71 use req <- result.try( 79 72 request.to(endpoint) 80 73 |> result.map_error(fn(_) { "Invalid endpoint URL" }), 81 74 ) 82 - let req = 83 - req 84 - |> request.set_method(http.Post) 85 - |> request.set_body(json.to_string(body)) 86 - |> request.set_header("content-type", "application/json") 75 + let req = req 76 + |> request.set_method(http.Post) 77 + |> request.set_body(json.to_string(body)) 78 + |> request.set_header("content-type", "application/json") 87 79 use resp <- result.try( 88 80 httpc.send(req) 89 81 |> result.map_error(fn(_) { "HTTP request failed" }),
+137
example/src/graphql/multi_query.gleam
··· 1 + import gleam/dynamic 2 + import gleam/http 3 + import gleam/http/request 4 + import gleam/httpc 5 + import gleam/json 6 + import gleam/option.{type Option} 7 + import gleam/result 8 + 9 + pub type Characters { 10 + Characters(info: Option(Info), results: Option(List(Character))) 11 + } 12 + 13 + pub fn characters_decoder() -> dynamic.Decoder(Characters) { 14 + fn(data: dynamic.Dynamic) -> Result(Characters, List(dynamic.DecodeError)) { 15 + use info <- result.try(dynamic.field( 16 + "info", 17 + dynamic.optional(info_decoder()), 18 + )(data)) 19 + use results <- result.try(dynamic.field( 20 + "results", 21 + dynamic.optional(dynamic.list(character_decoder())), 22 + )(data)) 23 + Ok(Characters(info: info, results: results)) 24 + } 25 + } 26 + 27 + pub type Info { 28 + Info(count: Option(Int)) 29 + } 30 + 31 + pub fn info_decoder() -> dynamic.Decoder(Info) { 32 + fn(data: dynamic.Dynamic) -> Result(Info, List(dynamic.DecodeError)) { 33 + use count <- result.try(dynamic.field( 34 + "count", 35 + dynamic.optional(dynamic.int), 36 + )(data)) 37 + Ok(Info(count: count)) 38 + } 39 + } 40 + 41 + pub type Character { 42 + Character(name: Option(String)) 43 + } 44 + 45 + pub fn character_decoder() -> dynamic.Decoder(Character) { 46 + fn(data: dynamic.Dynamic) -> Result(Character, List(dynamic.DecodeError)) { 47 + use name <- result.try(dynamic.field( 48 + "name", 49 + dynamic.optional(dynamic.string), 50 + )(data)) 51 + Ok(Character(name: name)) 52 + } 53 + } 54 + 55 + pub type Location { 56 + Location(id: Option(String)) 57 + } 58 + 59 + pub fn location_decoder() -> dynamic.Decoder(Location) { 60 + fn(data: dynamic.Dynamic) -> Result(Location, List(dynamic.DecodeError)) { 61 + use id <- result.try(dynamic.field("id", dynamic.optional(dynamic.string))(data)) 62 + Ok(Location(id: id)) 63 + } 64 + } 65 + 66 + pub type Episode { 67 + Episode(id: Option(String)) 68 + } 69 + 70 + pub fn episode_decoder() -> dynamic.Decoder(Episode) { 71 + fn(data: dynamic.Dynamic) -> Result(Episode, List(dynamic.DecodeError)) { 72 + use id <- result.try(dynamic.field("id", dynamic.optional(dynamic.string))(data)) 73 + Ok(Episode(id: id)) 74 + } 75 + } 76 + 77 + pub type MultiQueryResponse { 78 + MultiQueryResponse( 79 + characters: Option(Characters), 80 + location: Option(Location), 81 + episodes_by_ids: Option(List(Episode)), 82 + ) 83 + } 84 + 85 + pub fn multi_query_response_decoder() -> dynamic.Decoder(MultiQueryResponse) { 86 + fn(data: dynamic.Dynamic) -> Result(MultiQueryResponse, List(dynamic.DecodeError)) { 87 + use characters <- result.try(dynamic.field( 88 + "characters", 89 + dynamic.optional(characters_decoder()), 90 + )(data)) 91 + use location <- result.try(dynamic.field( 92 + "location", 93 + dynamic.optional(location_decoder()), 94 + )(data)) 95 + use episodes_by_ids <- result.try(dynamic.field( 96 + "episodesByIds", 97 + dynamic.optional(dynamic.list(episode_decoder())), 98 + )(data)) 99 + Ok( 100 + MultiQueryResponse( 101 + characters: characters, 102 + location: location, 103 + episodes_by_ids: episodes_by_ids, 104 + ), 105 + ) 106 + } 107 + } 108 + 109 + pub fn multi_query(endpoint: String) -> Result(MultiQueryResponse, String) { 110 + let query = 111 + "query MultiQuery { characters(page: 2, filter: { name: \"rick\" }) { info { count } results { name } } location(id: 1) { id } episodesByIds(ids: [1, 2]) { id } }" 112 + let variables = json.object([]) 113 + let body = 114 + json.object([#("query", json.string(query)), #("variables", variables)]) 115 + use req <- result.try( 116 + request.to(endpoint) 117 + |> result.map_error(fn(_) { "Invalid endpoint URL" }), 118 + ) 119 + let req = req 120 + |> request.set_method(http.Post) 121 + |> request.set_body(json.to_string(body)) 122 + |> request.set_header("content-type", "application/json") 123 + use resp <- result.try( 124 + httpc.send(req) 125 + |> result.map_error(fn(_) { "HTTP request failed" }), 126 + ) 127 + use json_value <- result.try( 128 + json.decode(from: resp.body, using: dynamic.dynamic) 129 + |> result.map_error(fn(_) { "Failed to decode JSON response" }), 130 + ) 131 + use data_field <- result.try( 132 + dynamic.field("data", dynamic.dynamic)(json_value) 133 + |> result.map_error(fn(_) { "No data field in response" }), 134 + ) 135 + multi_query_response_decoder()(data_field) 136 + |> result.map_error(fn(_) { "Failed to decode response data" }) 137 + }
+16
example/src/graphql/multi_query.gql
··· 1 + query MultiQuery { 2 + characters(page: 2, filter: { name: "rick" }) { 3 + info { 4 + count 5 + } 6 + results { 7 + name 8 + } 9 + } 10 + location(id: 1) { 11 + id 12 + } 13 + episodesByIds(ids: [1, 2]) { 14 + id 15 + } 16 + }
+165
example/src/graphql/multi_query_with_vars.gleam
··· 1 + import gleam/dynamic 2 + import gleam/http 3 + import gleam/http/request 4 + import gleam/httpc 5 + import gleam/json 6 + import gleam/option.{type Option} 7 + import gleam/result 8 + 9 + pub type Characters { 10 + Characters(info: Option(Info), results: Option(List(Character))) 11 + } 12 + 13 + pub fn characters_decoder() -> dynamic.Decoder(Characters) { 14 + fn(data: dynamic.Dynamic) -> Result(Characters, List(dynamic.DecodeError)) { 15 + use info <- result.try(dynamic.field( 16 + "info", 17 + dynamic.optional(info_decoder()), 18 + )(data)) 19 + use results <- result.try(dynamic.field( 20 + "results", 21 + dynamic.optional(dynamic.list(character_decoder())), 22 + )(data)) 23 + Ok(Characters(info: info, results: results)) 24 + } 25 + } 26 + 27 + pub type Info { 28 + Info(count: Option(Int)) 29 + } 30 + 31 + pub fn info_decoder() -> dynamic.Decoder(Info) { 32 + fn(data: dynamic.Dynamic) -> Result(Info, List(dynamic.DecodeError)) { 33 + use count <- result.try(dynamic.field( 34 + "count", 35 + dynamic.optional(dynamic.int), 36 + )(data)) 37 + Ok(Info(count: count)) 38 + } 39 + } 40 + 41 + pub type Character { 42 + Character(name: Option(String)) 43 + } 44 + 45 + pub fn character_decoder() -> dynamic.Decoder(Character) { 46 + fn(data: dynamic.Dynamic) -> Result(Character, List(dynamic.DecodeError)) { 47 + use name <- result.try(dynamic.field( 48 + "name", 49 + dynamic.optional(dynamic.string), 50 + )(data)) 51 + Ok(Character(name: name)) 52 + } 53 + } 54 + 55 + pub type Location { 56 + Location(id: Option(String), name: Option(String)) 57 + } 58 + 59 + pub fn location_decoder() -> dynamic.Decoder(Location) { 60 + fn(data: dynamic.Dynamic) -> Result(Location, List(dynamic.DecodeError)) { 61 + use id <- result.try(dynamic.field("id", dynamic.optional(dynamic.string))( 62 + data, 63 + )) 64 + use name <- result.try(dynamic.field( 65 + "name", 66 + dynamic.optional(dynamic.string), 67 + )(data)) 68 + Ok(Location(id: id, name: name)) 69 + } 70 + } 71 + 72 + pub type Episode { 73 + Episode(id: Option(String), name: Option(String)) 74 + } 75 + 76 + pub fn episode_decoder() -> dynamic.Decoder(Episode) { 77 + fn(data: dynamic.Dynamic) -> Result(Episode, List(dynamic.DecodeError)) { 78 + use id <- result.try(dynamic.field("id", dynamic.optional(dynamic.string))( 79 + data, 80 + )) 81 + use name <- result.try(dynamic.field( 82 + "name", 83 + dynamic.optional(dynamic.string), 84 + )(data)) 85 + Ok(Episode(id: id, name: name)) 86 + } 87 + } 88 + 89 + pub type MultiQueryWithVarsResponse { 90 + MultiQueryWithVarsResponse( 91 + characters: Option(Characters), 92 + location: Option(Location), 93 + episodes_by_ids: Option(List(Episode)), 94 + ) 95 + } 96 + 97 + pub fn multi_query_with_vars_response_decoder() -> dynamic.Decoder( 98 + MultiQueryWithVarsResponse, 99 + ) { 100 + fn(data: dynamic.Dynamic) -> Result( 101 + MultiQueryWithVarsResponse, 102 + List(dynamic.DecodeError), 103 + ) { 104 + use characters <- result.try(dynamic.field( 105 + "characters", 106 + dynamic.optional(characters_decoder()), 107 + )(data)) 108 + use location <- result.try(dynamic.field( 109 + "location", 110 + dynamic.optional(location_decoder()), 111 + )(data)) 112 + use episodes_by_ids <- result.try(dynamic.field( 113 + "episodesByIds", 114 + dynamic.optional(dynamic.list(episode_decoder())), 115 + )(data)) 116 + Ok(MultiQueryWithVarsResponse( 117 + characters: characters, 118 + location: location, 119 + episodes_by_ids: episodes_by_ids, 120 + )) 121 + } 122 + } 123 + 124 + pub fn multi_query_with_vars( 125 + endpoint: String, 126 + page: Int, 127 + name: String, 128 + location_id: String, 129 + episode_ids: List(Int), 130 + ) -> Result(MultiQueryWithVarsResponse, String) { 131 + let query = 132 + "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 } }" 133 + let variables = 134 + json.object([ 135 + #("page", json.int(page)), 136 + #("name", json.string(name)), 137 + #("locationId", json.string(location_id)), 138 + #("episodeIds", json.array(from: episode_ids, of: json.int)), 139 + ]) 140 + let body = 141 + json.object([#("query", json.string(query)), #("variables", variables)]) 142 + use req <- result.try( 143 + request.to(endpoint) 144 + |> result.map_error(fn(_) { "Invalid endpoint URL" }), 145 + ) 146 + let req = 147 + req 148 + |> request.set_method(http.Post) 149 + |> request.set_body(json.to_string(body)) 150 + |> request.set_header("content-type", "application/json") 151 + use resp <- result.try( 152 + httpc.send(req) 153 + |> result.map_error(fn(_) { "HTTP request failed" }), 154 + ) 155 + use json_value <- result.try( 156 + json.decode(from: resp.body, using: dynamic.dynamic) 157 + |> result.map_error(fn(_) { "Failed to decode JSON response" }), 158 + ) 159 + use data_field <- result.try( 160 + dynamic.field("data", dynamic.dynamic)(json_value) 161 + |> result.map_error(fn(_) { "No data field in response" }), 162 + ) 163 + multi_query_with_vars_response_decoder()(data_field) 164 + |> result.map_error(fn(_) { "Failed to decode response data" }) 165 + }
+18
example/src/graphql/multi_query_with_vars.gql
··· 1 + query MultiQueryWithVars($page: Int, $name: String, $locationId: ID!, $episodeIds: [Int!]!) { 2 + characters(page: $page, filter: { name: $name }) { 3 + info { 4 + count 5 + } 6 + results { 7 + name 8 + } 9 + } 10 + location(id: $locationId) { 11 + id 12 + name 13 + } 14 + episodesByIds(ids: $episodeIds) { 15 + id 16 + name 17 + } 18 + }
+70 -11
src/squall/internal/codegen.gleam
··· 1 1 import glam/doc.{type Document} 2 2 import gleam/dict 3 + import gleam/float 4 + import gleam/int 3 5 import gleam/list 4 6 import gleam/option.{None, Some} 5 7 import gleam/result ··· 99 101 |> doc.concat 100 102 } 101 103 102 - /// Sanitize field names by appending underscore to reserved keywords 104 + /// Sanitize field names by converting to snake_case and appending underscore to reserved keywords 103 105 fn sanitize_field_name(name: String) -> String { 104 - case list.contains(reserved_keywords, name) { 105 - True -> name <> "_" 106 - False -> name 106 + let snake_cased = snake_case(name) 107 + case list.contains(reserved_keywords, snake_cased) { 108 + True -> snake_cased <> "_" 109 + False -> snake_cased 107 110 } 108 111 } 109 112 ··· 511 514 type_mapping.parser_type_to_schema_type(var.type_ref) 512 515 |> result.then(type_mapping.graphql_to_gleam), 513 516 ) 517 + let param_name = snake_case(var.name) 514 518 Ok( 515 519 doc.from_string( 516 - var.name <> ": " <> type_mapping.to_gleam_type_string(gleam_type), 520 + param_name <> ": " <> type_mapping.to_gleam_type_string(gleam_type), 517 521 ), 518 522 ) 519 523 }) ··· 534 538 type_mapping.parser_type_to_schema_type(var.type_ref) 535 539 |> result.then(type_mapping.graphql_to_gleam), 536 540 ) 537 - let value_encoder = encode_variable_value(var.name, gleam_type) 541 + let param_name = snake_case(var.name) 542 + let value_encoder = encode_variable_value(param_name, gleam_type) 538 543 Ok( 539 544 doc.concat([ 540 545 doc.from_string("#("), ··· 668 673 type_mapping.FloatType -> 669 674 call_doc("json.float", [doc.from_string(var_name)]) 670 675 type_mapping.BoolType -> call_doc("json.bool", [doc.from_string(var_name)]) 671 - type_mapping.ListType(_inner) -> 676 + type_mapping.ListType(inner) -> { 677 + let encoder = case inner { 678 + type_mapping.StringType -> "json.string" 679 + type_mapping.IntType -> "json.int" 680 + type_mapping.FloatType -> "json.float" 681 + type_mapping.BoolType -> "json.bool" 682 + _ -> "json.string" 683 + } 672 684 call_doc("json.array", [ 673 685 doc.from_string("from: " <> var_name), 674 - doc.from_string("of: json.string"), 686 + doc.from_string("of: " <> encoder), 675 687 ]) 688 + } 676 689 type_mapping.OptionType(inner) -> 677 690 call_doc("json.nullable", [ 678 691 doc.from_string(var_name), ··· 720 733 selections 721 734 |> list.map(fn(selection) { 722 735 case selection { 723 - parser.FieldSelection(name, _alias, _args, nested) -> { 736 + parser.FieldSelection(name, _alias, args, nested) -> { 737 + let args_str = format_arguments(args) 724 738 case nested { 725 - [] -> name 726 - subs -> name <> " " <> build_selection_set(subs) 739 + [] -> name <> args_str 740 + subs -> name <> args_str <> " " <> build_selection_set(subs) 727 741 } 728 742 } 729 743 } ··· 755 769 |> string.split("_") 756 770 |> list.map(capitalize) 757 771 |> string.join("") 772 + } 773 + 774 + fn format_value(value: parser.Value) -> String { 775 + case value { 776 + parser.IntValue(i) -> int.to_string(i) 777 + parser.FloatValue(f) -> float.to_string(f) 778 + parser.StringValue(s) -> "\"" <> s <> "\"" 779 + parser.BooleanValue(True) -> "true" 780 + parser.BooleanValue(False) -> "false" 781 + parser.NullValue -> "null" 782 + parser.VariableValue(name) -> "$" <> name 783 + parser.ListValue(values) -> { 784 + let formatted_values = 785 + values 786 + |> list.map(format_value) 787 + |> string.join(", ") 788 + "[" <> formatted_values <> "]" 789 + } 790 + parser.ObjectValue(fields) -> { 791 + let formatted_fields = 792 + fields 793 + |> list.map(fn(field) { 794 + let #(name, value) = field 795 + name <> ": " <> format_value(value) 796 + }) 797 + |> string.join(", ") 798 + "{ " <> formatted_fields <> " }" 799 + } 800 + } 801 + } 802 + 803 + fn format_arguments(arguments: List(parser.Argument)) -> String { 804 + case arguments { 805 + [] -> "" 806 + args -> { 807 + let formatted_args = 808 + args 809 + |> list.map(fn(arg) { 810 + let parser.Argument(name, value) = arg 811 + name <> ": " <> format_value(value) 812 + }) 813 + |> string.join(", ") 814 + "(" <> formatted_args <> ")" 815 + } 816 + } 758 817 } 759 818 760 819 fn snake_case(s: String) -> String {
+2
src/squall/internal/parser.gleam
··· 660 660 661 661 case token_pos.token { 662 662 RightBracket -> Ok(#(ListValue(list.reverse(acc)), advance(state))) 663 + Comma -> parse_list_value(advance(state), acc) 663 664 _ -> { 664 665 use #(value, state) <- result.try(parse_value(state)) 665 666 parse_list_value(state, [value, ..acc]) ··· 675 676 676 677 case token_pos.token { 677 678 RightBrace -> Ok(#(ObjectValue(list.reverse(acc)), advance(state))) 679 + Comma -> parse_object_value(advance(state), acc) 678 680 Name(_) -> { 679 681 use #(name, state) <- result.try(parse_name(state)) 680 682 use state <- result.try(expect_token(state, Colon, "object field value"))
+371
test/codegen_test.gleam
··· 349 349 Error(_) -> Nil 350 350 } 351 351 } 352 + 353 + // Test: Generate query with inline scalar arguments 354 + pub fn generate_inline_scalar_arguments_test() { 355 + let query_source = 356 + " 357 + query GetCharacter { 358 + character(id: 1) { 359 + id 360 + name 361 + } 362 + } 363 + " 364 + 365 + let assert Ok(operation) = parser.parse(query_source) 366 + 367 + let character_fields = [ 368 + schema.Field( 369 + "id", 370 + schema.NonNullType(schema.NamedType("ID", schema.Scalar)), 371 + [], 372 + None, 373 + ), 374 + schema.Field( 375 + "name", 376 + schema.NonNullType(schema.NamedType("String", schema.Scalar)), 377 + [], 378 + None, 379 + ), 380 + ] 381 + 382 + let mock_schema = 383 + schema.Schema( 384 + Some("Query"), 385 + None, 386 + None, 387 + dict.from_list([ 388 + #("Character", schema.ObjectType("Character", character_fields, None)), 389 + #( 390 + "Query", 391 + schema.ObjectType( 392 + "Query", 393 + [ 394 + schema.Field( 395 + "character", 396 + schema.NamedType("Character", schema.Object), 397 + [ 398 + schema.InputValue( 399 + "id", 400 + schema.NonNullType(schema.NamedType("ID", schema.Scalar)), 401 + None, 402 + ), 403 + ], 404 + None, 405 + ), 406 + ], 407 + None, 408 + ), 409 + ), 410 + ]), 411 + ) 412 + 413 + let result = 414 + codegen.generate_operation("get_character", operation, mock_schema, "") 415 + 416 + case result { 417 + Ok(code) -> { 418 + code 419 + |> birdie.snap(title: "Query with inline scalar arguments") 420 + } 421 + Error(_) -> Nil 422 + } 423 + } 424 + 425 + // Test: Generate query with inline object arguments 426 + pub fn generate_inline_object_arguments_test() { 427 + let query_source = 428 + " 429 + query GetCharacters { 430 + characters(filter: { name: \"rick\", status: \"alive\" }) { 431 + results { 432 + id 433 + name 434 + } 435 + } 436 + } 437 + " 438 + 439 + let assert Ok(operation) = parser.parse(query_source) 440 + 441 + let character_fields = [ 442 + schema.Field( 443 + "id", 444 + schema.NonNullType(schema.NamedType("ID", schema.Scalar)), 445 + [], 446 + None, 447 + ), 448 + schema.Field( 449 + "name", 450 + schema.NonNullType(schema.NamedType("String", schema.Scalar)), 451 + [], 452 + None, 453 + ), 454 + ] 455 + 456 + let characters_result_fields = [ 457 + schema.Field( 458 + "results", 459 + schema.ListType(schema.NamedType("Character", schema.Object)), 460 + [], 461 + None, 462 + ), 463 + ] 464 + 465 + let mock_schema = 466 + schema.Schema( 467 + Some("Query"), 468 + None, 469 + None, 470 + dict.from_list([ 471 + #("Character", schema.ObjectType("Character", character_fields, None)), 472 + #( 473 + "CharactersResult", 474 + schema.ObjectType("CharactersResult", characters_result_fields, None), 475 + ), 476 + #( 477 + "Query", 478 + schema.ObjectType( 479 + "Query", 480 + [ 481 + schema.Field( 482 + "characters", 483 + schema.NamedType("CharactersResult", schema.Object), 484 + [ 485 + schema.InputValue( 486 + "filter", 487 + schema.NamedType("FilterInput", schema.InputObject), 488 + None, 489 + ), 490 + ], 491 + None, 492 + ), 493 + ], 494 + None, 495 + ), 496 + ), 497 + ]), 498 + ) 499 + 500 + let result = 501 + codegen.generate_operation("get_characters", operation, mock_schema, "") 502 + 503 + case result { 504 + Ok(code) -> { 505 + code 506 + |> birdie.snap(title: "Query with inline object arguments") 507 + } 508 + Error(_) -> Nil 509 + } 510 + } 511 + 512 + // Test: Generate query with inline array arguments 513 + pub fn generate_inline_array_arguments_test() { 514 + let query_source = 515 + " 516 + query GetEpisodes { 517 + episodesByIds(ids: [1, 2, 3]) { 518 + id 519 + name 520 + } 521 + } 522 + " 523 + 524 + let assert Ok(operation) = parser.parse(query_source) 525 + 526 + let episode_fields = [ 527 + schema.Field( 528 + "id", 529 + schema.NonNullType(schema.NamedType("ID", schema.Scalar)), 530 + [], 531 + None, 532 + ), 533 + schema.Field( 534 + "name", 535 + schema.NonNullType(schema.NamedType("String", schema.Scalar)), 536 + [], 537 + None, 538 + ), 539 + ] 540 + 541 + let mock_schema = 542 + schema.Schema( 543 + Some("Query"), 544 + None, 545 + None, 546 + dict.from_list([ 547 + #("Episode", schema.ObjectType("Episode", episode_fields, None)), 548 + #( 549 + "Query", 550 + schema.ObjectType( 551 + "Query", 552 + [ 553 + schema.Field( 554 + "episodesByIds", 555 + schema.ListType(schema.NamedType("Episode", schema.Object)), 556 + [ 557 + schema.InputValue( 558 + "ids", 559 + schema.ListType(schema.NonNullType(schema.NamedType( 560 + "Int", 561 + schema.Scalar, 562 + ))), 563 + None, 564 + ), 565 + ], 566 + None, 567 + ), 568 + ], 569 + None, 570 + ), 571 + ), 572 + ]), 573 + ) 574 + 575 + let result = 576 + codegen.generate_operation("get_episodes", operation, mock_schema, "") 577 + 578 + case result { 579 + Ok(code) -> { 580 + code 581 + |> birdie.snap(title: "Query with inline array arguments") 582 + } 583 + Error(_) -> Nil 584 + } 585 + } 586 + 587 + // Test: Generate query with multiple root fields and mixed argument types 588 + pub fn generate_multiple_root_fields_test() { 589 + let query_source = 590 + " 591 + query MultiQuery { 592 + characters(page: 2, filter: { name: \"rick\" }) { 593 + info { 594 + count 595 + } 596 + results { 597 + name 598 + } 599 + } 600 + location(id: 1) { 601 + id 602 + } 603 + episodesByIds(ids: [1, 2]) { 604 + id 605 + } 606 + } 607 + " 608 + 609 + let assert Ok(operation) = parser.parse(query_source) 610 + 611 + let character_fields = [ 612 + schema.Field("name", schema.NamedType("String", schema.Scalar), [], None), 613 + ] 614 + 615 + let info_fields = [ 616 + schema.Field("count", schema.NamedType("Int", schema.Scalar), [], None), 617 + ] 618 + 619 + let characters_result_fields = [ 620 + schema.Field( 621 + "info", 622 + schema.NamedType("Info", schema.Object), 623 + [], 624 + None, 625 + ), 626 + schema.Field( 627 + "results", 628 + schema.ListType(schema.NamedType("Character", schema.Object)), 629 + [], 630 + None, 631 + ), 632 + ] 633 + 634 + let location_fields = [ 635 + schema.Field("id", schema.NamedType("ID", schema.Scalar), [], None), 636 + ] 637 + 638 + let episode_fields = [ 639 + schema.Field("id", schema.NamedType("ID", schema.Scalar), [], None), 640 + ] 641 + 642 + let mock_schema = 643 + schema.Schema( 644 + Some("Query"), 645 + None, 646 + None, 647 + dict.from_list([ 648 + #("Character", schema.ObjectType("Character", character_fields, None)), 649 + #("Info", schema.ObjectType("Info", info_fields, None)), 650 + #( 651 + "CharactersResult", 652 + schema.ObjectType("CharactersResult", characters_result_fields, None), 653 + ), 654 + #("Location", schema.ObjectType("Location", location_fields, None)), 655 + #("Episode", schema.ObjectType("Episode", episode_fields, None)), 656 + #( 657 + "Query", 658 + schema.ObjectType( 659 + "Query", 660 + [ 661 + schema.Field( 662 + "characters", 663 + schema.NamedType("CharactersResult", schema.Object), 664 + [ 665 + schema.InputValue( 666 + "page", 667 + schema.NamedType("Int", schema.Scalar), 668 + None, 669 + ), 670 + schema.InputValue( 671 + "filter", 672 + schema.NamedType("FilterInput", schema.InputObject), 673 + None, 674 + ), 675 + ], 676 + None, 677 + ), 678 + schema.Field( 679 + "location", 680 + schema.NamedType("Location", schema.Object), 681 + [ 682 + schema.InputValue( 683 + "id", 684 + schema.NonNullType(schema.NamedType("ID", schema.Scalar)), 685 + None, 686 + ), 687 + ], 688 + None, 689 + ), 690 + schema.Field( 691 + "episodesByIds", 692 + schema.ListType(schema.NamedType("Episode", schema.Object)), 693 + [ 694 + schema.InputValue( 695 + "ids", 696 + schema.ListType(schema.NonNullType(schema.NamedType( 697 + "Int", 698 + schema.Scalar, 699 + ))), 700 + None, 701 + ), 702 + ], 703 + None, 704 + ), 705 + ], 706 + None, 707 + ), 708 + ), 709 + ]), 710 + ) 711 + 712 + let result = 713 + codegen.generate_operation("multi_query", operation, mock_schema, "") 714 + 715 + case result { 716 + Ok(code) -> { 717 + code 718 + |> birdie.snap(title: "Query with multiple root fields and mixed arguments") 719 + } 720 + Error(_) -> Nil 721 + } 722 + }