Skip to content

Commit

Permalink
Support DESC as described in Argo 1.2.0
Browse files Browse the repository at this point in the history
  • Loading branch information
potatosalad committed Apr 28, 2024
1 parent b2ad49a commit 6f26826
Show file tree
Hide file tree
Showing 27 changed files with 477 additions and 116 deletions.
2 changes: 1 addition & 1 deletion apps/argo/src/argo.app.src
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 3 additions & 1 deletion apps/argo/src/argo_typer.erl
Original file line number Diff line number Diff line change
Expand Up @@ -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 ->
Expand Down
38 changes: 30 additions & 8 deletions apps/argo/src/graphql/argo_graphql_directive_definition.erl
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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),
Expand Down
47 changes: 39 additions & 8 deletions apps/argo/src/graphql/argo_graphql_type_definition.erl
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down
5 changes: 4 additions & 1 deletion apps/argo/src/value/argo_json_scalar_decoder.erl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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([
Expand Down
8 changes: 7 additions & 1 deletion apps/argo/src/value/argo_json_scalar_decoder_base64.erl
Original file line number Diff line number Diff line change
Expand Up @@ -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}};
Expand All @@ -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.
Expand Down
3 changes: 3 additions & 0 deletions apps/argo/src/value/argo_json_scalar_encoder.erl
Original file line number Diff line number Diff line change
Expand Up @@ -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().

Expand Down
8 changes: 6 additions & 2 deletions apps/argo/src/value/argo_json_scalar_encoder_base64.erl
Original file line number Diff line number Diff line change
Expand Up @@ -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)};
Expand All @@ -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.

%%%=============================================================================
Expand Down
18 changes: 17 additions & 1 deletion apps/argo/src/value/argo_json_value_decoder.erl
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@
-export([
new/0,
new/2,
decode_wire_type/3
decode_wire_type/3,
decode_desc_wire_type/2
]).

%% Errors API
Expand Down Expand Up @@ -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} ->
Expand Down Expand Up @@ -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} ->
Expand Down Expand Up @@ -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.

Expand All @@ -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) ->
Expand Down
5 changes: 4 additions & 1 deletion apps/argo/src/value/argo_json_value_encoder.erl
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@
-export([
new/0,
new/2,
encode_value/2
encode_value/2,
encode_desc_value/2
]).

%% Types
Expand Down Expand Up @@ -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 =
Expand Down
15 changes: 15 additions & 0 deletions apps/argo/src/value/argo_scalar_value.erl
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
-export([
boolean/1,
bytes/1,
desc/1,
fixed/1,
float64/1,
string/1,
Expand All @@ -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,
Expand All @@ -49,6 +51,7 @@
-type inner() ::
{boolean, boolean()}
| {bytes, binary()}
| {desc, argo_desc_value:t()}
| {fixed, binary()}
| {float64, float()}
| {string, unicode:unicode_binary()}
Expand All @@ -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}}.

Expand All @@ -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.

Expand All @@ -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, _}}) ->
Expand Down
Loading

0 comments on commit 6f26826

Please sign in to comment.