From 6f26826c475d3d38025b9f325f2f3eeed1b91c94 Mon Sep 17 00:00:00 2001 From: Andrew Bennett Date: Sun, 28 Apr 2024 18:41:16 -0500 Subject: [PATCH] Support DESC as described in Argo 1.2.0 --- apps/argo/src/argo.app.src | 2 +- apps/argo/src/argo_typer.erl | 4 +- .../argo_graphql_directive_definition.erl | 38 ++++- .../graphql/argo_graphql_type_definition.erl | 47 +++++- .../src/value/argo_json_scalar_decoder.erl | 5 +- .../value/argo_json_scalar_decoder_base64.erl | 8 +- .../src/value/argo_json_scalar_encoder.erl | 3 + .../value/argo_json_scalar_encoder_base64.erl | 8 +- .../src/value/argo_json_value_decoder.erl | 18 ++- .../src/value/argo_json_value_encoder.erl | 5 +- apps/argo/src/value/argo_scalar_value.erl | 15 ++ apps/argo/src/value/argo_value_decoder.erl | 95 ++++++----- apps/argo/src/value/argo_value_encoder.erl | 33 +++- apps/argo/src/value/argo_value_printer.erl | 3 +- .../src/wire/argo_json_wire_type_decoder.erl | 4 + .../src/wire/argo_json_wire_type_encoder.erl | 4 +- apps/argo/src/wire/argo_scalar_wire_type.erl | 12 +- apps/argo/src/wire/argo_wire_type.erl | 2 + apps/argo/src/wire/argo_wire_type_decoder.erl | 4 +- apps/argo/src/wire/argo_wire_type_encoder.erl | 4 +- apps/argo/src/wire/argo_wire_type_printer.erl | 3 +- apps/argo_test/src/proper_argo.erl | 5 +- .../proper_argo_graphql_service_document.erl | 7 +- apps/argo_test/test/argo_typer_SUITE.erl | 153 +++++++++++++----- .../executable_document.graphql | 9 ++ .../responses/Argo_1_2_JSON.json | 40 +++++ .../service_document.graphql | 62 +++++++ 27 files changed, 477 insertions(+), 116 deletions(-) create mode 100644 apps/argo_test/test/argo_typer_SUITE_data/responses/Argo_1_2_JSON.json diff --git a/apps/argo/src/argo.app.src b/apps/argo/src/argo.app.src index cd06df1..8917dcf 100644 --- a/apps/argo/src/argo.app.src +++ b/apps/argo/src/argo.app.src @@ -7,7 +7,7 @@ %%% % @format {application, argo, [ {description, "argo: Erlang implementation of Argo for GraphQL"}, - {vsn, "1.0.6"}, + {vsn, "1.0.7"}, {modules, []}, {registered, []}, %% NOTE: Remember to sync changes to `applications` to diff --git a/apps/argo/src/argo_typer.erl b/apps/argo/src/argo_typer.erl index 394f5c1..a17fcbb 100644 --- a/apps/argo/src/argo_typer.erl +++ b/apps/argo/src/argo_typer.erl @@ -1122,7 +1122,9 @@ get_argo_codec_directive_value(#argo_graphql_type_definition{ value = #argo_graphql_value_const{inner = {int, FixedLength}} } when is_integer(FixedLength) andalso FixedLength >= 0 -> {some, argo_scalar_wire_type:fixed(FixedLength)} - end + end; + <<"DESC">> -> + {some, argo_scalar_wire_type:desc()} end end; error -> diff --git a/apps/argo/src/graphql/argo_graphql_directive_definition.erl b/apps/argo/src/graphql/argo_graphql_directive_definition.erl index fed8303..ef11400 100644 --- a/apps/argo/src/graphql/argo_graphql_directive_definition.erl +++ b/apps/argo/src/graphql/argo_graphql_directive_definition.erl @@ -80,19 +80,34 @@ -spec builtin(DirectiveName) -> {ok, DirectiveDefinition} | error when DirectiveName :: argo_types:name(), DirectiveDefinition :: t(). builtin(DirectiveName = <<"ArgoCodec">>) -> - % See: https://msolomon.github.io/argo/versions/1.0/spec#sec-Directives + % See: https://msolomon.github.io/argo/versions/1.2/spec#sec-Directives DirectiveDefinition1 = new(DirectiveName, false), CodecArgumentType = argo_graphql_type:non_null_type(argo_graphql_non_null_type:new(<<"ArgoCodecType">>)), - CodecArgumentDefinition = argo_graphql_input_value_definition:new(<<"codec">>, CodecArgumentType), + CodecArgumentDefinition1 = argo_graphql_input_value_definition:new(<<"codec">>, CodecArgumentType), + CodecArgumentDefinition2 = argo_graphql_input_value_definition:set_description( + CodecArgumentDefinition1, {some, <<"The codec to use to serialize and deserialize this scalar.">>} + ), FixedLengthArgumentType = argo_graphql_type:named_type(<<"Int">>), - FixedLengthArgumentDefinition = argo_graphql_input_value_definition:new(<<"fixedLength">>, FixedLengthArgumentType), + FixedLengthArgumentDefinition1 = argo_graphql_input_value_definition:new( + <<"fixedLength">>, FixedLengthArgumentType + ), + FixedLengthArgumentDefinition2 = argo_graphql_input_value_definition:set_description( + FixedLengthArgumentDefinition1, + {some, + <<"For the FIXED codec only: the length of the encoded value in bytes. Required for FIXED, and invalid for all other codecs.">>} + ), DirectiveDefinition2 = add_argument_definitions(DirectiveDefinition1, [ - CodecArgumentDefinition, FixedLengthArgumentDefinition + CodecArgumentDefinition2, FixedLengthArgumentDefinition2 ]), DirectiveDefinition3 = add_directive_locations(DirectiveDefinition2, ['SCALAR', 'ENUM']), - {ok, DirectiveDefinition3}; + DirectiveDefinition4 = set_description( + DirectiveDefinition3, + {some, + <<"Specifies how to serialize and deserialize this scalar. This is necessary for custom scalars to work with Argo serialization. Adding, changing, or removing this directive is typically a breaking change.">>} + ), + {ok, DirectiveDefinition4}; builtin(DirectiveName = <<"ArgoDeduplicate">>) -> - % See: https://msolomon.github.io/argo/versions/1.0/spec#sec-Directives + % See: https://msolomon.github.io/argo/versions/1.2/spec#sec-Directives DirectiveDefinition1 = new(DirectiveName, false), DeduplicateArgumentType = argo_graphql_type:non_null_type(argo_graphql_non_null_type:new(<<"Boolean">>)), DeduplicateArgumentDefinition1 = argo_graphql_input_value_definition:new( @@ -102,9 +117,16 @@ builtin(DirectiveName = <<"ArgoDeduplicate">>) -> DeduplicateArgumentDefinition2 = argo_graphql_input_value_definition:set_default_value( DeduplicateArgumentDefinition1, {some, DeduplicatDefaultValue} ), - DirectiveDefinition2 = add_argument_definitions(DirectiveDefinition1, [DeduplicateArgumentDefinition2]), + DeduplicateArgumentDefinition3 = argo_graphql_input_value_definition:set_description( + DeduplicateArgumentDefinition2, {some, <<"Should values of this type be deduplicated?">>} + ), + DirectiveDefinition2 = add_argument_definitions(DirectiveDefinition1, [DeduplicateArgumentDefinition3]), DirectiveDefinition3 = add_directive_locations(DirectiveDefinition2, ['SCALAR', 'ENUM']), - {ok, DirectiveDefinition3}; + DirectiveDefinition4 = set_description( + DirectiveDefinition3, + {some, <<"Deduplicate values of this type. Adding or removing this directive is typically a breaking change.">>} + ), + {ok, DirectiveDefinition4}; builtin(DirectiveName = <<"defer">>) -> % See: https://github.com/graphql/graphql-spec/pull/742 DirectiveDefinition1 = new(DirectiveName, false), diff --git a/apps/argo/src/graphql/argo_graphql_type_definition.erl b/apps/argo/src/graphql/argo_graphql_type_definition.erl index d84ffb6..3f0d381 100644 --- a/apps/argo/src/graphql/argo_graphql_type_definition.erl +++ b/apps/argo/src/graphql/argo_graphql_type_definition.erl @@ -88,16 +88,47 @@ builtin(TypeName = <<"ArgoCodecType">>) -> % See: https://msolomon.github.io/argo/versions/1.0/spec#sec-Directives EnumTypeDefinition1 = argo_graphql_enum_type_definition:new(), + MakeEnumValueDefinition = fun(EnumValue, OptionDescription) -> + EnumValueDefinition1 = argo_graphql_enum_value_definition:new(EnumValue), + EnumValueDefinition2 = argo_graphql_enum_value_definition:set_description( + EnumValueDefinition1, OptionDescription + ), + EnumValueDefinition2 + end, EnumTypeDefinition2 = argo_graphql_enum_type_definition:add_enum_value_definitions(EnumTypeDefinition1, [ - argo_graphql_enum_value_definition:new(<<"String">>), - argo_graphql_enum_value_definition:new(<<"Int">>), - argo_graphql_enum_value_definition:new(<<"Float">>), - argo_graphql_enum_value_definition:new(<<"Boolean">>), - argo_graphql_enum_value_definition:new(<<"BYTES">>), - argo_graphql_enum_value_definition:new(<<"FIXED">>) + MakeEnumValueDefinition( + <<"String">>, {some, <<"Serialize and deserialize a scalar as a GraphQL String (UTF-8).">>} + ), + MakeEnumValueDefinition( + <<"Int">>, {some, <<"Serialize and deserialize a scalar as a GraphQL Int (32-bit signed integer).">>} + ), + MakeEnumValueDefinition( + <<"Float">>, + {some, + <<"Serialize and deserialize a scalar as a GraphQL Float (IEEE 754 double-precision floating-point).">>} + ), + MakeEnumValueDefinition(<<"Boolean">>, {some, <<"Serialize and deserialize a scalar as a GraphQL Boolean.">>}), + MakeEnumValueDefinition( + <<"BYTES">>, + {some, + <<"Serialize and deserialize a scalar as Argo BYTES: a variable-length length-prefixed byte array.">>} + ), + MakeEnumValueDefinition( + <<"FIXED">>, {some, <<"Serialize and deserialize a scalar as Argo FIXED: a fixed-length byte array.">>} + ), + MakeEnumValueDefinition( + <<"DESC">>, + {some, + <<"Serialize and deserialize a scalar as Argo DESC: a flexible self-describing binary format (somewhat like JSON).">>} + ) ]), - TypeDefinition = enum_type_definition(TypeName, EnumTypeDefinition2), - {ok, TypeDefinition}; + TypeDefinition1 = enum_type_definition(TypeName, EnumTypeDefinition2), + TypeDefinition2 = set_description( + TypeDefinition1, + {some, + <<"Specifies how to serialize and deserialize this scalar. Adding, changing, or removing this directive is typically a breaking change.">>} + ), + {ok, TypeDefinition2}; builtin(TypeName) when ?is_builtin_scalar(TypeName) -> % See: https://spec.graphql.org/draft/#sec-Scalars.Built-in-Scalars ScalarTypeDefinition = argo_graphql_scalar_type_definition:new(), diff --git a/apps/argo/src/value/argo_json_scalar_decoder.erl b/apps/argo/src/value/argo_json_scalar_decoder.erl index a8e613d..7485715 100644 --- a/apps/argo/src/value/argo_json_scalar_decoder.erl +++ b/apps/argo/src/value/argo_json_scalar_decoder.erl @@ -19,6 +19,7 @@ %% Behaviour -callback init(Options) -> JsonScalarDecoder when Options :: options(), JsonScalarDecoder :: t(). + -callback decode_block_scalar(JsonScalarDecoder, BlockKey, BlockScalarHint, JsonValue) -> {ok, JsonScalarDecoder, BlockScalar} | {error, ErrorReason} when @@ -28,6 +29,7 @@ when JsonValue :: argo_json:json_value(), BlockScalar :: argo_scalar_value:inner(), ErrorReason :: error_reason(). + -callback decode_desc_scalar(JsonScalarDecoder, DescHint, JsonValue) -> {ok, JsonScalarDecoder, DescScalar} | {error, ErrorReason} when @@ -36,6 +38,7 @@ when JsonValue :: argo_json:json_value(), DescScalar :: argo_desc_value:inner_scalar(), ErrorReason :: error_reason(). + -callback decode_scalar(JsonScalarDecoder, ScalarHint, JsonValue) -> {ok, JsonScalarDecoder, Scalar} | {error, ErrorReason} when @@ -49,7 +52,7 @@ when -type desc_hint() :: null | boolean | string | bytes | int | float. -type error_reason() :: invalid | type_mismatch | dynamic(). -type options() :: dynamic(). --type scalar_hint() :: boolean | bytes | {fixed, argo_types:length()} | float64 | string | varint. +-type scalar_hint() :: boolean | bytes | desc | {fixed, argo_types:length()} | float64 | string | varint. -type t() :: dynamic(). -export_type([ diff --git a/apps/argo/src/value/argo_json_scalar_decoder_base64.erl b/apps/argo/src/value/argo_json_scalar_decoder_base64.erl index fa2ffc9..76b2e2c 100644 --- a/apps/argo/src/value/argo_json_scalar_decoder_base64.erl +++ b/apps/argo/src/value/argo_json_scalar_decoder_base64.erl @@ -118,7 +118,7 @@ when JsonValue :: argo_json:json_value(), Scalar :: argo_scalar_value:inner(), ErrorReason :: argo_json_scalar_decoder:error_reason(). -decode_scalar(JsonScalarDecoder1 = #argo_json_scalar_decoder_base64{}, ScalarHint, JsonValue) -> +decode_scalar(JsonScalarDecoder1 = #argo_json_scalar_decoder_base64{mode = Mode}, ScalarHint, JsonValue) -> case ScalarHint of string when is_binary(JsonValue) -> {ok, JsonScalarDecoder1, {string, JsonValue}}; @@ -142,6 +142,12 @@ decode_scalar(JsonScalarDecoder1 = #argo_json_scalar_decoder_base64{}, ScalarHin DecodeBytesError = {error, _Reason} -> DecodeBytesError end; + desc -> + JsonValueDecoder1 = argo_json_value_decoder:new(?MODULE, #{mode => Mode}), + {_JsonValueDecoder2, DescValue} = argo_json_value_decoder:decode_desc_wire_type( + JsonValueDecoder1, JsonValue + ), + {ok, JsonScalarDecoder1, {desc, DescValue}}; _ -> {error, type_mismatch} end. diff --git a/apps/argo/src/value/argo_json_scalar_encoder.erl b/apps/argo/src/value/argo_json_scalar_encoder.erl index b631363..4892550 100644 --- a/apps/argo/src/value/argo_json_scalar_encoder.erl +++ b/apps/argo/src/value/argo_json_scalar_encoder.erl @@ -19,13 +19,16 @@ %% Behaviour -callback init(Options) -> JsonScalarEncoder when Options :: options(), JsonScalarEncoder :: t(). + -callback encode_block_scalar(JsonScalarEncoder, BlockKey, BlockScalar) -> {JsonScalarEncoder, JsonValue} when JsonScalarEncoder :: t(), BlockKey :: argo_types:name(), BlockScalar :: argo_scalar_value:inner(), JsonValue :: argo_json:json_value(). + -callback encode_desc_scalar(JsonScalarEncoder, DescScalar) -> {JsonScalarEncoder, JsonValue} when JsonScalarEncoder :: t(), DescScalar :: argo_desc_value:inner_scalar(), JsonValue :: argo_json:json_value(). + -callback encode_scalar(JsonScalarEncoder, Scalar) -> {JsonScalarEncoder, JsonValue} when JsonScalarEncoder :: t(), Scalar :: argo_scalar_value:inner(), JsonValue :: argo_json:json_value(). diff --git a/apps/argo/src/value/argo_json_scalar_encoder_base64.erl b/apps/argo/src/value/argo_json_scalar_encoder_base64.erl index c3a52d2..a9866af 100644 --- a/apps/argo/src/value/argo_json_scalar_encoder_base64.erl +++ b/apps/argo/src/value/argo_json_scalar_encoder_base64.erl @@ -93,7 +93,7 @@ encode_desc_scalar(JsonScalarEncoder = #argo_json_scalar_encoder_base64{}, DescS -spec encode_scalar(JsonScalarEncoder, Scalar) -> {JsonScalarEncoder, JsonValue} when JsonScalarEncoder :: t(), Scalar :: argo_scalar_value:inner(), JsonValue :: argo_json:json_value(). -encode_scalar(JsonScalarEncoder = #argo_json_scalar_encoder_base64{}, Scalar) -> +encode_scalar(JsonScalarEncoder = #argo_json_scalar_encoder_base64{mode = Mode}, Scalar) -> case Scalar of {string, V} -> {JsonScalarEncoder, argo_json:string(V)}; @@ -106,7 +106,11 @@ encode_scalar(JsonScalarEncoder = #argo_json_scalar_encoder_base64{}, Scalar) -> {bytes, V} -> encode_bytes(JsonScalarEncoder, false, V); {fixed, V} -> - encode_bytes(JsonScalarEncoder, false, V) + encode_bytes(JsonScalarEncoder, false, V); + {desc, V} -> + JsonValueEncoder1 = argo_json_value_encoder:new(?MODULE, #{mode => Mode}), + {_JsonValueEncoder2, JsonValue} = argo_json_value_encoder:encode_desc_value(JsonValueEncoder1, V), + {JsonScalarEncoder, JsonValue} end. %%%============================================================================= diff --git a/apps/argo/src/value/argo_json_value_decoder.erl b/apps/argo/src/value/argo_json_value_decoder.erl index 4e16c33..efe330d 100644 --- a/apps/argo/src/value/argo_json_value_decoder.erl +++ b/apps/argo/src/value/argo_json_value_decoder.erl @@ -26,7 +26,8 @@ -export([ new/0, new/2, - decode_wire_type/3 + decode_wire_type/3, + decode_desc_wire_type/2 ]). %% Errors API @@ -162,6 +163,10 @@ decode_scalar_wire_type( ScalarValue = argo_scalar_value:fixed(FixedValue), JsonValueDecoder2 = maybe_update_json_scalar_decoder(JsonValueDecoder1, JsonScalarDecoder2), {JsonValueDecoder2, ScalarValue}; + {ok, JsonScalarDecoder2, {desc, DescValue}} when ScalarHint =:= desc -> + ScalarValue = argo_scalar_value:desc(DescValue), + JsonValueDecoder2 = maybe_update_json_scalar_decoder(JsonValueDecoder1, JsonScalarDecoder2), + {JsonValueDecoder2, ScalarValue}; {ok, _JsonScalarDecoder2, Scalar} -> error_scalar_type_mismatch([JsonValueDecoder1, ScalarWireType, JsonValue], ScalarHint, Scalar); {error, type_mismatch} -> @@ -229,6 +234,11 @@ decode_block_wire_type( BlockValue = argo_block_value:new(BlockWireType, ScalarValue), JsonValueDecoder2 = maybe_update_json_scalar_decoder(JsonValueDecoder1, JsonScalarDecoder2), {JsonValueDecoder2, BlockValue}; + {ok, JsonScalarDecoder2, {desc, DescValue}} when BlockScalarHint =:= desc -> + ScalarValue = argo_scalar_value:desc(DescValue), + BlockValue = argo_block_value:new(BlockWireType, ScalarValue), + JsonValueDecoder2 = maybe_update_json_scalar_decoder(JsonValueDecoder1, JsonScalarDecoder2), + {JsonValueDecoder2, BlockValue}; {ok, _JsonScalarDecoder2, BlockScalar} -> error_scalar_type_mismatch([JsonValueDecoder1, BlockWireType, JsonValue], BlockScalarHint, BlockScalar); {error, type_mismatch} -> @@ -605,6 +615,10 @@ error_scalar_type_mismatch(Args, ScalarHint, Scalar) -> {fixed, Length} -> error_with_info(badarg, Args, #{ 3 => {mismatch, {expected_fixed, Length}, Scalar} + }); + desc -> + error_with_info(badarg, Args, #{ + 3 => {mismatch, expected_desc, Scalar} }) end. @@ -623,6 +637,8 @@ format_error_description(_Key, expected_boolean) -> "expected JSON boolean"; format_error_description(_Key, expected_bytes) -> "expected JSON bytes"; +format_error_description(_Key, expected_desc) -> + "expected JSON self-describing type"; format_error_description(_Key, {expected_fixed, Length}) -> io_lib:format("expected JSON string of fixed-length ~w", [Length]); format_error_description(_Key, expected_float) -> diff --git a/apps/argo/src/value/argo_json_value_encoder.erl b/apps/argo/src/value/argo_json_value_encoder.erl index 67eae1e..853d5dc 100644 --- a/apps/argo/src/value/argo_json_value_encoder.erl +++ b/apps/argo/src/value/argo_json_value_encoder.erl @@ -26,7 +26,8 @@ -export([ new/0, new/2, - encode_value/2 + encode_value/2, + encode_desc_value/2 ]). %% Types @@ -135,6 +136,8 @@ encode_block_value( {bytes, V} when is_binary(V) -> JsonScalarEncoderModule:encode_block_scalar(JsonScalarEncoder1, BlockKey, BlockScalar); {fixed, V} when is_binary(V) -> + JsonScalarEncoderModule:encode_block_scalar(JsonScalarEncoder1, BlockKey, BlockScalar); + {desc, _V = #argo_desc_value{}} -> JsonScalarEncoderModule:encode_block_scalar(JsonScalarEncoder1, BlockKey, BlockScalar) end, JsonValueEncoder2 = diff --git a/apps/argo/src/value/argo_scalar_value.erl b/apps/argo/src/value/argo_scalar_value.erl index e6e94a2..4283c05 100644 --- a/apps/argo/src/value/argo_scalar_value.erl +++ b/apps/argo/src/value/argo_scalar_value.erl @@ -24,6 +24,7 @@ -export([ boolean/1, bytes/1, + desc/1, fixed/1, float64/1, string/1, @@ -35,6 +36,7 @@ deduplicate_by_default/1, is_boolean/1, is_bytes/1, + is_desc/1, is_fixed/1, is_fixed_length/2, is_float64/1, @@ -49,6 +51,7 @@ -type inner() :: {boolean, boolean()} | {bytes, binary()} + | {desc, argo_desc_value:t()} | {fixed, binary()} | {float64, float()} | {string, unicode:unicode_binary()} @@ -70,6 +73,9 @@ boolean(V) when erlang:is_boolean(V) -> #argo_scalar_value{inner = {boolean, V}} -spec bytes(binary()) -> ScalarValue when ScalarValue :: t(). bytes(V) when is_binary(V) -> #argo_scalar_value{inner = {bytes, V}}. +-spec desc(argo_desc_value:t()) -> ScalarValue when ScalarValue :: t(). +desc(V = #argo_desc_value{}) -> #argo_scalar_value{inner = {desc, V}}. + -spec fixed(binary()) -> ScalarValue when ScalarValue :: t(). fixed(V) when is_binary(V) -> #argo_scalar_value{inner = {fixed, V}}. @@ -93,13 +99,20 @@ deduplicate_by_default(#argo_scalar_value{}) -> false. -spec is_boolean(ScalarValue) -> boolean() when ScalarValue :: t(). is_boolean(#argo_scalar_value{inner = {T, _}}) -> T =:= boolean. + -spec is_bytes(ScalarValue) -> boolean() when ScalarValue :: t(). is_bytes(#argo_scalar_value{inner = {T, _}}) -> T =:= bytes. + +-spec is_desc(ScalarValue) -> boolean() when ScalarValue :: t(). +is_desc(#argo_scalar_value{inner = {T, _}}) -> T =:= desc. + -spec is_fixed(ScalarValue) -> boolean() when ScalarValue :: t(). is_fixed(#argo_scalar_value{inner = {T, _}}) -> T =:= fixed. + -spec is_fixed_length(ScalarValue, Length) -> boolean() when ScalarValue :: t(), Length :: argo_types:length(). is_fixed_length(#argo_scalar_value{inner = {fixed, V}}, Length) -> byte_size(V) =:= Length; is_fixed_length(#argo_scalar_value{}, _Length) -> false. + -spec is_float64(ScalarValue) -> boolean() when ScalarValue :: t(). is_float64(#argo_scalar_value{inner = {T, _}}) -> T =:= float64. @@ -126,6 +139,8 @@ to_scalar_wire_type(#argo_scalar_value{inner = {boolean, _}}) -> argo_scalar_wire_type:boolean(); to_scalar_wire_type(#argo_scalar_value{inner = {bytes, _}}) -> argo_scalar_wire_type:bytes(); +to_scalar_wire_type(#argo_scalar_value{inner = {desc, _}}) -> + argo_scalar_wire_type:desc(); to_scalar_wire_type(#argo_scalar_value{inner = {fixed, Fixed}}) -> argo_scalar_wire_type:fixed(byte_size(Fixed)); to_scalar_wire_type(#argo_scalar_value{inner = {float64, _}}) -> diff --git a/apps/argo/src/value/argo_value_decoder.erl b/apps/argo/src/value/argo_value_decoder.erl index abd6218..f53f47e 100644 --- a/apps/argo/src/value/argo_value_decoder.erl +++ b/apps/argo/src/value/argo_value_decoder.erl @@ -172,6 +172,10 @@ decode_scalar_wire_type(ValueDecoder1 = #argo_value_decoder{}, ScalarWireType = {MessageDecoder2, Value} = argo_message_decoder:decode_block_fixed(MessageDecoder1, Length), ScalarValue = argo_scalar_value:fixed(Value), ValueDecoder3 = ValueDecoder2#argo_value_decoder{message = MessageDecoder2}, + {ValueDecoder3, ScalarValue}; + desc -> + {ValueDecoder3, DescValue} = decode_desc_wire_type(ValueDecoder2), + ScalarValue = argo_scalar_value:desc(DescValue), {ValueDecoder3, ScalarValue} end. @@ -179,14 +183,22 @@ decode_scalar_wire_type(ValueDecoder1 = #argo_value_decoder{}, ScalarWireType = -spec decode_block_wire_type(ValueDecoder, BlockWireType) -> {ValueDecoder, BlockValue} when ValueDecoder :: t(), BlockWireType :: argo_block_wire_type:t(), BlockValue :: argo_block_value:t(). decode_block_wire_type(ValueDecoder1 = #argo_value_decoder{}, BlockWireType = #argo_block_wire_type{}) -> - ValueDecoder2 = - #argo_value_decoder{message = MessageDecoder1} = maybe_decode_self_describing_label_for_scalar( - ValueDecoder1, BlockWireType#argo_block_wire_type.'of' - ), - {MessageDecoder2, ScalarValue} = argo_message_decoder:decode_block_scalar(MessageDecoder1, BlockWireType), - BlockValue = argo_block_value:new(BlockWireType, ScalarValue), - ValueDecoder3 = ValueDecoder2#argo_value_decoder{message = MessageDecoder2}, - {ValueDecoder3, BlockValue}. + case argo_scalar_wire_type:is_desc(BlockWireType#argo_block_wire_type.'of') of + false -> + ValueDecoder2 = + #argo_value_decoder{message = MessageDecoder1} = maybe_decode_self_describing_label_for_scalar( + ValueDecoder1, BlockWireType#argo_block_wire_type.'of' + ), + {MessageDecoder2, ScalarValue} = argo_message_decoder:decode_block_scalar(MessageDecoder1, BlockWireType), + BlockValue = argo_block_value:new(BlockWireType, ScalarValue), + ValueDecoder3 = ValueDecoder2#argo_value_decoder{message = MessageDecoder2}, + {ValueDecoder3, BlockValue}; + true -> + {ValueDecoder2, DescValue} = decode_desc_wire_type(ValueDecoder1), + ScalarValue = argo_scalar_value:desc(DescValue), + BlockValue = argo_block_value:new(BlockWireType, ScalarValue), + {ValueDecoder2, BlockValue} + end. %% @private -spec decode_nullable_wire_type(ValueDecoder, NullableWireType) -> {ValueDecoder, NullableValue} when @@ -757,37 +769,42 @@ maybe_decode_self_describing_label_for_scalar( decode_self_describing_label_for_scalar( ValueDecoder1 = #argo_value_decoder{message = MessageDecoder1}, ScalarWireType = #argo_scalar_wire_type{} ) -> - {MessageDecoder2, Label} = argo_message_decoder:read_core_label(MessageDecoder1), - ValueDecoder2 = ValueDecoder1#argo_value_decoder{message = MessageDecoder2}, - case ScalarWireType#argo_scalar_wire_type.inner of - string when Label =:= ?ARGO_LABEL_SELF_DESCRIBING_MARKER_STRING -> - ValueDecoder2; - boolean when - (Label =:= ?ARGO_LABEL_SELF_DESCRIBING_MARKER_FALSE orelse - Label =:= ?ARGO_LABEL_SELF_DESCRIBING_MARKER_TRUE) - -> - % rollback read_core_label for boolean - ValueDecoder1; - varint when Label =:= ?ARGO_LABEL_SELF_DESCRIBING_MARKER_INT -> - ValueDecoder2; - float64 when Label =:= ?ARGO_LABEL_SELF_DESCRIBING_MARKER_FLOAT -> - ValueDecoder2; - bytes when Label =:= ?ARGO_LABEL_SELF_DESCRIBING_MARKER_BYTES -> - ValueDecoder2; - #argo_fixed_wire_type{} when Label =:= ?ARGO_LABEL_SELF_DESCRIBING_MARKER_BYTES -> - ValueDecoder2; - string -> - error_with_info(badarg, [ValueDecoder1, ScalarWireType], #{1 => {invalid_string_label, Label}}); - boolean -> - error_with_info(badarg, [ValueDecoder1, ScalarWireType], #{1 => {invalid_boolean_label, Label}}); - varint -> - error_with_info(badarg, [ValueDecoder1, ScalarWireType], #{1 => {invalid_varint_label, Label}}); - float64 -> - error_with_info(badarg, [ValueDecoder1, ScalarWireType], #{1 => {invalid_float64_label, Label}}); - bytes -> - error_with_info(badarg, [ValueDecoder1, ScalarWireType], #{1 => {invalid_bytes_label, Label}}); - #argo_fixed_wire_type{} -> - error_with_info(badarg, [ValueDecoder1, ScalarWireType], #{1 => {invalid_fixed_label, Label}}) + case argo_scalar_wire_type:is_desc(ScalarWireType) of + false -> + {MessageDecoder2, Label} = argo_message_decoder:read_core_label(MessageDecoder1), + ValueDecoder2 = ValueDecoder1#argo_value_decoder{message = MessageDecoder2}, + case ScalarWireType#argo_scalar_wire_type.inner of + string when Label =:= ?ARGO_LABEL_SELF_DESCRIBING_MARKER_STRING -> + ValueDecoder2; + boolean when + (Label =:= ?ARGO_LABEL_SELF_DESCRIBING_MARKER_FALSE orelse + Label =:= ?ARGO_LABEL_SELF_DESCRIBING_MARKER_TRUE) + -> + % rollback read_core_label for boolean + ValueDecoder1; + varint when Label =:= ?ARGO_LABEL_SELF_DESCRIBING_MARKER_INT -> + ValueDecoder2; + float64 when Label =:= ?ARGO_LABEL_SELF_DESCRIBING_MARKER_FLOAT -> + ValueDecoder2; + bytes when Label =:= ?ARGO_LABEL_SELF_DESCRIBING_MARKER_BYTES -> + ValueDecoder2; + #argo_fixed_wire_type{} when Label =:= ?ARGO_LABEL_SELF_DESCRIBING_MARKER_BYTES -> + ValueDecoder2; + string -> + error_with_info(badarg, [ValueDecoder1, ScalarWireType], #{1 => {invalid_string_label, Label}}); + boolean -> + error_with_info(badarg, [ValueDecoder1, ScalarWireType], #{1 => {invalid_boolean_label, Label}}); + varint -> + error_with_info(badarg, [ValueDecoder1, ScalarWireType], #{1 => {invalid_varint_label, Label}}); + float64 -> + error_with_info(badarg, [ValueDecoder1, ScalarWireType], #{1 => {invalid_float64_label, Label}}); + bytes -> + error_with_info(badarg, [ValueDecoder1, ScalarWireType], #{1 => {invalid_bytes_label, Label}}); + #argo_fixed_wire_type{} -> + error_with_info(badarg, [ValueDecoder1, ScalarWireType], #{1 => {invalid_fixed_label, Label}}) + end; + true -> + ValueDecoder1 end. %% @private diff --git a/apps/argo/src/value/argo_value_encoder.erl b/apps/argo/src/value/argo_value_encoder.erl index 26673f9..3981ca8 100644 --- a/apps/argo/src/value/argo_value_encoder.erl +++ b/apps/argo/src/value/argo_value_encoder.erl @@ -114,10 +114,18 @@ encode_scalar_value(ValueEncoder1 = #argo_value_encoder{}, ScalarValue = #argo_s {varint, Value} -> argo_message_encoder:encode_block_varint(MessageEncoder1, Value); {float64, Value} -> argo_message_encoder:encode_block_float64(MessageEncoder1, Value); {bytes, Value} -> argo_message_encoder:encode_block_bytes(MessageEncoder1, Value); - {fixed, Value} -> argo_message_encoder:encode_block_fixed(MessageEncoder1, Value) + {fixed, Value} -> argo_message_encoder:encode_block_fixed(MessageEncoder1, Value); + {desc, _Value} -> MessageEncoder1 end, ValueEncoder3 = ValueEncoder2#argo_value_encoder{message = MessageEncoder2}, - ValueEncoder3. + ValueEncoder4 = + case ScalarValue#argo_scalar_value.inner of + {desc, DescValue} -> + encode_desc_value(ValueEncoder3, DescValue); + _ -> + ValueEncoder3 + end, + ValueEncoder4. %% @private -spec encode_block_value(ValueEncoder, BlockValue) -> ValueEncoder when @@ -127,9 +135,22 @@ encode_block_value(ValueEncoder1 = #argo_value_encoder{}, BlockValue = #argo_blo #argo_value_encoder{message = MessageEncoder1} = maybe_encode_self_describing_label_for_scalar( ValueEncoder1, BlockValue#argo_block_value.value ), - MessageEncoder2 = argo_message_encoder:encode_block_type(MessageEncoder1, BlockValue), + MessageEncoder2 = + case argo_scalar_value:is_desc(BlockValue#argo_block_value.value) of + false -> + argo_message_encoder:encode_block_type(MessageEncoder1, BlockValue); + true -> + MessageEncoder1 + end, ValueEncoder3 = ValueEncoder2#argo_value_encoder{message = MessageEncoder2}, - ValueEncoder3. + ValueEncoder4 = + case BlockValue#argo_block_value.value#argo_scalar_value.inner of + {desc, DescValue} -> + encode_desc_value(ValueEncoder3, DescValue); + _ -> + ValueEncoder3 + end, + ValueEncoder4. %% @private -spec encode_nullable_value(ValueEncoder, NullableValue) -> ValueEncoder when @@ -607,7 +628,9 @@ encode_self_describing_label_for_scalar( {bytes, _} -> argo_message_encoder:write_core_label(MessageEncoder1, ?ARGO_LABEL_SELF_DESCRIBING_MARKER_BYTES); {fixed, _} -> - argo_message_encoder:write_core_label(MessageEncoder1, ?ARGO_LABEL_SELF_DESCRIBING_MARKER_BYTES) + argo_message_encoder:write_core_label(MessageEncoder1, ?ARGO_LABEL_SELF_DESCRIBING_MARKER_BYTES); + {desc, _} -> + MessageEncoder1 end, ValueEncoder2 = ValueEncoder1#argo_value_encoder{message = MessageEncoder2}, ValueEncoder2. diff --git a/apps/argo/src/value/argo_value_printer.erl b/apps/argo/src/value/argo_value_printer.erl index 9e3c9ad..b8ec4d5 100644 --- a/apps/argo/src/value/argo_value_printer.erl +++ b/apps/argo/src/value/argo_value_printer.erl @@ -392,7 +392,8 @@ print_scalar_value(Printer1 = #argo_value_printer{}, ScalarValue = #argo_scalar_ {varint, Value} -> write(Printer1, "VARINT(~0tp)", [Value]); {float64, Value} -> write(Printer1, "FLOAT64(~0tp)", [Value]); {bytes, Value} -> write(Printer1, "BYTES(~0tp)", [Value]); - {fixed, Value} -> write(Printer1, "FIXED(~w, ~0tp)", [byte_size(Value), Value]) + {fixed, Value} -> write(Printer1, "FIXED(~w, ~0tp)", [byte_size(Value), Value]); + {desc, Value} -> print_desc_value(Printer1, Value) end. %% @private diff --git a/apps/argo/src/wire/argo_json_wire_type_decoder.erl b/apps/argo/src/wire/argo_json_wire_type_decoder.erl index 24a00cf..b91f05f 100644 --- a/apps/argo/src/wire/argo_json_wire_type_decoder.erl +++ b/apps/argo/src/wire/argo_json_wire_type_decoder.erl @@ -268,6 +268,10 @@ decode_scalar_wire_type(JsonWireTypeDecoder1 = #argo_json_wire_type_decoder{}, J 2 => {invalid_length, JsonLengthNumber} }) end; + <<"DESC">> -> + ok = check_for_unknown_keys(JsonObject, #{<<"type">> => []}), + ScalarWireType = argo_scalar_wire_type:desc(), + {JsonWireTypeDecoder1, ScalarWireType}; JsonTypeString when is_binary(JsonTypeString) -> error_with_info(badarg, [JsonWireTypeDecoder1, JsonValue], #{2 => {invalid_type, JsonTypeString}}) end. diff --git a/apps/argo/src/wire/argo_json_wire_type_encoder.erl b/apps/argo/src/wire/argo_json_wire_type_encoder.erl index 22497d5..41bb3fa 100644 --- a/apps/argo/src/wire/argo_json_wire_type_encoder.erl +++ b/apps/argo/src/wire/argo_json_wire_type_encoder.erl @@ -109,7 +109,9 @@ encode_scalar_wire_type( bytes -> {JsonWireTypeEncoder1, {[{<<"type">>, <<"BYTES">>}]}}; #argo_fixed_wire_type{length = Length} -> - {JsonWireTypeEncoder1, {[{<<"type">>, <<"FIXED">>}, {<<"length">>, Length}]}} + {JsonWireTypeEncoder1, {[{<<"type">>, <<"FIXED">>}, {<<"length">>, Length}]}}; + desc -> + {JsonWireTypeEncoder1, {[{<<"type">>, <<"DESC">>}]}} end. %% @private diff --git a/apps/argo/src/wire/argo_scalar_wire_type.erl b/apps/argo/src/wire/argo_scalar_wire_type.erl index 68c7c2a..c11178a 100644 --- a/apps/argo/src/wire/argo_scalar_wire_type.erl +++ b/apps/argo/src/wire/argo_scalar_wire_type.erl @@ -24,6 +24,7 @@ -export([ boolean/0, bytes/0, + desc/0, fixed/1, float64/0, string/0, @@ -35,6 +36,7 @@ deduplicate_by_default/1, is_boolean/1, is_bytes/1, + is_desc/1, is_fixed/1, is_fixed_length/2, is_float64/1, @@ -46,7 +48,7 @@ ]). %% Types --type inner() :: boolean | bytes | argo_fixed_wire_type:t() | float64 | string | varint. +-type inner() :: boolean | bytes | desc | argo_fixed_wire_type:t() | float64 | string | varint. -type t() :: #argo_scalar_wire_type{}. -export_type([ @@ -64,6 +66,9 @@ boolean() -> #argo_scalar_wire_type{inner = boolean}. -spec bytes() -> ScalarWireType when ScalarWireType :: t(). bytes() -> #argo_scalar_wire_type{inner = bytes}. +-spec desc() -> ScalarWireType when ScalarWireType :: t(). +desc() -> #argo_scalar_wire_type{inner = desc}. + -spec fixed(LengthOrFixedWireType) -> ScalarWireType when LengthOrFixedWireType :: argo_types:length() | argo_fixed_wire_type:t(), ScalarWireType :: t(). fixed(Length) when ?is_usize(Length) -> fixed(argo_fixed_wire_type:new(Length)); @@ -93,6 +98,9 @@ is_boolean(#argo_scalar_wire_type{inner = Inner}) -> Inner =:= boolean. -spec is_bytes(ScalarWireType) -> boolean() when ScalarWireType :: t(). is_bytes(#argo_scalar_wire_type{inner = Inner}) -> Inner =:= bytes. +-spec is_desc(ScalarWireType) -> boolean() when ScalarWireType :: t(). +is_desc(#argo_scalar_wire_type{inner = Inner}) -> Inner =:= desc. + -spec is_fixed(ScalarWireType) -> boolean() when ScalarWireType :: t(). is_fixed(#argo_scalar_wire_type{inner = #argo_fixed_wire_type{}}) -> true; is_fixed(#argo_scalar_wire_type{}) -> false. @@ -126,6 +134,8 @@ to_string(#argo_scalar_wire_type{inner = boolean}) -> "BOOLEAN"; to_string(#argo_scalar_wire_type{inner = bytes}) -> "BYTES"; +to_string(#argo_scalar_wire_type{inner = desc}) -> + "DESC"; to_string(#argo_scalar_wire_type{inner = #argo_fixed_wire_type{length = Length}}) -> io_lib:format("FIXED(~w)", [Length]); to_string(#argo_scalar_wire_type{inner = float64}) -> diff --git a/apps/argo/src/wire/argo_wire_type.erl b/apps/argo/src/wire/argo_wire_type.erl index 095617e..9bbe65e 100644 --- a/apps/argo/src/wire/argo_wire_type.erl +++ b/apps/argo/src/wire/argo_wire_type.erl @@ -198,6 +198,8 @@ record(RecordWireType = #argo_record_wire_type{}) -> #argo_wire_type{inner = RecordWireType}. -spec scalar(ScalarWireType) -> WireType when ScalarWireType :: argo_scalar_wire_type:t(), WireType :: t(). +scalar(#argo_scalar_wire_type{inner = desc}) -> + desc(); scalar(ScalarWireType = #argo_scalar_wire_type{}) -> #argo_wire_type{inner = ScalarWireType}. diff --git a/apps/argo/src/wire/argo_wire_type_decoder.erl b/apps/argo/src/wire/argo_wire_type_decoder.erl index 20bb479..7c785f7 100644 --- a/apps/argo/src/wire/argo_wire_type_decoder.erl +++ b/apps/argo/src/wire/argo_wire_type_decoder.erl @@ -144,7 +144,9 @@ decode_scalar_wire_type(WireTypeDecoder1 = #argo_wire_type_decoder{message = Mes ?ARGO_LABEL_WIRE_TYPE_MARKER_FIXED -> {MessageDecoder3, Length} = argo_message_decoder:read_core_length(MessageDecoder2), WireTypeDecoder3 = WireTypeDecoder2#argo_wire_type_decoder{message = MessageDecoder3}, - {WireTypeDecoder3, argo_scalar_wire_type:fixed(Length)} + {WireTypeDecoder3, argo_scalar_wire_type:fixed(Length)}; + ?ARGO_LABEL_WIRE_TYPE_MARKER_DESC -> + {WireTypeDecoder2, argo_scalar_wire_type:desc()} end. -spec decode_wire_type_store(WireTypeDecoder) -> {WireTypeDecoder, WireTypeStore} when diff --git a/apps/argo/src/wire/argo_wire_type_encoder.erl b/apps/argo/src/wire/argo_wire_type_encoder.erl index a2f872c..65de974 100644 --- a/apps/argo/src/wire/argo_wire_type_encoder.erl +++ b/apps/argo/src/wire/argo_wire_type_encoder.erl @@ -111,7 +111,9 @@ encode_scalar_wire_type( ME1 = MessageEncoder1, ME2 = argo_message_encoder:write_core_label(ME1, ?ARGO_LABEL_WIRE_TYPE_MARKER_FIXED), ME3 = argo_message_encoder:write_core_length(ME2, Length), - ME3 + ME3; + desc -> + argo_message_encoder:write_core_label(MessageEncoder1, ?ARGO_LABEL_WIRE_TYPE_MARKER_DESC) end, WireTypeEncoder2 = WireTypeEncoder1#argo_wire_type_encoder{message = MessageEncoder2}, WireTypeEncoder2. diff --git a/apps/argo/src/wire/argo_wire_type_printer.erl b/apps/argo/src/wire/argo_wire_type_printer.erl index 68d52cb..8f99f70 100644 --- a/apps/argo/src/wire/argo_wire_type_printer.erl +++ b/apps/argo/src/wire/argo_wire_type_printer.erl @@ -193,7 +193,8 @@ print_scalar_wire_type(Printer1 = #argo_wire_type_printer{}, ScalarWireType = #a varint -> write(Printer1, "VARINT", []); float64 -> write(Printer1, "FLOAT64", []); bytes -> write(Printer1, "BYTES", []); - #argo_fixed_wire_type{length = Length} -> write(Printer1, "FIXED(~w)", [Length]) + #argo_fixed_wire_type{length = Length} -> write(Printer1, "FIXED(~w)", [Length]); + desc -> write(Printer1, "DESC", []) end. %% @private diff --git a/apps/argo_test/src/proper_argo.erl b/apps/argo_test/src/proper_argo.erl index 6b3b5d3..1bda184 100644 --- a/apps/argo_test/src/proper_argo.erl +++ b/apps/argo_test/src/proper_argo.erl @@ -452,6 +452,8 @@ scalar_value(#argo_scalar_wire_type{inner = boolean}) -> ?LET(Value, boolean(), argo_scalar_value:boolean(Value)); scalar_value(#argo_scalar_wire_type{inner = bytes}) -> ?LET(Value, bytes(), argo_scalar_value:bytes(Value)); +scalar_value(#argo_scalar_wire_type{inner = desc}) -> + ?LET(Value, desc_value(), argo_scalar_value:desc(Value)); scalar_value(#argo_scalar_wire_type{inner = #argo_fixed_wire_type{length = Length}}) -> ?LET(Value, fixed(Length), argo_scalar_value:fixed(Value)); scalar_value(#argo_scalar_wire_type{inner = float64}) -> @@ -612,7 +614,8 @@ scalar_wire_type() -> exactly(argo_scalar_wire_type:float64()), exactly(argo_scalar_wire_type:string()), exactly(argo_scalar_wire_type:bytes()), - ?LET(Length, non_neg_integer(), argo_scalar_wire_type:fixed(Length)) + ?LET(Length, non_neg_integer(), argo_scalar_wire_type:fixed(Length)), + exactly(argo_scalar_wire_type:desc()) ]). -spec wire_type() -> proper_types:type(). diff --git a/apps/argo_test/src/proper_argo_graphql_service_document.erl b/apps/argo_test/src/proper_argo_graphql_service_document.erl index 2d7d12d..ea64f0d 100644 --- a/apps/argo_test/src/proper_argo_graphql_service_document.erl +++ b/apps/argo_test/src/proper_argo_graphql_service_document.erl @@ -199,7 +199,8 @@ add_argo_directives(TypeDefinition1 = #argo_graphql_type_definition{kind = Kind} {float, none}, {boolean, none}, {bytes, option(oneof([default, true, false]))}, - {{fixed, non_neg_integer()}, none} + {{fixed, non_neg_integer()}, none}, + {desc, none} ]), begin ArgoCodecType2 = @@ -215,7 +216,9 @@ add_argo_directives(TypeDefinition1 = #argo_graphql_type_definition{kind = Kind} bytes -> <<"BYTES">>; {fixed, _} -> - <<"FIXED">> + <<"FIXED">>; + desc -> + <<"DESC">> end, ArgoCodec1 = argo_graphql_directive_const:new(<<"ArgoCodec">>), ArgoCodec2 = argo_graphql_directive_const:add_argument_const( diff --git a/apps/argo_test/test/argo_typer_SUITE.erl b/apps/argo_test/test/argo_typer_SUITE.erl index b2be5ce..204d357 100644 --- a/apps/argo_test/test/argo_typer_SUITE.erl +++ b/apps/argo_test/test/argo_typer_SUITE.erl @@ -42,7 +42,8 @@ test_issue_8_inline_fragment_omittable/1, test_issue_19_field_selection_merging/1, test_issue_19_field_selection_merging_invalid/1, - test_argo_typer_resolver/1 + test_argo_typer_resolver/1, + test_argo_1_2_json/1 ]). %%%============================================================================= @@ -67,7 +68,8 @@ groups() -> test_issue_8_inline_fragment_omittable, test_issue_19_field_selection_merging, test_issue_19_field_selection_merging_invalid, - test_argo_typer_resolver + test_argo_typer_resolver, + test_argo_1_2_json ]} ]. @@ -83,7 +85,24 @@ init_per_group(static, Config) -> ExecutableDocumentFileName = filename:join([DataDir, "executable_document.graphql"]), ServiceDocument = argo_graphql_service_document:from_file(ServiceDocumentFileName), ExecutableDocument = argo_graphql_executable_document:from_file(ExecutableDocumentFileName), - [{service_document, ServiceDocument}, {executable_document, ExecutableDocument} | Config]; + JsonResponses = filelib:fold_files( + filename:join([DataDir, "responses"]), + ".*\\.json", + false, + fun(File, Acc) -> + {ok, JsonEncoded} = file:read_file(File), + JsonValue = argo_types:dynamic_cast(jsone:decode(JsonEncoded, [{object_format, tuple}])), + Key = argo_types:unicode_binary(filename:basename(File, ".json")), + Acc#{Key => JsonValue} + end, + maps:new() + ), + [ + {service_document, ServiceDocument}, + {executable_document, ExecutableDocument}, + {json_responses, JsonResponses} + | Config + ]; init_per_group(_Group, Config) -> Config. @@ -139,12 +158,8 @@ test_issue_7_incorrect_type_for_fields_in_fragment(Config) -> " extensions?: EXTENSIONS\n" "}" >>, - case Actual =:= Expected of - false -> - ct:fail("Expected:~n~ts~nActual:~n~ts~n", [Expected, Actual]); - true -> - ok - end. + ?assertEqual(Expected, Actual), + ok. test_issue_8_field_omittable(Config) -> ServiceDocument = test_server:lookup_config(service_document, Config), @@ -166,12 +181,8 @@ test_issue_8_field_omittable(Config) -> " extensions?: EXTENSIONS\n" "}" >>, - case Actual =:= Expected of - false -> - ct:fail("Expected:~n~ts~nActual:~n~ts~n", [Expected, Actual]); - true -> - ok - end. + ?assertEqual(Expected, Actual), + ok. test_issue_8_fragment_spread_omittable(Config) -> ServiceDocument = test_server:lookup_config(service_document, Config), @@ -214,12 +225,8 @@ test_issue_8_fragment_spread_omittable(Config) -> " extensions?: EXTENSIONS\n" "}" >>, - case Actual =:= Expected of - false -> - ct:fail("Expected:~n~ts~nActual:~n~ts~n", [Expected, Actual]); - true -> - ok - end. + ?assertEqual(Expected, Actual), + ok. test_issue_8_inline_fragment_omittable(Config) -> ServiceDocument = test_server:lookup_config(service_document, Config), @@ -246,12 +253,8 @@ test_issue_8_inline_fragment_omittable(Config) -> " extensions?: EXTENSIONS\n" "}" >>, - case Actual =:= Expected of - false -> - ct:fail("Expected:~n~ts~nActual:~n~ts~n", [Expected, Actual]); - true -> - ok - end. + ?assertEqual(Expected, Actual), + ok. test_issue_19_field_selection_merging(Config) -> ServiceDocument = test_server:lookup_config(service_document, Config), @@ -282,12 +285,8 @@ test_issue_19_field_selection_merging(Config) -> " extensions?: EXTENSIONS\n" "}" >>, - case Actual =:= Expected of - false -> - ct:fail("Expected:~n~ts~nActual:~n~ts~n", [Expected, Actual]); - true -> - ok - end. + ?assertEqual(Expected, Actual), + ok. test_issue_19_field_selection_merging_invalid(Config) -> SD = test_server:lookup_config(service_document, Config), @@ -316,12 +315,88 @@ test_argo_typer_resolver(Config) -> " extensions?: EXTENSIONS\n" "}" >>, - case Actual =:= Expected of - false -> - ct:fail("Expected:~n~ts~nActual:~n~ts~n", [Expected, Actual]); - true -> - ok - end. + ?assertEqual(Expected, Actual), + ok. + +test_argo_1_2_json(Config) -> + SD = test_server:lookup_config(service_document, Config), + ED = test_server:lookup_config(executable_document, Config), + Op = <<"Argo_1_2_JSON">>, + #{Op := JsonValue} = test_server:lookup_config(json_responses, Config), + {_, WireType} = argo_typer:derive_wire_type(SD, ED, {some, Op}), + ActualWireType = erlang:iolist_to_binary(argo:format(WireType)), + ExpectedWireType = + << + "{\n" + " data: {\n" + " json: DESC{JSON}?\n" + " nullJson: DESC{JSON}?\n" + " requiredJson: DESC{JSON}\n" + " nullRequiredJson: DESC{JSON}\n" + " }?\n" + " errors?: ERROR[]\n" + " extensions?: EXTENSIONS\n" + "}" + >>, + ?assertEqual(ExpectedWireType, ActualWireType), + Value = argo_value:from_json(WireType, JsonValue), + ActualValue = erlang:iolist_to_binary(argo:format(Value)), + ExpectedValue = + << + "{\n" + " data: NON_NULL({\n" + " json: NON_NULL(DESC({\n" + " <<\"name\">>: DESC(STRING(<<\"John Doe\">>))\n" + " <<\"age\">>: DESC(INT(30))\n" + " <<\"score\">>: DESC(FLOAT(-10.1))\n" + " <<\"eligible\">>: DESC(BOOLEAN(false))\n" + " <<\"null\">>: DESC(NULL)\n" + " <<\"entries\">>: DESC([\n" + " DESC({\n" + " <<\"name\">>: DESC(STRING(<<\"Entry 1\">>))\n" + " <<\"value\">>: DESC(INT(10))\n" + " }),\n" + " DESC({\n" + " <<\"name\">>: DESC(STRING(<<\"Entry 2\">>))\n" + " <<\"value\">>: DESC(INT(20))\n" + " }),\n" + " ])\n" + " }){JSON})\n" + " nullJson: NULL\n" + " requiredJson: DESC({\n" + " <<\"name\">>: DESC(STRING(<<\"John Doe\">>))\n" + " <<\"age\">>: DESC(INT(30))\n" + " <<\"score\">>: DESC(FLOAT(-10.1))\n" + " <<\"eligible\">>: DESC(BOOLEAN(false))\n" + " <<\"null\">>: DESC(NULL)\n" + " <<\"entries\">>: DESC([\n" + " DESC({\n" + " <<\"name\">>: DESC(STRING(<<\"Entry 1\">>))\n" + " <<\"value\">>: DESC(INT(10))\n" + " }),\n" + " DESC({\n" + " <<\"name\">>: DESC(STRING(<<\"Entry 2\">>))\n" + " <<\"value\">>: DESC(INT(20))\n" + " }),\n" + " ])\n" + " }){JSON}\n" + " nullRequiredJson: DESC(NULL){JSON}\n" + " })\n" + " errors?: ABSENT\n" + " extensions?: ABSENT\n" + "}" + >>, + ?assertEqual(ExpectedValue, ActualValue), + ArgoEncoded = argo_value:to_writer(Value), + ?assertMatch({<<>>, Value}, argo_value:from_reader(WireType, ArgoEncoded)), + ArgoSelfDescribingHeader = argo_header:new(#{self_describing => true}), + ArgoSelfDescribingEncoded = argo_value:to_writer(Value, ArgoSelfDescribingHeader), + ?assertMatch({<<>>, Value}, argo_value:from_reader(WireType, ArgoSelfDescribingEncoded)), + ExpectedJson = jsone:encode(JsonValue), + ArgoEncodedJson = argo_value:to_json(Value), + ActualJson = jsone:encode(ArgoEncodedJson), + ?assertEqual(ExpectedJson, ActualJson), + ok. %%%----------------------------------------------------------------------------- %%% Internal functions diff --git a/apps/argo_test/test/argo_typer_SUITE_data/executable_document.graphql b/apps/argo_test/test/argo_typer_SUITE_data/executable_document.graphql index 4c394b8..6421600 100644 --- a/apps/argo_test/test/argo_typer_SUITE_data/executable_document.graphql +++ b/apps/argo_test/test/argo_typer_SUITE_data/executable_document.graphql @@ -165,3 +165,12 @@ query SimpleQueryWithRelayResolver { strong_id__ } } + +# Test: Argo 1.2 - https://github.com/msolomon/argo/commit/4ab303b6c3fd433f44bc2b0389dc685265c362df + +query Argo_1_2_JSON { + json + nullJson: json + requiredJson + nullRequiredJson: requiredJson +} diff --git a/apps/argo_test/test/argo_typer_SUITE_data/responses/Argo_1_2_JSON.json b/apps/argo_test/test/argo_typer_SUITE_data/responses/Argo_1_2_JSON.json new file mode 100644 index 0000000..3ea5fad --- /dev/null +++ b/apps/argo_test/test/argo_typer_SUITE_data/responses/Argo_1_2_JSON.json @@ -0,0 +1,40 @@ +{ + "data": { + "json": { + "name": "John Doe", + "age": 30, + "score": -10.1, + "eligible": false, + "null": null, + "entries": [ + { + "name": "Entry 1", + "value": 10 + }, + { + "name": "Entry 2", + "value": 20 + } + ] + }, + "nullJson": null, + "requiredJson": { + "name": "John Doe", + "age": 30, + "score": -10.1, + "eligible": false, + "null": null, + "entries": [ + { + "name": "Entry 1", + "value": 10 + }, + { + "name": "Entry 2", + "value": 20 + } + ] + }, + "nullRequiredJson": null + } +} diff --git a/apps/argo_test/test/argo_typer_SUITE_data/service_document.graphql b/apps/argo_test/test/argo_typer_SUITE_data/service_document.graphql index 0f11250..24cca61 100644 --- a/apps/argo_test/test/argo_typer_SUITE_data/service_document.graphql +++ b/apps/argo_test/test/argo_typer_SUITE_data/service_document.graphql @@ -1,3 +1,61 @@ +""" +Specifies how to serialize and deserialize this scalar. Adding, changing, or removing this directive is typically a breaking change. +""" +enum ArgoCodecType { + """ + Serialize and deserialize a scalar as a GraphQL String (UTF-8). + """ + String + """ + Serialize and deserialize a scalar as a GraphQL Int (32-bit signed integer). + """ + Int + """ + Serialize and deserialize a scalar as a GraphQL Float (IEEE 754 double-precision floating-point). + """ + Float + """ + Serialize and deserialize a scalar as a GraphQL Boolean. + """ + Boolean + """ + Serialize and deserialize a scalar as Argo BYTES: a variable-length length-prefixed byte array. + """ + BYTES + """ + Serialize and deserialize a scalar as Argo FIXED: a fixed-length byte array. + """ + FIXED + """ + Serialize and deserialize a scalar as Argo DESC: a flexible self-describing binary format (somewhat like JSON). + """ + DESC +} + +""" +Specifies how to serialize and deserialize this scalar. This is necessary for custom scalars to work with Argo serialization. Adding, changing, or removing this directive is typically a breaking change. +""" +directive @ArgoCodec( + """ + The codec to use to serialize and deserialize this scalar. + """ + codec: ArgoCodecType!, + """ + For the FIXED codec only: the length of the encoded value in bytes. Required for FIXED, and invalid for all other codecs. + """ + fixedLength: Int +) on SCALAR | ENUM + +""" +Deduplicate values of this type. Adding or removing this directive is typically a breaking change. +""" +directive @ArgoDeduplicate( + """ + Should values of this type be deduplicated? + """ + deduplicate: Boolean! = true +) on SCALAR | ENUM + schema { query: Query } @@ -45,7 +103,11 @@ type OtherObjectProperties { type Query { hero: Character + json: JSON + requiredJson: JSON! root: Child x: Int! y: Int } + +scalar JSON @ArgoCodec(codec: DESC)