diff --git a/src/base/result_decoder.re b/src/base/result_decoder.re index afd2863b..b458cfde 100644 --- a/src/base/result_decoder.re +++ b/src/base/result_decoder.re @@ -575,6 +575,45 @@ let rec unify_document_schema = (config, document) => { ...rest, ] => [ { + open Result; + + let with_decoder = + switch (fg_directives |> find_directive("bsDecoder")) { + | None => Ok(None) + | Some({item: {d_arguments, _}, span}) => + switch (find_argument("fn", d_arguments)) { + | None => + Error( + make_error( + error_marker, + config.map_loc, + span, + "bsDecoder must be given 'fn' argument", + ), + ) + | Some((_, {item: Iv_string(fn_name), span})) => + Ok( + Some( + structure => + Res_custom_decoder( + config.map_loc(span), + fn_name, + structure, + ), + ), + ) + | Some((_, {span, _})) => + Error( + make_error( + error_marker, + config.map_loc, + span, + "The 'fn' argument must be a string", + ), + ) + } + }; + let is_record = has_directive("bsRecord", fg_directives); switch (Schema.lookup_type(config.schema, fg_type_condition.item)) { | None => @@ -601,13 +640,20 @@ let rec unify_document_schema = (config, document) => { Some(fg_selection_set), ); - Mod_fragment( - fg_name.item, - [], - error_marker.has_error, - fg, - structure, - ); + switch (with_decoder) { + | Error(err) => Mod_fragment(fg_name.item, [], true, fg, err) + | Ok(decoder) => + Mod_fragment( + fg_name.item, + [], + error_marker.has_error, + fg, + switch (decoder) { + | Some(decoder) => decoder(structure) + | None => structure + }, + ) + }; }; }, ...unify_document_schema(config, rest), diff --git a/tests_bucklescript/__tests__/fragmentDefinition.re b/tests_bucklescript/__tests__/fragmentDefinition.re index 83d61174..8263d70f 100644 --- a/tests_bucklescript/__tests__/fragmentDefinition.re +++ b/tests_bucklescript/__tests__/fragmentDefinition.re @@ -1,9 +1,29 @@ +type record = { + nullableOfNullable: option(array(option(string))), + nullableOfNonNullable: option(array(string)), +}; + +let concat = ({nullableOfNullable, nullableOfNonNullable}) => { + let x = + nullableOfNullable + ->Belt.Option.getWithDefault([||]) + ->Belt.Array.keepMap(x => x); + let y = nullableOfNonNullable->Belt.Option.getWithDefault([||]); + + Belt.Array.concat(x, y); +}; + module Fragments = [%graphql {| fragment listFragment on Lists { nullableOfNullable nullableOfNonNullable } + + fragment concatFragment on Lists @bsRecord @bsDecoder(fn: "concat") { + nullableOfNullable + nullableOfNonNullable + } |} ]; @@ -18,6 +38,10 @@ module MyQuery = [%graphql ...Fragments.ListFragment @bsField(name: "frag1") ...Fragments.ListFragment @bsField(name: "frag2") } + + l3: lists { + ...Fragments.ConcatFragment + } } |} ]; @@ -26,34 +50,35 @@ open Jest; open Expect; describe("Fragment definition", () => { + let expectedObject = { + "nullableOfNullable": Some([|Some("a"), None, Some("b")|]), + "nullableOfNonNullable": None, + }; + test("Decodes the fragment", () => {| { "l1": {"nullableOfNullable": ["a", null, "b"]}, - "l2": {"nullableOfNullable": ["a", null, "b"]} + "l2": {"nullableOfNullable": ["a", null, "b"]}, + "l3": { + "nullableOfNullable": ["a", null, "b", null, "c"], + "nullableOfNonNullable": ["d", "e"] + } }|} |> Js.Json.parseExn |> MyQuery.parse |> expect |> toEqual({ - "l1": { - "nullableOfNullable": Some([|Some("a"), None, Some("b")|]), - "nullableOfNonNullable": None, - }, + "l1": expectedObject, "l2": { - "frag1": { - "nullableOfNullable": Some([|Some("a"), None, Some("b")|]), - "nullableOfNonNullable": None, - }, - "frag2": { - "nullableOfNullable": Some([|Some("a"), None, Some("b")|]), - "nullableOfNonNullable": None, - }, + "frag1": expectedObject, + "frag2": expectedObject, }, + "l3": [|"a", "b", "c", "d", "e"|], }) ); test("Removes @bsField from query output", () => MyQuery.query |> Js.String.includes("@bsField") |> expect |> toBe(false) ); -}); +}); \ No newline at end of file diff --git a/tests_bucklescript/__tests__/fragmentDefinition.rei b/tests_bucklescript/__tests__/fragmentDefinition.rei index 312bab8f..549438c0 100644 --- a/tests_bucklescript/__tests__/fragmentDefinition.rei +++ b/tests_bucklescript/__tests__/fragmentDefinition.rei @@ -1,3 +1,8 @@ +type record = { + nullableOfNullable: option(array(option(string))), + nullableOfNonNullable: option(array(string)), +}; + module Fragments: { module ListFragment: { type t = { @@ -33,6 +38,7 @@ module MyQuery: { "nullableOfNonNullable": option(array(string)), }, }, + "l3": array(string), }; let make: @@ -52,4 +58,4 @@ module MyQuery: { "variables": Js.Json.t, }; let makeVariables: unit => Js.Json.t; -}; +}; \ No newline at end of file diff --git a/tests_native/fragment_definition.re b/tests_native/fragment_definition.re index 6738efaf..729ff9db 100644 --- a/tests_native/fragment_definition.re +++ b/tests_native/fragment_definition.re @@ -1,11 +1,38 @@ open Test_shared; +type record = { + nullableOfNullable: option(array(option(string))), + nullableOfNonNullable: option(array(string)), +}; + +let concat = ({nullableOfNullable, nullableOfNonNullable}) => { + let x = switch (nullableOfNullable) { + | None => [||] + | Some(arr) => arr |> Array.map(v => switch(v) { + | None => [||] + | Some(s) => [|s|] + }) |> Array.to_list |> Array.concat + }; + + let y = switch (nullableOfNonNullable) { + | None => [||] + | Some(a) => a + }; + + Array.append(x, y); +}; + module Fragments = [%graphql {| fragment listFragment on Lists { nullableOfNullable nullableOfNonNullable } + + fragment concatFragment on Lists @bsRecord @bsDecoder(fn: "concat") { + nullableOfNullable + nullableOfNonNullable + } |} ]; @@ -26,6 +53,10 @@ module MyQuery = [%graphql ...Fragments.ListFragment @bsField(name: "frag1") ...Fragments.ListFragment @bsField(name: "frag2") } + + l3: lists { + ...Fragments.ConcatFragment + } } |} ]; @@ -38,6 +69,7 @@ type qt = { frag1: ft, frag2: ft, }, + l3: array(string) }; let print_fragment = (formatter, obj: ft) => @@ -84,7 +116,8 @@ let my_query: module Alcotest.TESTABLE with type t = qt = let equal = (a, b) => fragment_equal(a#l1, b#l1) && fragment_equal(a#l2#frag1, b#l2#frag1) - && fragment_equal(a#l2#frag2, b#l2#frag2); + && fragment_equal(a#l2#frag2, b#l2#frag2) + && a#l3 == b#l3; }); let decodes_the_fragment = () => @@ -96,7 +129,11 @@ let decodes_the_fragment = () => {| { "l1": {"nullableOfNullable": ["a", null, "b"]}, - "l2": {"nullableOfNullable": ["a", null, "b"]} + "l2": {"nullableOfNullable": ["a", null, "b"]}, + "l3": { + "nullableOfNullable": ["a", null, "b", null, "c"], + "nullableOfNonNullable": ["d", "e"] + } }|}, ), ), @@ -119,7 +156,8 @@ let decodes_the_fragment = () => pub nullableOfNullable = Some([|Some("a"), None, Some("b")|]); pub nullableOfNonNullable = None } - } + }; + pub l3 = [|"a", "b", "c", "d", "e"|] }, );