From d8b8431696c148662b008c04faa4ae9e01d14dd6 Mon Sep 17 00:00:00 2001 From: Andrew Bennett Date: Wed, 17 Apr 2024 18:04:45 -0500 Subject: [PATCH] Various fixes for 1.1.4 Argo spec compatibility. --- Makefile | 14 ++ apps/argo/include/argo_label.hrl | 2 + apps/argo/include/argo_value.hrl | 2 +- apps/argo/include/argo_wire_type.hrl | 9 +- apps/argo/src/argo.erl | 117 +++++++++++--- apps/argo/src/argo_debug_type.erl | 41 +++++ apps/argo/src/argo_header.erl | 7 +- apps/argo/src/argo_typer.erl | 8 +- apps/argo/src/core/argo_core_reader.erl | 4 +- apps/argo/src/core/argo_core_writer.erl | 2 +- apps/argo/src/graphql/argo_graphql.erl | 58 +++---- .../graphql/argo_graphql_input_type_graph.erl | 44 ----- .../argo/src/graphql/argo_graphql_printer.erl | 25 +-- apps/argo/src/value/argo_error_value.erl | 14 +- .../src/value/argo_json_value_decoder.erl | 10 +- .../src/value/argo_json_value_encoder.erl | 10 +- apps/argo/src/value/argo_value.erl | 50 +++--- apps/argo/src/value/argo_value_decoder.erl | 113 +++++++++---- apps/argo/src/value/argo_value_encoder.erl | 97 ++++++----- apps/argo/src/value/argo_value_printer.erl | 84 +++++++--- apps/argo/src/wire/argo_error_wire_type.erl | 34 +++- .../src/wire/argo_extensions_wire_type.erl | 7 +- apps/argo/src/wire/argo_field_wire_type.erl | 12 +- .../src/wire/argo_json_wire_type_decoder.erl | 18 +-- .../src/wire/argo_json_wire_type_encoder.erl | 17 +- apps/argo/src/wire/argo_wire_type.erl | 152 +++++++++++++++--- apps/argo/src/wire/argo_wire_type_decoder.erl | 3 +- apps/argo/src/wire/argo_wire_type_encoder.erl | 9 +- apps/argo/src/wire/argo_wire_type_printer.erl | 68 +++++--- apps/argo/src/wire/argo_wire_type_store.erl | 75 ++++++--- .../src/wire/argo_wire_type_store_entry.erl | 41 +++++ apps/argo_test/src/proper_argo.erl | 28 +++- apps/argo_test/src/proper_argo_typer.erl | 2 +- .../argo_graphql_executable_document_prop.erl | 4 +- .../argo_graphql_language_prop.erl | 8 +- .../argo_graphql_service_document_prop.erl | 4 +- .../src/property_test/argo_typer_prop.erl | 8 +- .../src/property_test/argo_value_prop.erl | 4 +- .../src/property_test/argo_wire_type_prop.erl | 4 +- apps/argo_test/test/argo_typer_SUITE.erl | 8 +- .../executable_document.graphql | 5 + .../service_document.graphql | 2 + 42 files changed, 834 insertions(+), 390 deletions(-) create mode 100644 apps/argo/src/argo_debug_type.erl create mode 100644 apps/argo/src/wire/argo_wire_type_store_entry.erl diff --git a/Makefile b/Makefile index 3053115..da5d0aa 100644 --- a/Makefile +++ b/Makefile @@ -123,3 +123,17 @@ distclean-erlfmt: format: $(ERLFMT) $(verbose) $(MAKE) erlfmt + +.PHONY: lint lint-dialyzer lint-eqwalizer lint-format lint-xref + +lint:: lint-format lint-eqwalizer lint-xref lint-dialyzer + +lint-dialyzer: + $(verbose) rebar3 dialyzer + +lint-eqwalizer: eqwalizer + +lint-format: erlfmt-check + +lint-xref: + $(verbose) rebar3 xref diff --git a/apps/argo/include/argo_label.hrl b/apps/argo/include/argo_label.hrl index f190ad4..d87509e 100644 --- a/apps/argo/include/argo_label.hrl +++ b/apps/argo/include/argo_label.hrl @@ -25,6 +25,8 @@ -define(ARGO_LABEL_MARKER_ERROR, -3). % ?ARGO_LABEL_MARKER_ERROR -define(ARGO_LABEL_MARKER_LOWEST_RESERVED_VALUE, -3). +% ?ARGO_LABEL_MARKER_LOWEST_RESERVED_VALUE - 1 +-define(ARGO_LABEL_MARKER_OFFSET_FACTOR, -4). % ?ARGO_LABEL_MARKER_NULL -define(ARGO_LABEL_SELF_DESCRIBING_MARKER_NULL, -1). diff --git a/apps/argo/include/argo_value.hrl b/apps/argo/include/argo_value.hrl index ef5c167..6bdd364 100644 --- a/apps/argo/include/argo_value.hrl +++ b/apps/argo/include/argo_value.hrl @@ -33,7 +33,7 @@ -record(argo_error_value, { message :: argo_desc_value:desc_string(), - location :: none | {some, [argo_location_value:t()]}, + locations :: none | {some, [argo_location_value:t()]}, path :: none | {some, argo_path_value:t()}, extensions :: none | {some, argo_extensions_value:t()} }). diff --git a/apps/argo/include/argo_wire_type.hrl b/apps/argo/include/argo_wire_type.hrl index 7e9ee3e..6993d27 100644 --- a/apps/argo/include/argo_wire_type.hrl +++ b/apps/argo/include/argo_wire_type.hrl @@ -35,7 +35,7 @@ -record(argo_field_wire_type, { name :: argo_types:name(), - type :: argo_wire_type:t(), + 'of' :: argo_wire_type:t(), omittable :: boolean() }). @@ -80,7 +80,12 @@ }). -record(argo_wire_type_store, { - types :: argo_index_map:t(argo_types:name(), argo_wire_type:t()) + types :: argo_index_map:t(argo_types:name(), argo_wire_type_store_entry:t()) +}). + +-record(argo_wire_type_store_entry, { + name :: argo_types:name(), + type :: argo_wire_type:t() }). -endif. diff --git a/apps/argo/src/argo.erl b/apps/argo/src/argo.erl index b4a1ce5..edc9805 100644 --- a/apps/argo/src/argo.erl +++ b/apps/argo/src/argo.erl @@ -21,71 +21,138 @@ -export([ display/1, display/2, + display/3, display_with_lines/1, display_with_lines/2, + display_with_lines/3, format/1, - format_with_lines/1 + format/2, + format_with_lines/1, + format_with_lines/2 +]). +%% Errors API +-export([ + format_error/2 ]). %% Macros +-define(DEFAULT_IO_DEVICE, standard_io). +-define(DEFAULT_OPTIONS, #{}). -define(is_record(X), (is_tuple((X)) andalso tuple_size((X)) > 0 andalso is_atom(element(1, (X))))). %%%============================================================================= %%% API functions %%%============================================================================= --spec display(dynamic()) -> ok. +-spec display(Type) -> ok when Type :: dynamic(). display(Type) when ?is_record(Type) -> - display(standard_io, Type). + display(?DEFAULT_IO_DEVICE, Type, ?DEFAULT_OPTIONS). --spec display(io:device(), dynamic()) -> ok. +-spec display(IoDevice | Type, Type | Options) -> ok when + IoDevice :: io:device(), Type :: dynamic(), Options :: dynamic(). display(IoDevice, Type) when not is_list(IoDevice) andalso ?is_record(Type) -> - Module = element(1, Type), - case Module of + display(IoDevice, Type, ?DEFAULT_OPTIONS); +display(Type, Options) when ?is_record(Type) andalso is_map(Options) -> + display(?DEFAULT_IO_DEVICE, Type, Options). + +-spec display(IoDevice, Type, Options) -> ok when IoDevice :: io:device(), Type :: dynamic(), Options :: dynamic(). +display(IoDevice, Type, Options) when not is_list(IoDevice) andalso ?is_record(Type) andalso is_map(Options) -> + case element(1, Type) of argo_value -> - argo_value:display(IoDevice, Type); + argo_value:display(IoDevice, Type, Options); argo_wire_type -> - argo_wire_type:display(IoDevice, Type); + argo_wire_type:display(IoDevice, Type, Options); argo_wire_type_store -> - argo_wire_type_store:display(IoDevice, Type); - _ -> + argo_wire_type_store:display(IoDevice, Type, Options); + Module -> case erlang:atom_to_binary(Module, utf8) of <<"argo_graphql_", _/bytes>> -> - argo_graphql:display(IoDevice, Type) + argo_graphql:display(IoDevice, Type, Options); + _ -> + error_with_info(badarg, [IoDevice, Type, Options], #{2 => display_not_supported}) end end. --spec display_with_lines(dynamic()) -> ok. +-spec display_with_lines(Type) -> ok when Type :: dynamic(). display_with_lines(Type) when ?is_record(Type) -> - display_with_lines(standard_io, Type). + display_with_lines(?DEFAULT_IO_DEVICE, Type, ?DEFAULT_OPTIONS). --spec display_with_lines(io:device(), dynamic()) -> ok. +-spec display_with_lines(IoDevice | Type, Type | Options) -> ok when + IoDevice :: io:device(), Type :: dynamic(), Options :: dynamic(). display_with_lines(IoDevice, Type) when not is_list(IoDevice) andalso ?is_record(Type) -> - Lines = format_with_lines(Type), - Printer1 = argo_graphql_printer:new_io_device(IoDevice), + display_with_lines(IoDevice, Type, ?DEFAULT_OPTIONS); +display_with_lines(Type, Options) when ?is_record(Type) andalso is_map(Options) -> + display_with_lines(?DEFAULT_IO_DEVICE, Type, Options). + +-spec display_with_lines(IoDevice, Type, Options) -> ok when + IoDevice :: io:device(), Type :: dynamic(), Options :: dynamic(). +display_with_lines(IoDevice, Type, Options) when + not is_list(IoDevice) andalso ?is_record(Type) andalso is_map(Options) +-> + Lines = format_with_lines(Type, Options), + Printer1 = argo_graphql_printer:new_io_device(IoDevice, Options), Printer2 = argo_graphql_printer:write(Printer1, "~ts", [Lines]), case argo_graphql_printer:finalize(Printer2) of ok -> ok end. --spec format(dynamic()) -> unicode:unicode_binary(). +-spec format(Type) -> Output when Type :: dynamic(), Output :: unicode:unicode_binary(). format(Type) when ?is_record(Type) -> - Module = element(1, Type), - case Module of + format(Type, ?DEFAULT_OPTIONS). + +-spec format(Type, Options) -> Output when Type :: dynamic(), Options :: dynamic(), Output :: unicode:unicode_binary(). +format(Type, Options) when ?is_record(Type) andalso is_map(Options) -> + case element(1, Type) of argo_value -> - argo_types:unicode_binary(argo_value:format(Type)); + argo_value:format(Type, Options); argo_wire_type -> - argo_types:unicode_binary(argo_wire_type:format(Type)); + argo_wire_type:format(Type, Options); argo_wire_type_store -> - argo_types:unicode_binary(argo_wire_type_store:format(Type)); - _ -> + argo_wire_type_store:format(Type, Options); + Module -> case erlang:atom_to_binary(Module, utf8) of <<"argo_graphql_", _/bytes>> -> - argo_graphql:format(Type) + argo_graphql:format(Type, Options); + _ -> + error_with_info(badarg, [Type, Options], #{1 => format_not_supported}) end end. -spec format_with_lines(dynamic()) -> unicode:unicode_binary(). format_with_lines(Type) when ?is_record(Type) -> - argo_types:format_with_lines(format(Type)). + format_with_lines(Type, ?DEFAULT_OPTIONS). + +-spec format_with_lines(Type, Options) -> Output when + Type :: dynamic(), Options :: dynamic(), Output :: unicode:unicode_binary(). +format_with_lines(Type, Options) when ?is_record(Type) andalso is_map(Options) -> + argo_types:format_with_lines(format(Type, Options)). + +%%%============================================================================= +%%% Errors API functions +%%%============================================================================= + +%% @private +-spec error_with_info(dynamic(), dynamic(), dynamic()) -> no_return(). +error_with_info(Reason, Args, Cause) -> + erlang:error(Reason, Args, [{error_info, #{module => ?MODULE, cause => Cause}}]). + +-spec format_error(dynamic(), dynamic()) -> dynamic(). +format_error(_Reason, [{_M, _F, _As, Info} | _]) -> + ErrorInfo = proplists:get_value(error_info, Info, #{}), + ErrorDescription1 = maps:get(cause, ErrorInfo), + ErrorDescription2 = maps:map(fun format_error_description/2, ErrorDescription1), + ErrorDescription2. + +%% @private +-spec format_error_description(dynamic(), dynamic()) -> dynamic(). +format_error_description(_Key, display_not_supported) -> + "display/3 is not supported for this Type"; +format_error_description(_Key, format_not_supported) -> + "format/2 is not supported for this Type"; +format_error_description(_Key, Value) -> + Value. + +%%%----------------------------------------------------------------------------- +%%% Internal functions +%%%----------------------------------------------------------------------------- diff --git a/apps/argo/src/argo_debug_type.erl b/apps/argo/src/argo_debug_type.erl new file mode 100644 index 0000000..4840570 --- /dev/null +++ b/apps/argo/src/argo_debug_type.erl @@ -0,0 +1,41 @@ +%%%----------------------------------------------------------------------------- +%%% Copyright (c) Meta Platforms, Inc. and affiliates. +%%% Copyright (c) WhatsApp LLC +%%% +%%% This source code is licensed under the MIT license found in the +%%% LICENSE.md file in the root directory of this source tree. +%%% +%%% @author Andrew Bennett +%%% @copyright (c) Meta Platforms, Inc. and affiliates. +%%% @doc +%%% +%%% @end +%%% Created : 17 Apr 2024 by Andrew Bennett +%%%----------------------------------------------------------------------------- +%%% % @format +-module(argo_debug_type). +-compile(warn_missing_spec_all). +-oncall("whatsapp_clr"). + +%% Behaviour +-callback display(IoDevice, Type, Options) -> ok when + IoDevice :: io:device(), Type :: type(), Options :: options(). +-callback format(Type) -> Output when + Type :: type(), Output :: output(). +-callback format(Type, Options) -> Output when + Type :: type(), Options :: options(), Output :: output(). + +-optional_callbacks([ + format/1 +]). + +%% Types +-type options() :: dynamic(). +-type output() :: unicode:unicode_binary(). +-type type() :: dynamic(). + +-export_type([ + options/0, + output/0, + type/0 +]). diff --git a/apps/argo/src/argo_header.erl b/apps/argo/src/argo_header.erl index faab3b7..ad17bea 100644 --- a/apps/argo/src/argo_header.erl +++ b/apps/argo/src/argo_header.erl @@ -17,12 +17,6 @@ -compile(warn_missing_spec_all). -oncall("whatsapp_clr"). --compile( - {inline, [ - error_with_info/3 - ]} -). - -include_lib("argo/include/argo_header.hrl"). %% Codec API @@ -302,6 +296,7 @@ set_user_flags(H0 = #argo_header{}, New) when is_bitstring(New) -> %%%============================================================================= %% @private +-compile({inline, [error_with_info/3]}). -spec error_with_info(dynamic(), dynamic(), dynamic()) -> no_return(). error_with_info(Reason, Args, Cause) -> erlang:error(Reason, Args, [{error_info, #{module => ?MODULE, cause => Cause}}]). diff --git a/apps/argo/src/argo_typer.erl b/apps/argo/src/argo_typer.erl index ce18794..2213214 100644 --- a/apps/argo/src/argo_typer.erl +++ b/apps/argo/src/argo_typer.erl @@ -162,8 +162,8 @@ path_to_wire_path(WireType = #argo_wire_type{inner = RecordWireType = #argo_reco is_binary(FieldName) andalso is_list(Path) -> case argo_record_wire_type:find_index_of(RecordWireType, FieldName) of - {ok, FieldIndex, _FieldWireType = #argo_field_wire_type{name = FieldName, type = Type}} -> - [FieldIndex | path_to_wire_path(Type, Path)]; + {ok, FieldIndex, _FieldWireType = #argo_field_wire_type{name = FieldName, 'of' = FieldOf}} -> + [FieldIndex | path_to_wire_path(FieldOf, Path)]; error -> error_with_info(badarg, [WireType, [FieldName | Path]], #{2 => {missing_field_name, FieldName}}) end; @@ -199,8 +199,8 @@ wire_path_to_path(WireType = #argo_wire_type{inner = RecordWireType = #argo_reco ?is_usize(FieldIndex) andalso is_list(WirePath) -> case argo_record_wire_type:find_index(RecordWireType, FieldIndex) of - {ok, _FieldWireType = #argo_field_wire_type{name = FieldName, type = Type}} -> - [FieldName | wire_path_to_path(Type, WirePath)]; + {ok, _FieldWireType = #argo_field_wire_type{name = FieldName, 'of' = FieldOf}} -> + [FieldName | wire_path_to_path(FieldOf, WirePath)]; error -> error_with_info(badarg, [WireType, [FieldIndex | WirePath]], #{2 => {missing_field_index, FieldIndex}}) end; diff --git a/apps/argo/src/core/argo_core_reader.erl b/apps/argo/src/core/argo_core_reader.erl index b781315..e8ec32b 100644 --- a/apps/argo/src/core/argo_core_reader.erl +++ b/apps/argo/src/core/argo_core_reader.erl @@ -116,9 +116,9 @@ read_label(R = #argo_core_reader{}) -> CoreReader :: t(), LabeledType :: argo_core:labeled_type(). read_labeled_type(R0 = #argo_core_reader{}) -> {R1, LengthOrBackreference} = read_varint(R0), - case LengthOrBackreference =< ?ARGO_LABEL_MARKER_LOWEST_RESERVED_VALUE of + case LengthOrBackreference < ?ARGO_LABEL_MARKER_LOWEST_RESERVED_VALUE of true -> - Backreference = abs(LengthOrBackreference - ?ARGO_LABEL_MARKER_LOWEST_RESERVED_VALUE), + Backreference = -LengthOrBackreference + ?ARGO_LABEL_MARKER_OFFSET_FACTOR, ok = backreference_sanity_check(Backreference), {R1, {backreference, Backreference}}; false -> diff --git a/apps/argo/src/core/argo_core_writer.erl b/apps/argo/src/core/argo_core_writer.erl index fea6b63..e613f00 100644 --- a/apps/argo/src/core/argo_core_writer.erl +++ b/apps/argo/src/core/argo_core_writer.erl @@ -112,7 +112,7 @@ write_label(W = #argo_core_writer{}, Label) -> CoreWriter :: t(), LabeledType :: argo_core:labeled_type(). write_labeled_type(W = #argo_core_writer{}, {backreference, Backreference}) when ?is_usize(Backreference) -> ok = backreference_sanity_check(Backreference), - Label = ?ARGO_LABEL_MARKER_LOWEST_RESERVED_VALUE - Backreference, + Label = ?ARGO_LABEL_MARKER_OFFSET_FACTOR - Backreference, write_label(W, Label); write_labeled_type(W = #argo_core_writer{}, {length, Length}) when ?is_usize(Length) -> write_length(W, Length). diff --git a/apps/argo/src/graphql/argo_graphql.erl b/apps/argo/src/graphql/argo_graphql.erl index c46aaf3..48b6faf 100644 --- a/apps/argo/src/graphql/argo_graphql.erl +++ b/apps/argo/src/graphql/argo_graphql.erl @@ -17,16 +17,19 @@ -compile(warn_missing_spec_all). -oncall("whatsapp_clr"). +-behaviour(argo_debug_type). + -include_lib("argo/include/argo_graphql.hrl"). -%% API +%% argo_debug_type callbacks -export([ - display/1, - display/2, - display_with_lines/1, - display_with_lines/2, + display/3, format/1, - format_with_lines/1, + format/2 +]). + +%% API +-export([ xform/3 ]). @@ -44,53 +47,42 @@ ]). %% Macros +-define(DEFAULT_OPTIONS, #{}). -define(is_record(X), (is_tuple((X)) andalso tuple_size((X)) > 0 andalso is_atom(element(1, (X))))). %%%============================================================================= -%%% API functions +%%% argo_debug_type callbacks %%%============================================================================= --spec display(dynamic()) -> ok. -display(Type) when ?is_record(Type) -> - display(standard_io, Type). - --spec display(io:device(), dynamic()) -> ok. -display(IoDevice, Type) when not is_list(IoDevice) andalso ?is_record(Type) -> +-spec display(IoDevice, Type, Options) -> ok when + IoDevice :: io:device(), Type :: dynamic(), Options :: argo_graphql_printer:options(). +display(IoDevice, Type, Options) when not is_list(IoDevice) andalso ?is_record(Type) andalso is_map(Options) -> Module = element(1, Type), - Printer1 = argo_graphql_printer:new_io_device(IoDevice), + Printer1 = argo_graphql_printer:new_io_device(IoDevice, Options), Printer2 = Module:format(Printer1, Type), case argo_graphql_printer:finalize(Printer2) of ok -> ok end. --spec display_with_lines(dynamic()) -> ok. -display_with_lines(Type) when ?is_record(Type) -> - display_with_lines(standard_io, Type). - --spec display_with_lines(io:device(), dynamic()) -> ok. -display_with_lines(IoDevice, Type) when not is_list(IoDevice) andalso ?is_record(Type) -> - Lines = format_with_lines(Type), - Printer1 = argo_graphql_printer:new_io_device(IoDevice), - Printer2 = argo_graphql_printer:write(Printer1, "~ts", [Lines]), - case argo_graphql_printer:finalize(Printer2) of - ok -> - ok - end. - --spec format(dynamic()) -> unicode:unicode_binary(). +-spec format(Type) -> Output when Type :: dynamic(), Output :: unicode:unicode_binary(). format(Type) when ?is_record(Type) -> + format(Type, ?DEFAULT_OPTIONS). + +-spec format(Type, Options) -> Output when + Type :: dynamic(), Options :: argo_graphql_printer:options(), Output :: unicode:unicode_binary(). +format(Type, Options) when ?is_record(Type) andalso is_map(Options) -> Module = element(1, Type), - Printer1 = argo_graphql_printer:new_string(), + Printer1 = argo_graphql_printer:new_string(Options), Printer2 = Module:format(Printer1, Type), case argo_graphql_printer:finalize(Printer2) of Output when is_list(Output) -> argo_types:unicode_binary(Output) end. --spec format_with_lines(dynamic()) -> unicode:unicode_binary(). -format_with_lines(Type) when ?is_record(Type) -> - argo_types:format_with_lines(format(Type)). +%%%============================================================================= +%%% API functions +%%%============================================================================= -spec xform(TypeIn, AccIn, Fun) -> {TypeOut, AccOut} when TypeIn :: dynamic(), diff --git a/apps/argo/src/graphql/argo_graphql_input_type_graph.erl b/apps/argo/src/graphql/argo_graphql_input_type_graph.erl index 93c10e8..5d4d669 100644 --- a/apps/argo/src/graphql/argo_graphql_input_type_graph.erl +++ b/apps/argo/src/graphql/argo_graphql_input_type_graph.erl @@ -30,10 +30,6 @@ is_valid_dependency/3, merge/2 ]). -% %% argo_graphql_display callbacks -% -export([ -% format/2 -% ]). %% Errors API -export([ format_error/2 @@ -126,13 +122,6 @@ is_valid_dependency(#argo_graphql_input_type_graph{inputs = Graph}, InputTypeNam -> not has_path(Graph, DependencyTypeName, InputTypeName). -% is_valid_merge(InputTypeGraph = #argo_graphql_input_type_graph{}, ) - -% merge(InputTypeGraph = #argo_graphql_input_type_graph{}, #argo_graphql_input_type_graph{inputs = BInputs}) -> -% maps:fold(fun(InputTypeName, Dependencies, Acc) -> - -% end, InputTypeGraph, BInputs), - %% @private -spec has_path(G, U, V) -> HasPath when G :: inputs(), @@ -182,39 +171,6 @@ has_path(G, U, V, Visited1, Iterator1) -> end end. -% %%%============================================================================= -% %%% argo_graphql_display callbacks -% %%%============================================================================= - -% -spec format(Formatter1, Type :: t()) -> Formatter2 when -% Formatter1 :: argo_graphql_formatter:t(), Formatter2 :: argo_graphql_formatter:t(). -% format(Formatter1, #argo_graphql_input_type_graph{inputs = InputsMap}) -> -% case argo_index_map:size(InputsMap) of -% 0 -> -% Formatter2 = argo_graphql_formatter:write(Formatter1, " {}", []), -% Formatter2; -% _ -> -% Formatter2 = argo_graphql_formatter:write(Formatter1, " {", []), -% Formatter3 = argo_graphql_formatter:shift_right(Formatter2), -% Formatter4 = argo_index_map:foldl( -% fun(_Index, _InputName, InputValueDefinition, Formatter3_Acc1) -> -% Formatter3_Acc2 = argo_graphql_formatter:write(Formatter3_Acc1, "~n", []), -% Formatter3_Acc3 = argo_graphql_formatter:write_indent(Formatter3_Acc2), -% Formatter3_Acc4 = argo_graphql_input_value_definition:format( -% Formatter3_Acc3, InputValueDefinition -% ), -% Formatter3_Acc4 -% end, -% Formatter3, -% InputsMap -% ), -% Formatter5 = argo_graphql_formatter:write(Formatter4, "~n", []), -% Formatter6 = argo_graphql_formatter:shift_left(Formatter5), -% Formatter7 = argo_graphql_formatter:write_indent(Formatter6), -% Formatter8 = argo_graphql_formatter:write(Formatter7, "}", []), -% Formatter8 -% end. - %%%============================================================================= %%% Errors API functions %%%============================================================================= diff --git a/apps/argo/src/graphql/argo_graphql_printer.erl b/apps/argo/src/graphql/argo_graphql_printer.erl index 8603f47..af6e7b5 100644 --- a/apps/argo/src/graphql/argo_graphql_printer.erl +++ b/apps/argo/src/graphql/argo_graphql_printer.erl @@ -21,8 +21,8 @@ %% API -export([ - new_io_device/1, - new_string/0, + new_io_device/2, + new_string/1, finalize/1 ]). %% argo_graphql_formatter callbacks @@ -36,13 +36,18 @@ %% Records -record(argo_graphql_printer, { depth = 0 :: non_neg_integer(), - output = [] :: iolist() | io:device() + output = [] :: iolist() | io:device(), + strict = false :: boolean() }). %% Types +-type options() :: #{ + strict => boolean() +}. -type t() :: #argo_graphql_printer{}. -export_type([ + options/0, t/0 ]). @@ -50,13 +55,15 @@ %%% API functions %%%============================================================================= --spec new_io_device(IoDevice) -> Printer when IoDevice :: io:device(), Printer :: t(). -new_io_device(IoDevice) when not is_list(IoDevice) -> - #argo_graphql_printer{depth = 0, output = IoDevice}. +-spec new_io_device(IoDevice, Options) -> Printer when IoDevice :: io:device(), Options :: options(), Printer :: t(). +new_io_device(IoDevice, Options) when not is_list(IoDevice) andalso is_map(Options) -> + Strict = maps:get(strict, Options, false), + #argo_graphql_printer{depth = 0, output = IoDevice, strict = Strict}. --spec new_string() -> Printer when Printer :: t(). -new_string() -> - #argo_graphql_printer{depth = 0, output = []}. +-spec new_string(Options) -> Printer when Options :: options(), Printer :: t(). +new_string(Options) when is_map(Options) -> + Strict = maps:get(strict, Options, false), + #argo_graphql_printer{depth = 0, output = [], strict = Strict}. -spec finalize(Printer) -> ok | iolist() when Printer :: t(). finalize(#argo_graphql_printer{output = Output}) when is_list(Output) -> diff --git a/apps/argo/src/value/argo_error_value.erl b/apps/argo/src/value/argo_error_value.erl index 6a8c1bd..a832a90 100644 --- a/apps/argo/src/value/argo_error_value.erl +++ b/apps/argo/src/value/argo_error_value.erl @@ -43,23 +43,23 @@ %%% API functions %%%============================================================================= --spec new(Message, none | {some, Location}, none | {some, Path}, none | {some, Extensions}) -> ErrorValue when +-spec new(Message, none | {some, Locations}, none | {some, Path}, none | {some, Extensions}) -> ErrorValue when Message :: argo_desc_value:desc_string(), - Location :: [argo_location_value:t()], + Locations :: [argo_location_value:t()], Path :: argo_path_value:t(), Extensions :: argo_extensions_value:t(), ErrorValue :: t(). -new(Message, Location, Path, Extensions) when - is_binary(Message) andalso ?is_option_list(Location) andalso ?is_option_record(Path, argo_path_value) andalso +new(Message, Locations, Path, Extensions) when + is_binary(Message) andalso ?is_option_list(Locations) andalso ?is_option_record(Path, argo_path_value) andalso ?is_option_record(Extensions, argo_extensions_value) -> - #argo_error_value{message = Message, location = Location, path = Path, extensions = Extensions}. + #argo_error_value{message = Message, locations = Locations, path = Path, extensions = Extensions}. -spec present_fields_count(ErrorValue) -> non_neg_integer() when ErrorValue :: t(). -present_fields_count(#argo_error_value{location = Location, path = Path, extensions = Extensions}) -> +present_fields_count(#argo_error_value{locations = Locations, path = Path, extensions = Extensions}) -> Count1 = 1, Count2 = - case Location of + case Locations of none -> Count1; {some, _} -> diff --git a/apps/argo/src/value/argo_json_value_decoder.erl b/apps/argo/src/value/argo_json_value_decoder.erl index 30e339c..d30760e 100644 --- a/apps/argo/src/value/argo_json_value_decoder.erl +++ b/apps/argo/src/value/argo_json_value_decoder.erl @@ -331,7 +331,7 @@ decode_field_wire_type( case argo_json:object_find(Name, JsonObject) of {ok, JsonValue} -> {JsonValueDecoder2, Value} = decode_wire_type( - JsonValueDecoder1, FieldWireType#argo_field_wire_type.type, JsonValue + JsonValueDecoder1, FieldWireType#argo_field_wire_type.'of', JsonValue ), FieldValue = argo_field_value:required(FieldWireType, Value), {JsonValueDecoder2, FieldValue}; @@ -344,7 +344,7 @@ decode_field_wire_type( case argo_json:object_find(Name, JsonObject) of {ok, JsonValue} -> {JsonValueDecoder2, Value} = decode_wire_type( - JsonValueDecoder1, FieldWireType#argo_field_wire_type.type, JsonValue + JsonValueDecoder1, FieldWireType#argo_field_wire_type.'of', JsonValue ), FieldValue = argo_field_value:optional(FieldWireType, {some, Value}), {JsonValueDecoder2, FieldValue}; @@ -448,8 +448,8 @@ decode_error_wire_type(JsonValueDecoder1 = #argo_json_value_decoder{}, JsonObjec 2 => {required_object_key_missing, <<"message">>} }) end, - {JsonValueDecoder2, Location} = - case argo_json:object_find(<<"location">>, JsonObject) of + {JsonValueDecoder2, Locations} = + case argo_json:object_find(<<"locations">>, JsonObject) of {ok, LocationList} when is_list(LocationList) -> Dec1_1 = JsonValueDecoder1, {Dec1_2, LocationAcc} = lists:foldl( @@ -489,7 +489,7 @@ decode_error_wire_type(JsonValueDecoder1 = #argo_json_value_decoder{}, JsonObjec error -> {JsonValueDecoder3, none} end, - ErrorValue = argo_error_value:new(Message, Location, Path, Extensions), + ErrorValue = argo_error_value:new(Message, Locations, Path, Extensions), {JsonValueDecoder4, ErrorValue}; decode_error_wire_type(JsonValueDecoder1 = #argo_json_value_decoder{}, JsonValue) -> error_with_info(badarg, [JsonValueDecoder1, JsonValue], #{2 => {mismatch, expected_object, JsonValue}}). diff --git a/apps/argo/src/value/argo_json_value_encoder.erl b/apps/argo/src/value/argo_json_value_encoder.erl index bf60ec3..67eae1e 100644 --- a/apps/argo/src/value/argo_json_value_encoder.erl +++ b/apps/argo/src/value/argo_json_value_encoder.erl @@ -265,20 +265,20 @@ encode_error_value(JsonValueEncoder1 = #argo_json_value_encoder{}, ErrorValue = JsonObject1 = argo_index_map:new(), JsonObject2 = argo_index_map:put(<<"message">>, argo_json:string(ErrorValue#argo_error_value.message), JsonObject1), {JsonValueEncoder2, JsonObject3} = - case ErrorValue#argo_error_value.location of + case ErrorValue#argo_error_value.locations of none -> {JsonValueEncoder1, JsonObject2}; - {some, Location} -> + {some, Locations} -> Enc1_1 = JsonValueEncoder1, - {Enc1_2, JsonLocation} = lists:foldl( + {Enc1_2, JsonLocations} = lists:foldl( fun(LocationValue, {Enc1_Acc1, LocationAcc1}) -> {Enc1_Acc2, JsonLocation} = encode_location_value(Enc1_Acc1, LocationValue), {Enc1_Acc2, [JsonLocation | LocationAcc1]} end, {Enc1_1, []}, - Location + Locations ), - {Enc1_2, argo_index_map:put(<<"location">>, lists:reverse(JsonLocation), JsonObject2)} + {Enc1_2, argo_index_map:put(<<"locations">>, lists:reverse(JsonLocations), JsonObject2)} end, {JsonValueEncoder3, JsonObject4} = case ErrorValue#argo_error_value.path of diff --git a/apps/argo/src/value/argo_value.erl b/apps/argo/src/value/argo_value.erl index 0044eb4..2fd4508 100644 --- a/apps/argo/src/value/argo_value.erl +++ b/apps/argo/src/value/argo_value.erl @@ -17,16 +17,20 @@ -compile(warn_missing_spec_all). -oncall("whatsapp_clr"). +-behaviour(argo_debug_type). + -include_lib("argo/include/argo_header.hrl"). -include_lib("argo/include/argo_value.hrl"). -include_lib("argo/include/argo_wire_type.hrl"). +%% argo_debug_type callbacks +-export([ + display/3, + format/2 +]). + %% Codec API -export([ - display/1, - display/2, - format/1, - format_with_lines/1, from_json/2, from_json/4, from_reader/2, @@ -93,34 +97,32 @@ ]). %%%============================================================================= -%%% Codec API functions +%%% argo_debug_type callbacks %%%============================================================================= --spec display(Value) -> ok when Value :: t(). -display(Value = #argo_value{}) -> - display(standard_io, Value). - --spec display(IoDevice, Value) -> ok when IoDevice :: io:device(), Value :: t(). -display(IoDevice, Value = #argo_value{}) when not is_list(IoDevice) -> - Printer1 = argo_value_printer:new_io_device(IoDevice), +-spec display(IoDevice, Value, Options) -> ok when + IoDevice :: io:device(), Value :: t(), Options :: argo_value_printer:options(). +display(IoDevice, Value = #argo_value{}, Options) when not is_list(IoDevice) andalso is_map(Options) -> + Printer1 = argo_value_printer:new_io_device(IoDevice, Options), Printer2 = argo_value_printer:print_value(Printer1, Value), case argo_value_printer:finalize(Printer2) of ok -> ok end. --spec format(Value) -> Output when Value :: t(), Output :: iolist(). -format(Value = #argo_value{}) -> - Printer1 = argo_value_printer:new_string(), +-spec format(Value, Options) -> Output when + Value :: t(), Options :: argo_value_printer:options(), Output :: unicode:unicode_binary(). +format(Value = #argo_value{}, Options) when is_map(Options) -> + Printer1 = argo_value_printer:new_string(Options), Printer2 = argo_value_printer:print_value(Printer1, Value), case argo_value_printer:finalize(Printer2) of Output when is_list(Output) -> - Output + argo_types:unicode_binary(Output) end. --spec format_with_lines(Value) -> unicode:unicode_binary() when Value :: t(). -format_with_lines(Value = #argo_value{}) -> - argo_types:format_with_lines(format(Value)). +%%%============================================================================= +%%% Codec API functions +%%%============================================================================= -spec from_json(WireType, JsonValue) -> Value when WireType :: argo_wire_type:t(), JsonValue :: argo_json:json_value(), Value :: t(). @@ -355,11 +357,11 @@ xform(T1, Acc1, Fun) when is_function(Fun, 2) -> L =:= 'boolean' orelse L =:= 'string' orelse L =:= 'bytes' orelse L =:= 'int' orelse L =:= 'float' -> {T2, Acc2}; - #argo_error_value{location = OptionLocation1, path = OptionPath1, extensions = OptionExtensions1} -> - {OptionLocation2, Acc3} = - case OptionLocation1 of + #argo_error_value{locations = OptionLocations1, path = OptionPath1, extensions = OptionExtensions1} -> + {OptionLocations2, Acc3} = + case OptionLocations1 of none -> - {OptionLocation1, Acc2}; + {OptionLocations1, Acc2}; {some, LocationList1} -> A2_1 = Acc2, {LocationList2, A2_2} = lists:foldl( @@ -393,7 +395,7 @@ xform(T1, Acc1, Fun) when is_function(Fun, 2) -> {{some, Extensions2}, A4_2} end, T3 = T2#argo_error_value{ - location = OptionLocation2, path = OptionPath2, extensions = OptionExtensions2 + locations = OptionLocations2, path = OptionPath2, extensions = OptionExtensions2 }, {T3, Acc5}; #argo_extensions_value{inner = Extensions1} -> diff --git a/apps/argo/src/value/argo_value_decoder.erl b/apps/argo/src/value/argo_value_decoder.erl index 5bd9bc5..41366b7 100644 --- a/apps/argo/src/value/argo_value_decoder.erl +++ b/apps/argo/src/value/argo_value_decoder.erl @@ -24,8 +24,6 @@ ). -include_lib("argo/include/argo_common.hrl"). --include_lib("argo/include/argo_header.hrl"). --include_lib("argo/include/argo_index_map.hrl"). -include_lib("argo/include/argo_label.hrl"). -include_lib("argo/include/argo_message.hrl"). -include_lib("argo/include/argo_value.hrl"). @@ -70,7 +68,7 @@ decode_wire_type(ValueDecoder1 = #argo_value_decoder{wire_type = undefined}, Wir case WireType#argo_wire_type.inner of RecordWireType = #argo_record_wire_type{} -> case argo_record_wire_type:find(RecordWireType, <<"data">>) of - {ok, _FieldWireType = #argo_field_wire_type{type = DataWireType = #argo_wire_type{}}} -> + {ok, _FieldWireType = #argo_field_wire_type{'of' = DataWireType = #argo_wire_type{}}} -> decode_wire_type(ValueDecoder1, WireType, {some, DataWireType}); error -> RootWireType = WireType, @@ -196,22 +194,69 @@ decode_block_wire_type(ValueDecoder1 = #argo_value_decoder{}, BlockWireType = #a decode_nullable_wire_type( ValueDecoder1 = #argo_value_decoder{message = MessageDecoder1}, NullableWireType = #argo_nullable_wire_type{} ) -> - IsLabeled = argo_nullable_wire_type:is_labeled(NullableWireType), - {MessageDecoder2, NullableType} = argo_message_decoder:read_core_nullable_type(MessageDecoder1, IsLabeled), - ValueDecoder2 = ValueDecoder1#argo_value_decoder{message = MessageDecoder2}, - case NullableType of - null -> - NullableValue = argo_nullable_value:null(NullableWireType), - {ValueDecoder2, NullableValue}; - non_null -> - {ValueDecoder3, Value} = decode_wire_type(ValueDecoder2, NullableWireType#argo_nullable_wire_type.'of'), - NullableValue = argo_nullable_value:non_null(NullableWireType, Value), - {ValueDecoder3, NullableValue}; - error -> - NullableValue = argo_nullable_value:field_errors(NullableWireType, []), - {ValueDecoder2, NullableValue} + case argo_header:self_describing(MessageDecoder1#argo_message_decoder.header) of + false -> + IsLabeled = argo_nullable_wire_type:is_labeled(NullableWireType), + {MessageDecoder2, NullableType} = argo_message_decoder:read_core_nullable_type(MessageDecoder1, IsLabeled), + ValueDecoder2 = ValueDecoder1#argo_value_decoder{message = MessageDecoder2}, + case NullableType of + null -> + NullableValue = argo_nullable_value:null(NullableWireType), + {ValueDecoder2, NullableValue}; + non_null -> + {ValueDecoder3, Value} = decode_wire_type( + ValueDecoder2, NullableWireType#argo_nullable_wire_type.'of' + ), + NullableValue = argo_nullable_value:non_null(NullableWireType, Value), + {ValueDecoder3, NullableValue}; + error -> + case argo_header:out_of_band_field_errors(MessageDecoder2#argo_message_decoder.header) of + false -> + {MessageDecoder3, Length} = argo_message_decoder:read_core_length(MessageDecoder2), + ValueDecoder3 = ValueDecoder2#argo_value_decoder{message = MessageDecoder3}, + {ValueDecoder4, FieldErrors} = decode_nullable_wire_type_field_errors( + ValueDecoder3, Length, [] + ), + NullableValue = argo_nullable_value:field_errors(NullableWireType, FieldErrors), + {ValueDecoder4, NullableValue}; + true -> + NullableValue = argo_nullable_value:field_errors(NullableWireType, []), + {ValueDecoder2, NullableValue} + end + end; + true -> + {MessageDecoder2, NullableType} = argo_message_decoder:read_core_nullable_type(MessageDecoder1, true), + ValueDecoder2 = ValueDecoder1#argo_value_decoder{message = MessageDecoder2}, + case NullableType of + null -> + NullableValue = argo_nullable_value:null(NullableWireType), + {ValueDecoder2, NullableValue}; + non_null -> + {ValueDecoder3, Value} = decode_wire_type( + ValueDecoder2, NullableWireType#argo_nullable_wire_type.'of' + ), + NullableValue = argo_nullable_value:non_null(NullableWireType, Value), + {ValueDecoder3, NullableValue}; + error -> + error_with_info(badarg, [ValueDecoder1, NullableWireType], #{ + 1 => {invalid_nullable_type_for_self_describing_mode, NullableType} + }) + end end. +%% @private +-spec decode_nullable_wire_type_field_errors(ValueDecoder, Length, FieldErrors) -> {ValueDecoder, FieldErrors} when + ValueDecoder :: t(), + Length :: non_neg_integer(), + FieldErrors :: [argo_error_value:t()]. +decode_nullable_wire_type_field_errors(ValueDecoder1 = #argo_value_decoder{}, 0, FieldErrors) -> + {ValueDecoder1, lists:reverse(FieldErrors)}; +decode_nullable_wire_type_field_errors( + ValueDecoder1 = #argo_value_decoder{}, Length, FieldErrors +) when is_integer(Length) andalso Length > 0 -> + {ValueDecoder2, ErrorValue} = decode_error_wire_type(ValueDecoder1), + decode_nullable_wire_type_field_errors(ValueDecoder2, Length - 1, [ErrorValue | FieldErrors]). + %% @private -spec decode_array_wire_type(ValueDecoder, ArrayWireType) -> {ValueDecoder, ArrayValue} when ValueDecoder :: t(), ArrayWireType :: argo_array_wire_type:t(), ArrayValue :: argo_array_value:t(). @@ -280,11 +325,11 @@ decode_field_wire_type( ) -> case FieldWireType#argo_field_wire_type.omittable of false -> - {ValueDecoder2, Value} = decode_wire_type(ValueDecoder1, FieldWireType#argo_field_wire_type.type), + {ValueDecoder2, Value} = decode_wire_type(ValueDecoder1, FieldWireType#argo_field_wire_type.'of'), FieldValue = argo_field_value:required(FieldWireType, Value), {ValueDecoder2, FieldValue}; true -> - IsLabeled = argo_wire_type:is_labeled(FieldWireType#argo_field_wire_type.type), + IsLabeled = argo_wire_type:is_labeled(FieldWireType#argo_field_wire_type.'of'), {MessageDecoder2, OmittableType} = argo_message_decoder:read_core_omittable_type( MessageDecoder1, IsLabeled ), @@ -296,7 +341,7 @@ decode_field_wire_type( non_null -> ValueDecoder2 = ValueDecoder1#argo_value_decoder{message = MessageDecoder2}, {ValueDecoder3, Value} = decode_wire_type( - ValueDecoder2, FieldWireType#argo_field_wire_type.type + ValueDecoder2, FieldWireType#argo_field_wire_type.'of' ), FieldValue = argo_field_value:optional(FieldWireType, {some, Value}), {ValueDecoder3, FieldValue} @@ -395,12 +440,12 @@ decode_error_wire_type(ValueDecoder1 = #argo_value_decoder{message = MessageDeco of false -> {MessageDecoder2, Message} = argo_message_decoder:decode_block_string(MessageDecoder1), - {MessageDecoder3, LocationOmittableType} = argo_message_decoder:read_core_omittable_type( + {MessageDecoder3, LocationsOmittableType} = argo_message_decoder:read_core_omittable_type( MessageDecoder2, true ), ValueDecoder2 = ValueDecoder1#argo_value_decoder{message = MessageDecoder3}, {ValueDecoder3 = #argo_value_decoder{message = MessageDecoder4}, Location} = - case LocationOmittableType of + case LocationsOmittableType of absent -> {ValueDecoder2, none}; non_null -> @@ -535,10 +580,10 @@ decode_self_describing_error_wire_type_fields(ValueDecoder1 = #argo_value_decode error -> error_with_info(badarg, [ValueDecoder1, 0, Map1], #{1 => {missing_required_field, <<"message">>}}) end, - Location = - case maps:find(<<"location">>, Map1) of - {ok, LocationValue} when is_list(LocationValue) -> - {some, LocationValue}; + Locations = + case maps:find(<<"locations">>, Map1) of + {ok, LocationsValue} when is_list(LocationsValue) -> + {some, LocationsValue}; error -> none end, @@ -556,7 +601,7 @@ decode_self_describing_error_wire_type_fields(ValueDecoder1 = #argo_value_decode error -> none end, - ErrorValue = argo_error_value:new(Message, Location, Path, Extensions), + ErrorValue = argo_error_value:new(Message, Locations, Path, Extensions), {ValueDecoder1, ErrorValue}; decode_self_describing_error_wire_type_fields( ValueDecoder1 = #argo_value_decoder{message = MessageDecoder1}, Length, Map1 @@ -576,7 +621,7 @@ decode_self_describing_error_wire_type_fields( _ -> error_with_info(badarg, [ValueDecoder1, Length, Map1], #{1 => {invalid_string_label, MessageLabel}}) end; - <<"location">> -> + <<"locations">> -> {MessageDecoder3, LocationLabel} = argo_message_decoder:read_core_label(MessageDecoder2), case LocationLabel of ?ARGO_LABEL_SELF_DESCRIBING_MARKER_LIST -> @@ -676,7 +721,7 @@ decode_self_describing_error_wire_type_location_fields( Map2 = maps:put(Key, LineValue, Map1), decode_self_describing_error_wire_type_location_fields(ValueDecoder2, Length - 1, Map2); _ -> - error_with_info(badarg, [ValueDecoder1, Length, Map1], #{1 => {invalid_array_label, LineLabel}}) + error_with_info(badarg, [ValueDecoder1, Length, Map1], #{1 => {invalid_varint_label, LineLabel}}) end; <<"column">> -> {MessageDecoder3, ColumnLabel} = argo_message_decoder:read_core_label(MessageDecoder2), @@ -687,7 +732,7 @@ decode_self_describing_error_wire_type_location_fields( Map2 = maps:put(Key, ColumnValue, Map1), decode_self_describing_error_wire_type_location_fields(ValueDecoder2, Length - 1, Map2); _ -> - error_with_info(badarg, [ValueDecoder1, Length, Map1], #{1 => {invalid_array_label, ColumnLabel}}) + error_with_info(badarg, [ValueDecoder1, Length, Map1], #{1 => {invalid_varint_label, ColumnLabel}}) end; _ -> error_with_info(badarg, [ValueDecoder1, Length, Map1], #{1 => {unknown_field, Key}}) @@ -847,7 +892,7 @@ decode_self_describing_record_wire_type_fields( case argo_index_map:find(FieldName, RecordWireType#argo_record_wire_type.fields) of {ok, FieldWireType = #argo_field_wire_type{}} -> ValueDecoder2 = ValueDecoder1#argo_value_decoder{message = MessageDecoder2}, - {ValueDecoder3, Value} = decode_wire_type(ValueDecoder2, FieldWireType#argo_field_wire_type.type), + {ValueDecoder3, Value} = decode_wire_type(ValueDecoder2, FieldWireType#argo_field_wire_type.'of'), case FieldWireType#argo_field_wire_type.omittable of false -> Map2 = maps:put(FieldName, argo_field_value:required(FieldWireType, Value), Map1), @@ -901,9 +946,9 @@ format_error_description(_Key, {invalid_fixed_label, Actual}) -> io_lib:format("invalid FIXED label, expected ~w, but was ~w", [?ARGO_LABEL_SELF_DESCRIBING_MARKER_BYTES, Actual]); format_error_description(_Key, {invalid_float64_label, Actual}) -> io_lib:format("invalid FLOAT64 label, expected ~w, but was ~w", [?ARGO_LABEL_SELF_DESCRIBING_MARKER_FLOAT, Actual]); -format_error_description(_Key, {invalid_nullable_label, Actual}) -> - io_lib:format("invalid NULLABLE label, expected ~w or ~w, but was ~w", [ - ?ARGO_LABEL_MARKER_NULL, ?ARGO_LABEL_MARKER_NON_NULL, Actual +format_error_description(_Key, {invalid_nullable_type_for_self_describing_mode, Actual}) -> + io_lib:format("invalid NULLABLE type for self-describing, expected null or non_null, but was ~w", [ + Actual ]); format_error_description(_Key, {invalid_record_label, Actual}) -> io_lib:format("invalid RECORD label, expected ~w, but was ~w", [?ARGO_LABEL_SELF_DESCRIBING_MARKER_OBJECT, Actual]); diff --git a/apps/argo/src/value/argo_value_encoder.erl b/apps/argo/src/value/argo_value_encoder.erl index cc64b6a..26673f9 100644 --- a/apps/argo/src/value/argo_value_encoder.erl +++ b/apps/argo/src/value/argo_value_encoder.erl @@ -19,7 +19,6 @@ -include_lib("argo/include/argo_common.hrl"). -include_lib("argo/include/argo_header.hrl"). --include_lib("argo/include/argo_index_map.hrl"). -include_lib("argo/include/argo_label.hrl"). -include_lib("argo/include/argo_message.hrl"). -include_lib("argo/include/argo_value.hrl"). @@ -63,7 +62,7 @@ encode_value(ValueEncoder1 = #argo_value_encoder{wire_type = undefined}, Value = case argo_record_value:find(RecordValue, <<"data">>) of {ok, _FieldValue = #argo_field_value{ - wire_type = #argo_field_wire_type{type = DataWireType = #argo_wire_type{}} + wire_type = #argo_field_wire_type{'of' = DataWireType = #argo_wire_type{}} }} -> encode_value(ValueEncoder1, Value, {some, DataWireType}); error -> @@ -138,36 +137,56 @@ encode_block_value(ValueEncoder1 = #argo_value_encoder{}, BlockValue = #argo_blo encode_nullable_value( ValueEncoder1 = #argo_value_encoder{message = MessageEncoder1}, NullableValue = #argo_nullable_value{} ) -> - IsLabeled = argo_nullable_value:is_labeled(NullableValue), - case NullableValue#argo_nullable_value.inner of - null -> - MessageEncoder2 = argo_message_encoder:write_core_nullable_type(MessageEncoder1, null, IsLabeled), - ValueEncoder2 = ValueEncoder1#argo_value_encoder{message = MessageEncoder2}, - ValueEncoder2; - {non_null, Value = #argo_value{}} -> - MessageEncoder2 = argo_message_encoder:write_core_nullable_type(MessageEncoder1, non_null, IsLabeled), - ValueEncoder2 = ValueEncoder1#argo_value_encoder{message = MessageEncoder2}, - ValueEncoder3 = encode_value(ValueEncoder2, Value), - ValueEncoder3; - {field_errors, FieldErrors} when is_list(FieldErrors) -> - case argo_header:out_of_band_field_errors(MessageEncoder1#argo_message_encoder.header) of - false -> - FieldErrorsLength = length(FieldErrors), - MessageEncoder2 = argo_message_encoder:write_core_nullable_type(MessageEncoder1, error, IsLabeled), - MessageEncoder3 = argo_message_encoder:write_core_length(MessageEncoder2, FieldErrorsLength), - ValueEncoder2 = ValueEncoder1#argo_value_encoder{message = MessageEncoder3}, - ValueEncoder3 = lists:foldl( - fun(ErrorValue = #argo_error_value{}, ValueEncoderAcc) -> - encode_error_value(ValueEncoderAcc, ErrorValue) - end, - ValueEncoder2, - FieldErrors + case argo_header:self_describing(MessageEncoder1#argo_message_encoder.header) of + false -> + IsLabeled = argo_nullable_value:is_labeled(NullableValue), + case NullableValue#argo_nullable_value.inner of + null -> + MessageEncoder2 = argo_message_encoder:write_core_nullable_type(MessageEncoder1, null, IsLabeled), + ValueEncoder2 = ValueEncoder1#argo_value_encoder{message = MessageEncoder2}, + ValueEncoder2; + {non_null, Value = #argo_value{}} -> + MessageEncoder2 = argo_message_encoder:write_core_nullable_type( + MessageEncoder1, non_null, IsLabeled ), - ValueEncoder3; - true -> - MessageEncoder2 = argo_message_encoder:write_core_nullable_type(MessageEncoder1, error, IsLabeled), ValueEncoder2 = ValueEncoder1#argo_value_encoder{message = MessageEncoder2}, - ValueEncoder2 + ValueEncoder3 = encode_value(ValueEncoder2, Value), + ValueEncoder3; + {field_errors, FieldErrors} when is_list(FieldErrors) -> + case argo_header:out_of_band_field_errors(MessageEncoder1#argo_message_encoder.header) of + false -> + FieldErrorsLength = length(FieldErrors), + MessageEncoder2 = argo_message_encoder:write_core_nullable_type( + MessageEncoder1, error, IsLabeled + ), + MessageEncoder3 = argo_message_encoder:write_core_length( + MessageEncoder2, FieldErrorsLength + ), + ValueEncoder2 = ValueEncoder1#argo_value_encoder{message = MessageEncoder3}, + ValueEncoder3 = lists:foldl( + fun(ErrorValue = #argo_error_value{}, ValueEncoderAcc) -> + encode_error_value(ValueEncoderAcc, ErrorValue) + end, + ValueEncoder2, + FieldErrors + ), + ValueEncoder3; + true -> + MessageEncoder2 = argo_message_encoder:write_core_nullable_type( + MessageEncoder1, error, IsLabeled + ), + ValueEncoder2 = ValueEncoder1#argo_value_encoder{message = MessageEncoder2}, + ValueEncoder2 + end + end; + true -> + case NullableValue#argo_nullable_value.inner of + null -> + encode_desc_value(ValueEncoder1, argo_desc_value:null()); + {non_null, Value = #argo_value{}} -> + encode_value(ValueEncoder1, Value); + {field_errors, FieldErrors} when is_list(FieldErrors) -> + encode_desc_value(ValueEncoder1, argo_desc_value:null()) end end. @@ -328,13 +347,13 @@ encode_error_value(ValueEncoder1 = #argo_value_encoder{message = MessageEncoder1 MessageEncoder1, ErrorValue#argo_error_value.message ), MessageEncoder3 = - case ErrorValue#argo_error_value.location of + case ErrorValue#argo_error_value.locations of none -> argo_message_encoder:write_core_omittable_type(MessageEncoder2, absent, true); - {some, Location} when is_list(Location) -> + {some, Locations} when is_list(Locations) -> ME2_1 = MessageEncoder2, ME2_2 = argo_message_encoder:write_core_omittable_type(ME2_1, non_null, true), - ME2_3 = argo_message_encoder:write_core_length(ME2_2, length(Location)), + ME2_3 = argo_message_encoder:write_core_length(ME2_2, length(Locations)), ME2_4 = lists:foldl( fun(LocationValue = #argo_location_value{}, ME2_3_Acc1) -> ME2_3_Acc2 = argo_message_encoder:encode_block_varint( @@ -346,7 +365,7 @@ encode_error_value(ValueEncoder1 = #argo_value_encoder{message = MessageEncoder1 ME2_3_Acc3 end, ME2_3, - Location + Locations ), ME2_4 end, @@ -457,14 +476,14 @@ encode_self_describing_error_value( MessageEncoder5 = argo_message_encoder:write_core_label(MessageEncoder4, ?ARGO_LABEL_SELF_DESCRIBING_MARKER_STRING), MessageEncoder6 = argo_message_encoder:encode_block_string(MessageEncoder5, ErrorValue#argo_error_value.message), MessageEncoder7 = - case ErrorValue#argo_error_value.location of + case ErrorValue#argo_error_value.locations of none -> MessageEncoder6; - {some, Location} when is_list(Location) -> + {some, Locations} when is_list(Locations) -> ME6_1 = MessageEncoder6, - ME6_2 = argo_message_encoder:encode_block_string(ME6_1, <<"location">>), + ME6_2 = argo_message_encoder:encode_block_string(ME6_1, <<"locations">>), ME6_3 = argo_message_encoder:write_core_label(ME6_2, ?ARGO_LABEL_SELF_DESCRIBING_MARKER_LIST), - ME6_4 = argo_message_encoder:write_core_length(ME6_3, length(Location)), + ME6_4 = argo_message_encoder:write_core_length(ME6_3, length(Locations)), ME6_5 = lists:foldl( fun(LocationValue = #argo_location_value{}, ME6_4_Acc1) -> ME6_4_Acc2 = argo_message_encoder:write_core_label( @@ -488,7 +507,7 @@ encode_self_describing_error_value( ME6_4_Acc9 end, ME6_4, - Location + Locations ), ME6_5 end, diff --git a/apps/argo/src/value/argo_value_printer.erl b/apps/argo/src/value/argo_value_printer.erl index 41536ca..9e3c9ad 100644 --- a/apps/argo/src/value/argo_value_printer.erl +++ b/apps/argo/src/value/argo_value_printer.erl @@ -17,17 +17,14 @@ -compile(warn_missing_spec_all). -oncall("whatsapp_clr"). --include_lib("argo/include/argo_header.hrl"). -include_lib("argo/include/argo_index_map.hrl"). --include_lib("argo/include/argo_label.hrl"). --include_lib("argo/include/argo_message.hrl"). -include_lib("argo/include/argo_value.hrl"). -include_lib("argo/include/argo_wire_type.hrl"). %% API -export([ - new_io_device/1, - new_string/0, + new_io_device/2, + new_string/1, finalize/1, print_value/2 ]). @@ -35,13 +32,18 @@ %% Records -record(argo_value_printer, { depth = 0 :: non_neg_integer(), - output = [] :: iolist() | io:device() + output = [] :: iolist() | io:device(), + strict = false :: boolean() }). %% Types +-type options() :: #{ + strict => boolean() +}. -type t() :: #argo_value_printer{}. -export_type([ + options/0, t/0 ]). @@ -49,13 +51,15 @@ %%% API functions %%%============================================================================= --spec new_io_device(IoDevice) -> Printer when IoDevice :: io:device(), Printer :: t(). -new_io_device(IoDevice) when not is_list(IoDevice) -> - #argo_value_printer{depth = 0, output = IoDevice}. +-spec new_io_device(IoDevice, Options) -> Printer when IoDevice :: io:device(), Options :: options(), Printer :: t(). +new_io_device(IoDevice, Options) when is_map(Options) -> + Strict = maps:get(strict, Options, false), + #argo_value_printer{depth = 0, output = IoDevice, strict = Strict}. --spec new_string() -> Printer when Printer :: t(). -new_string() -> - #argo_value_printer{depth = 0, output = []}. +-spec new_string(Options) -> Printer when Options :: options(), Printer :: t(). +new_string(Options) when is_map(Options) -> + Strict = maps:get(strict, Options, false), + #argo_value_printer{depth = 0, output = [], strict = Strict}. -spec finalize(Printer) -> ok | iolist() when Printer :: t(). finalize(#argo_value_printer{output = Output}) when is_list(Output) -> @@ -191,32 +195,41 @@ print_desc_value_object(Printer1 = #argo_value_printer{}, Object = #argo_index_m -spec print_error_value(Printer, ErrorValue) -> Printer when Printer :: t(), ErrorValue :: argo_error_value:t(). print_error_value(Printer1 = #argo_value_printer{}, ErrorValue = #argo_error_value{}) -> - Printer2 = write(Printer1, "ERROR({~n", []), + {ErrorLabelOpen, ErrorLabelClose, LocationLabelOpen, LocationLabelClose} = + case Printer1 of + #argo_value_printer{strict = false} -> + {"ERROR({", "})", "LOCATION({", "})"}; + #argo_value_printer{strict = true} -> + {"{", "}", "{", "}"} + end, + Printer2 = write(Printer1, "~ts~n", [ErrorLabelOpen]), Printer3 = Printer2#argo_value_printer{depth = Printer2#argo_value_printer.depth + 1}, Printer4 = indent(Printer3), Printer5 = write(Printer4, "message: ~0tp~n", [ErrorValue#argo_error_value.message]), Printer6 = - case ErrorValue#argo_error_value.location of + case ErrorValue#argo_error_value.locations of none -> Printer5; {some, []} -> P5_1 = Printer5, P5_2 = indent(P5_1), - P5_3 = write(P5_2, "location: []~n", []), + P5_3 = write(P5_2, "locations: []~n", []), P5_3; - {some, Location} -> + {some, Locations} -> P5_1 = Printer5, P5_2 = indent(P5_1), - P5_3 = write(P5_2, "location: [~n", []), + P5_3 = write(P5_2, "locations: [~n", []), P5_4 = P5_3#argo_value_printer{depth = P5_3#argo_value_printer.depth + 1}, P5_5 = lists:foldl( fun(#argo_location_value{line = Line, column = Column}, P5_4_Acc1) -> P5_4_Acc2 = indent(P5_4_Acc1), - P5_4_Acc3 = write(P5_4_Acc2, "LOCATION({line: ~0tp, column: ~0tp})~n", [Line, Column]), + P5_4_Acc3 = write(P5_4_Acc2, "~tsline: ~0tp, column: ~0tp~ts~n", [ + LocationLabelOpen, Line, Column, LocationLabelClose + ]), P5_4_Acc3 end, P5_4, - Location + Locations ), P5_6 = P5_5#argo_value_printer{depth = P5_5#argo_value_printer.depth - 1}, P5_7 = indent(P5_6), @@ -249,18 +262,25 @@ print_error_value(Printer1 = #argo_value_printer{}, ErrorValue = #argo_error_val end, Printer9 = Printer8#argo_value_printer{depth = Printer8#argo_value_printer.depth - 1}, Printer10 = indent(Printer9), - Printer11 = write(Printer10, "})", []), + Printer11 = write(Printer10, "~ts", [ErrorLabelClose]), Printer11. %% @private -spec print_extensions_value(Printer, ExtensionsValue) -> Printer when Printer :: t(), ExtensionsValue :: argo_extensions_value:t(). print_extensions_value(Printer1 = #argo_value_printer{}, _ExtensionsValue = #argo_extensions_value{inner = Extensions}) -> + ExtensionsLabel = + case Printer1 of + #argo_value_printer{strict = false} -> + "EXTENSIONS"; + #argo_value_printer{strict = true} -> + "DESC" + end, case argo_index_map:size(Extensions) of 0 -> - write(Printer1, "EXTENSIONS({})", []); + write(Printer1, "~ts({})", [ExtensionsLabel]); _ -> - Printer2 = write(Printer1, "EXTENSIONS({~n", []), + Printer2 = write(Printer1, "~ts({~n", [ExtensionsLabel]), Printer3 = argo_index_map:foldl( fun(_Index, Key, DescValue, PrinterAcc1) -> PrinterAcc2 = indent(PrinterAcc1), @@ -317,7 +337,25 @@ print_nullable_value(Printer1 = #argo_value_printer{}, NullableValue = #argo_nul Printer2 = write(Printer1, "NON_NULL(", []), Printer3 = print_value(Printer2, Value), Printer4 = write(Printer3, ")", []), - Printer4 + Printer4; + {field_errors, []} -> + write(Printer1, "FIELD_ERRORS([])", []); + {field_errors, ErrorValueList} -> + Printer2 = write(Printer1, "FIELD_ERRORS([~n", []), + Printer3 = lists:foldl( + fun(ErrorValue, PrinterAcc1) -> + PrinterAcc2 = indent(PrinterAcc1), + PrinterAcc3 = print_error_value(PrinterAcc2, ErrorValue), + PrinterAcc4 = write(PrinterAcc3, ",~n", []), + PrinterAcc4 + end, + Printer2#argo_value_printer{depth = Printer2#argo_value_printer.depth + 1}, + ErrorValueList + ), + Printer4 = Printer3#argo_value_printer{depth = Printer3#argo_value_printer.depth - 1}, + Printer5 = indent(Printer4), + Printer6 = write(Printer5, "])", []), + Printer6 end. %% @private diff --git a/apps/argo/src/wire/argo_error_wire_type.erl b/apps/argo/src/wire/argo_error_wire_type.erl index c634f14..27ba3c5 100644 --- a/apps/argo/src/wire/argo_error_wire_type.erl +++ b/apps/argo/src/wire/argo_error_wire_type.erl @@ -21,7 +21,8 @@ %% API -export([ - new/0 + new/0, + expand_wire_type/1 ]). %% Types @@ -38,3 +39,34 @@ -spec new() -> ErrorWireType when ErrorWireType :: t(). new() -> #argo_error_wire_type{}. + +-spec expand_wire_type(ErrorWireType) -> WireType when ErrorWireType :: t(), WireType :: argo_wire_type:t(). +expand_wire_type(#argo_error_wire_type{}) -> + % "message" Field + MessageWireType = argo_wire_type:block(argo_label:self_describing_blocks_string()), + MessageFieldWireType = argo_field_wire_type:new(<<"message">>, MessageWireType, false), + % "locations" Field + LineWireType = argo_wire_type:block(argo_label:self_describing_blocks_varint()), + ColumnWireType = argo_wire_type:block(argo_label:self_describing_blocks_varint()), + LineFieldWireType = argo_field_wire_type:new(<<"line">>, LineWireType, false), + ColumnFieldWireType = argo_field_wire_type:new(<<"column">>, ColumnWireType, false), + LocationRecordWireType1 = argo_record_wire_type:new(), + LocationRecordWireType2 = argo_record_wire_type:insert(LocationRecordWireType1, LineFieldWireType), + LocationRecordWireType3 = argo_record_wire_type:insert(LocationRecordWireType2, ColumnFieldWireType), + LocationWireType = argo_wire_type:record(LocationRecordWireType3), + LocationsWireType = argo_wire_type:array(argo_array_wire_type:new(LocationWireType)), + LocationsFieldWireType = argo_field_wire_type:new(<<"locations">>, LocationsWireType, true), + % "path" Field + PathWireType = argo_wire_type:path(), + PathFieldWireType = argo_field_wire_type:new(<<"path">>, PathWireType, true), + % "extensions" Field + ExtensionsWireType = argo_extensions_wire_type:expand_wire_type(argo_extensions_wire_type:new()), + ExtensionsFieldWireType = argo_field_wire_type:new(<<"extensions">>, ExtensionsWireType, true), + % "Error" Record Wire Type + RecordWireType1 = argo_record_wire_type:new(), + RecordWireType2 = argo_record_wire_type:insert(RecordWireType1, MessageFieldWireType), + RecordWireType3 = argo_record_wire_type:insert(RecordWireType2, LocationsFieldWireType), + RecordWireType4 = argo_record_wire_type:insert(RecordWireType3, PathFieldWireType), + RecordWireType5 = argo_record_wire_type:insert(RecordWireType4, ExtensionsFieldWireType), + WireType = argo_wire_type:record(RecordWireType5), + WireType. diff --git a/apps/argo/src/wire/argo_extensions_wire_type.erl b/apps/argo/src/wire/argo_extensions_wire_type.erl index 6770500..7bef7bf 100644 --- a/apps/argo/src/wire/argo_extensions_wire_type.erl +++ b/apps/argo/src/wire/argo_extensions_wire_type.erl @@ -21,7 +21,8 @@ %% API -export([ - new/0 + new/0, + expand_wire_type/1 ]). %% Types @@ -38,3 +39,7 @@ -spec new() -> ExtensionsWireType when ExtensionsWireType :: t(). new() -> #argo_extensions_wire_type{}. + +-spec expand_wire_type(ErrorWireType) -> WireType when ErrorWireType :: t(), WireType :: argo_wire_type:t(). +expand_wire_type(#argo_extensions_wire_type{}) -> + argo_wire_type:desc(). diff --git a/apps/argo/src/wire/argo_field_wire_type.erl b/apps/argo/src/wire/argo_field_wire_type.erl index 75319ae..3d2b99c 100644 --- a/apps/argo/src/wire/argo_field_wire_type.erl +++ b/apps/argo/src/wire/argo_field_wire_type.erl @@ -37,15 +37,15 @@ %%% API functions %%%============================================================================= --spec new(Name, Type, Omittable) -> FieldWireType when - Name :: argo_types:name(), Type :: argo_wire_type:t(), Omittable :: boolean(), FieldWireType :: t(). -new(Name, Type = #argo_wire_type{}, Omittable) when is_binary(Name) andalso is_boolean(Omittable) -> - #argo_field_wire_type{name = Name, type = Type, omittable = Omittable}. +-spec new(Name, Of, Omittable) -> FieldWireType when + Name :: argo_types:name(), Of :: argo_wire_type:t(), Omittable :: boolean(), FieldWireType :: t(). +new(Name, Of = #argo_wire_type{}, Omittable) when is_binary(Name) andalso is_boolean(Omittable) -> + #argo_field_wire_type{name = Name, 'of' = Of, omittable = Omittable}. -compile({inline, [is_labeled/1]}). -spec is_labeled(FieldWireType) -> boolean() when FieldWireType :: t(). -is_labeled(#argo_field_wire_type{type = Type}) -> - argo_wire_type:is_labeled(Type). +is_labeled(#argo_field_wire_type{'of' = Of}) -> + argo_wire_type:is_labeled(Of). -spec is_omittable(FieldWireType) -> boolean() when FieldWireType :: t(). is_omittable(#argo_field_wire_type{omittable = Omittable}) -> 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 22d122f..24a00cf 100644 --- a/apps/argo/src/wire/argo_json_wire_type_decoder.erl +++ b/apps/argo/src/wire/argo_json_wire_type_decoder.erl @@ -161,12 +161,10 @@ decode_wire_type_store(JsonWireTypeDecoder1 = #argo_json_wire_type_decoder{}, Js WireTypeStore1 = argo_wire_type_store:new(), {JsonWireTypeDecoder2, WireTypeStore2} = lists:foldl( fun(JsonField, {JsonWireTypeDecoder1_Acc1, WireTypeStore1_Acc1}) -> - {JsonWireTypeDecoder1_Acc2, TypeName, WireType} = decode_wire_type_store_entry( + {JsonWireTypeDecoder1_Acc2, WireTypeStoreEntry} = decode_wire_type_store_entry( JsonWireTypeDecoder1_Acc1, JsonField ), - WireTypeStore1_Acc2 = argo_wire_type_store:insert( - WireTypeStore1_Acc1, TypeName, WireType - ), + WireTypeStore1_Acc2 = argo_wire_type_store:insert(WireTypeStore1_Acc1, WireTypeStoreEntry), {JsonWireTypeDecoder1_Acc2, WireTypeStore1_Acc2} end, {JsonWireTypeDecoder1, WireTypeStore1}, @@ -280,23 +278,23 @@ decode_scalar_wire_type(JsonWireTypeDecoder1 = #argo_json_wire_type_decoder{}, J decode_field_wire_type(JsonWireTypeDecoder1 = #argo_json_wire_type_decoder{}, JsonValue) -> JsonObject = argo_json:as_object(JsonValue), Name = argo_json:as_string(argo_json:object_get(<<"name">>, JsonObject)), - JsonOf = argo_json:object_get(<<"type">>, JsonObject), + JsonOf = argo_json:object_get(<<"of">>, JsonObject), {JsonWireTypeDecoder2, Of} = decode_wire_type(JsonWireTypeDecoder1, JsonOf), Omittable = argo_json:as_boolean(argo_json:object_get(<<"omittable">>, JsonObject)), - ok = check_for_unknown_keys(JsonObject, #{<<"name">> => [], <<"type">> => [], <<"omittable">> => []}), + ok = check_for_unknown_keys(JsonObject, #{<<"name">> => [], <<"of">> => [], <<"omittable">> => []}), FieldWireType = argo_field_wire_type:new(Name, Of, Omittable), {JsonWireTypeDecoder2, FieldWireType}. %% @private --spec decode_wire_type_store_entry(JsonWireTypeDecoder, JsonValue) -> {JsonWireTypeDecoder, TypeName, WireType} when +-spec decode_wire_type_store_entry(JsonWireTypeDecoder, JsonValue) -> {JsonWireTypeDecoder, WireTypeStoreEntry} when JsonWireTypeDecoder :: t(), JsonValue :: argo_json:json_value(), - TypeName :: argo_types:name(), - WireType :: argo_wire_type:t(). + WireTypeStoreEntry :: argo_wire_type_store_entry:t(). decode_wire_type_store_entry(JsonWireTypeDecoder1 = #argo_json_wire_type_decoder{}, JsonValue) -> JsonObject = argo_json:as_object(JsonValue), TypeName = argo_json:as_string(argo_json:object_get(<<"name">>, JsonObject)), JsonWireType = argo_json:object_get(<<"type">>, JsonObject), {JsonWireTypeDecoder2, WireType} = decode_wire_type(JsonWireTypeDecoder1, JsonWireType), ok = check_for_unknown_keys(JsonObject, #{<<"name">> => [], <<"type">> => []}), - {JsonWireTypeDecoder2, TypeName, WireType}. + WireTypeStoreEntry = argo_wire_type_store_entry:new(TypeName, WireType), + {JsonWireTypeDecoder2, WireTypeStoreEntry}. 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 600e628..2ddc73e 100644 --- a/apps/argo/src/wire/argo_json_wire_type_encoder.erl +++ b/apps/argo/src/wire/argo_json_wire_type_encoder.erl @@ -75,7 +75,12 @@ encode_wire_type(JsonWireTypeEncoder1 = #argo_json_wire_type_encoder{}, WireType encode_wire_type_store(JsonWireTypeEncoder1 = #argo_json_wire_type_encoder{}, WireTypeStore = #argo_wire_type_store{}) -> Types = WireTypeStore#argo_wire_type_store.types, {JsonWireTypeEncoder2, JsonTypes1} = argo_index_map:foldl( - fun(_Index, TypeName, WireType, {JsonWireTypeEncoder1_Acc1, JsonTypesAcc1}) -> + fun( + _Index, + TypeName, + #argo_wire_type_store_entry{name = TypeName, type = WireType}, + {JsonWireTypeEncoder1_Acc1, JsonTypesAcc1} + ) -> {JsonWireTypeEncoder1_Acc2, JsonType} = encode_wire_type(JsonWireTypeEncoder1_Acc1, WireType), JsonTypesAcc2 = [{[{<<"name">>, TypeName}, {<<"type">>, JsonType}]} | JsonTypesAcc1], {JsonWireTypeEncoder1_Acc2, JsonTypesAcc2} @@ -150,10 +155,10 @@ encode_record_wire_type(JsonWireTypeEncoder1 = #argo_json_wire_type_encoder{}, # -spec encode_field_wire_type(JsonWireTypeEncoder, FieldWireType) -> {JsonWireTypeEncoder, JsonValue} when JsonWireTypeEncoder :: t(), FieldWireType :: argo_field_wire_type:t(), JsonValue :: argo_json:json_value(). encode_field_wire_type(JsonWireTypeEncoder1 = #argo_json_wire_type_encoder{}, #argo_field_wire_type{ - name = Name, type = Type, omittable = Omittable + name = Name, 'of' = Of, omittable = Omittable }) -> - {JsonWireTypeEncoder2, JsonType} = encode_wire_type(JsonWireTypeEncoder1, Type), - {JsonWireTypeEncoder2, {[{<<"name">>, Name}, {<<"type">>, JsonType}, {<<"omittable">>, Omittable}]}}. + {JsonWireTypeEncoder2, JsonOf} = encode_wire_type(JsonWireTypeEncoder1, Of), + {JsonWireTypeEncoder2, {[{<<"name">>, Name}, {<<"of">>, JsonOf}, {<<"omittable">>, Omittable}]}}. %% @private -spec encode_desc_wire_type(JsonWireTypeEncoder) -> {JsonWireTypeEncoder, JsonValue} when @@ -179,7 +184,7 @@ encode_error_wire_type(JsonWireTypeEncoder1 = #argo_json_wire_type_encoder{stric {[{<<"name">>, <<"column">>}, {<<"type">>, JsonVarintWireType}, {<<"omittable">>, false}]} ]} ]}, - JsonLocationWireType = + JsonLocationsWireType = {[ {<<"type">>, <<"ARRAY">>}, {<<"of">>, JsonLocationRecordWireType} @@ -189,7 +194,7 @@ encode_error_wire_type(JsonWireTypeEncoder1 = #argo_json_wire_type_encoder{stric {<<"type">>, <<"RECORD">>}, {<<"fields">>, [ {[{<<"name">>, <<"message">>}, {<<"type">>, JsonStringWireType}, {<<"omittable">>, false}]}, - {[{<<"name">>, <<"location">>}, {<<"type">>, JsonLocationWireType}, {<<"omittable">>, true}]}, + {[{<<"name">>, <<"locations">>}, {<<"type">>, JsonLocationsWireType}, {<<"omittable">>, true}]}, {[{<<"name">>, <<"path">>}, {<<"type">>, JsonPathWireType}, {<<"omittable">>, true}]}, {[{<<"name">>, <<"extensions">>}, {<<"type">>, JsonExtensionsWireType}, {<<"omittable">>, true}]} ]} diff --git a/apps/argo/src/wire/argo_wire_type.erl b/apps/argo/src/wire/argo_wire_type.erl index 78a4c6d..095617e 100644 --- a/apps/argo/src/wire/argo_wire_type.erl +++ b/apps/argo/src/wire/argo_wire_type.erl @@ -17,16 +17,20 @@ -compile(warn_missing_spec_all). -oncall("whatsapp_clr"). +-behaviour(argo_debug_type). + -include_lib("argo/include/argo_header.hrl"). -include_lib("argo/include/argo_value.hrl"). -include_lib("argo/include/argo_wire_type.hrl"). +%% argo_debug_type callbacks +-export([ + display/3, + format/2 +]). + %% Codec API -export([ - display/1, - display/2, - format/1, - format_with_lines/1, from_json/1, from_reader/1, to_json/1, @@ -60,7 +64,8 @@ is_nullable/1, is_path/1, is_record/1, - is_scalar/1 + is_scalar/1, + xform/3 ]). %% Types @@ -77,40 +82,47 @@ -type t() :: #argo_wire_type{}. +-type xform_action() :: cont | skip. +-type xform_result(TypeOut, AccOut) :: xform_action() | {xform_action(), AccOut} | {xform_action(), TypeOut, AccOut}. +-type xform_func(Type, Acc) :: xform_func(Type, Acc, Type, Acc). +-type xform_func(TypeIn, AccIn, TypeOut, AccOut) :: fun((TypeIn, AccIn) -> xform_result(TypeOut, AccOut)). + -export_type([ inner/0, - t/0 + t/0, + xform_action/0, + xform_result/2, + xform_func/2, + xform_func/4 ]). %%%============================================================================= -%%% Codec API functions +%%% argo_debug_type callbacks %%%============================================================================= --spec display(WireType) -> ok when WireType :: t(). -display(WireType = #argo_wire_type{}) -> - display(standard_io, WireType). - --spec display(IoDevice, WireType) -> ok when IoDevice :: io:device(), WireType :: t(). -display(IoDevice, WireType = #argo_wire_type{}) when not is_list(IoDevice) -> - Printer1 = argo_wire_type_printer:new_io_device(IoDevice), +-spec display(IoDevice, WireType, Options) -> ok when + IoDevice :: io:device(), WireType :: t(), Options :: argo_wire_type_printer:options(). +display(IoDevice, WireType = #argo_wire_type{}, Options) when not is_list(IoDevice) andalso is_map(Options) -> + Printer1 = argo_wire_type_printer:new_io_device(IoDevice, Options), Printer2 = argo_wire_type_printer:print_wire_type(Printer1, WireType), case argo_wire_type_printer:finalize(Printer2) of ok -> ok end. --spec format(WireType) -> Output when WireType :: t(), Output :: iolist(). -format(WireType = #argo_wire_type{}) -> - Printer1 = argo_wire_type_printer:new_string(), +-spec format(WireType, Options) -> Output when + WireType :: t(), Options :: argo_wire_type_printer:options(), Output :: unicode:unicode_binary(). +format(WireType = #argo_wire_type{}, Options) when is_map(Options) -> + Printer1 = argo_wire_type_printer:new_string(Options), Printer2 = argo_wire_type_printer:print_wire_type(Printer1, WireType), case argo_wire_type_printer:finalize(Printer2) of Output when is_list(Output) -> - Output + argo_types:unicode_binary(Output) end. --spec format_with_lines(WireType) -> unicode:unicode_binary() when WireType :: t(). -format_with_lines(WireType = #argo_wire_type{}) -> - argo_types:format_with_lines(format(WireType)). +%%%============================================================================= +%%% Codec API functions +%%%============================================================================= -spec from_json(JsonValue) -> WireType when JsonValue :: argo_json:json_value(), WireType :: t(). @@ -250,6 +262,85 @@ is_record(#argo_wire_type{}) -> false. is_scalar(#argo_wire_type{inner = #argo_scalar_wire_type{}}) -> true; is_scalar(#argo_wire_type{}) -> false. +-spec xform(TypeIn, AccIn, Fun) -> {TypeOut, AccOut} when + TypeIn :: dynamic(), + AccIn :: dynamic(), + Fun :: xform_func(TypeIn, AccIn, TypeOut, AccOut), + TypeOut :: dynamic(), + AccOut :: dynamic(). +xform(T1, Acc1, Fun) when is_function(Fun, 2) -> + case xform_normalize(T1, Acc1, Fun(T1, Acc1)) of + {cont, T2, Acc2} -> + case T2 of + #argo_array_wire_type{'of' = WireType1} -> + {WireType2, Acc3} = xform(WireType1, Acc2, Fun), + T3 = T2#argo_array_wire_type{'of' = WireType2}, + {T3, Acc3}; + #argo_block_wire_type{'of' = ScalarWireType1} -> + {ScalarWireType2, Acc3} = xform(ScalarWireType1, Acc2, Fun), + T3 = T2#argo_block_wire_type{'of' = ScalarWireType2}, + {T3, Acc3}; + #argo_desc_wire_type{} -> + {T2, Acc2}; + #argo_error_wire_type{} -> + {T2, Acc2}; + #argo_extensions_wire_type{} -> + {T2, Acc2}; + #argo_field_wire_type{'of' = WireType1} -> + {WireType2, Acc3} = xform(WireType1, Acc2, Fun), + T3 = T2#argo_field_wire_type{'of' = WireType2}, + {T3, Acc3}; + #argo_fixed_wire_type{} -> + {T2, Acc2}; + #argo_nullable_wire_type{'of' = WireType1} -> + {WireType2, Acc3} = xform(WireType1, Acc2, Fun), + T3 = T2#argo_nullable_wire_type{'of' = WireType2}, + {T3, Acc3}; + #argo_path_wire_type{} -> + {T2, Acc2}; + #argo_record_wire_type{fields = Fields1} -> + {Fields2, Acc3} = argo_index_map:foldl( + fun(_Index, FieldName, FieldWireType1, {Fields1_Acc1, Acc2_Acc1}) -> + {FieldWireType2, Acc2_Acc2} = xform(FieldWireType1, Acc2_Acc1, Fun), + Fields1_Acc2 = argo_index_map:put(FieldName, FieldWireType2, Fields1_Acc1), + {Fields1_Acc2, Acc2_Acc2} + end, + {argo_index_map:new(), Acc2}, + Fields1 + ), + T3 = T2#argo_record_wire_type{fields = Fields2}, + {T3, Acc3}; + #argo_scalar_wire_type{inner = FixedWireType1 = #argo_fixed_wire_type{}} -> + {FixedWireType2, Acc3} = xform(FixedWireType1, Acc2, Fun), + T3 = T2#argo_scalar_wire_type{inner = FixedWireType2}, + {T3, Acc3}; + #argo_scalar_wire_type{} -> + {T2, Acc2}; + #argo_wire_type_store{types = Types1} -> + {Types2, Acc3} = argo_index_map:foldl( + fun(_Index, TypeName, WireTypeStoreEntry1, {Types1_Acc1, Acc2_Acc1}) -> + {WireTypeStoreEntry2, Acc2_Acc2} = xform(WireTypeStoreEntry1, Acc2_Acc1, Fun), + Types1_Acc2 = argo_index_map:put(TypeName, WireTypeStoreEntry2, Types1_Acc1), + {Types1_Acc2, Acc2_Acc2} + end, + {argo_index_map:new(), Acc2}, + Types1 + ), + T3 = T2#argo_wire_type_store{types = Types2}, + {T3, Acc3}; + #argo_wire_type_store_entry{type = WireType1} -> + {WireType2, Acc3} = xform(WireType1, Acc2, Fun), + T3 = T2#argo_wire_type_store_entry{type = WireType2}, + {T3, Acc3}; + #argo_wire_type{inner = Inner1} -> + {Inner2, Acc3} = xform(Inner1, Acc2, Fun), + T3 = T2#argo_wire_type{inner = Inner2}, + {T3, Acc3} + end; + {skip, T2, Acc2} -> + {T2, Acc2} + end. + %%%----------------------------------------------------------------------------- %%% Internal functions %%%----------------------------------------------------------------------------- @@ -283,10 +374,10 @@ fold_path_values(WireType = #argo_wire_type{}, Function, Acc1, PathValue1 = #arg Acc1; #argo_record_wire_type{fields = Fields} -> Acc2 = argo_index_map:foldl( - fun(_Index, FieldName, #argo_field_wire_type{type = FieldType}, Acc1_Acc1) -> + fun(_Index, FieldName, #argo_field_wire_type{'of' = FieldOf}, Acc1_Acc1) -> PathValue2 = argo_path_value:push_field_name(PathValue1, FieldName), Acc1_Acc2 = Function(PathValue2, Acc1_Acc1), - Acc1_Acc3 = fold_path_values(FieldType, Function, Acc1_Acc2, PathValue2), + Acc1_Acc3 = fold_path_values(FieldOf, Function, Acc1_Acc2, PathValue2), Acc1_Acc3 end, Acc1, @@ -296,3 +387,18 @@ fold_path_values(WireType = #argo_wire_type{}, Function, Acc1, PathValue1 = #arg #argo_scalar_wire_type{} -> Acc1 end. + +%% @private +-spec xform_normalize(TypeIn, AccIn, Result) -> {Action, TypeOut, AccOut} when + TypeIn :: dynamic(), + AccIn :: dynamic(), + Result :: xform_result(TypeOut, AccOut), + Action :: xform_action(), + TypeOut :: dynamic(), + AccOut :: dynamic(). +xform_normalize(TypeIn, AccIn, Action) when Action =:= 'cont' orelse Action =:= 'skip' -> + {Action, TypeIn, AccIn}; +xform_normalize(TypeIn, _AccIn, {Action, AccOut}) when Action =:= 'cont' orelse Action =:= 'skip' -> + {Action, TypeIn, AccOut}; +xform_normalize(_TypeIn, _AccIn, {Action, TypeOut, AccOut}) when Action =:= 'cont' orelse Action =:= 'skip' -> + {Action, TypeOut, AccOut}. diff --git a/apps/argo/src/wire/argo_wire_type_decoder.erl b/apps/argo/src/wire/argo_wire_type_decoder.erl index f9342ab..20bb479 100644 --- a/apps/argo/src/wire/argo_wire_type_decoder.erl +++ b/apps/argo/src/wire/argo_wire_type_decoder.erl @@ -173,5 +173,6 @@ decode_wire_type_store_types( {MessageDecoder2, TypeName} = argo_message_decoder:decode_block_string(MessageDecoder1), WireTypeDecoder2 = WireTypeDecoder1#argo_wire_type_decoder{message = MessageDecoder2}, {WireTypeDecoder3, WireType} = decode_wire_type(WireTypeDecoder2), - WireTypeStore2 = argo_wire_type_store:insert(WireTypeStore1, TypeName, WireType), + WireTypeStoreEntry = argo_wire_type_store_entry:new(TypeName, WireType), + WireTypeStore2 = argo_wire_type_store:insert(WireTypeStore1, WireTypeStoreEntry), decode_wire_type_store_types(WireTypeDecoder3, Length - 1, WireTypeStore2). diff --git a/apps/argo/src/wire/argo_wire_type_encoder.erl b/apps/argo/src/wire/argo_wire_type_encoder.erl index c91eddb..7a666d1 100644 --- a/apps/argo/src/wire/argo_wire_type_encoder.erl +++ b/apps/argo/src/wire/argo_wire_type_encoder.erl @@ -73,7 +73,12 @@ encode_wire_type_store( MessageEncoder3 = argo_message_encoder:write_core_length(MessageEncoder2, argo_index_map:size(Types)), WireTypeEncoder2 = WireTypeEncoder1#argo_wire_type_encoder{message = MessageEncoder3}, WireTypeEncoder3 = argo_index_map:foldl( - fun(_Index, TypeName, WireType, WireTypeEncoderAcc1 = #argo_wire_type_encoder{message = MessageEncoderAcc1}) -> + fun( + _Index, + TypeName, + #argo_wire_type_store_entry{name = TypeName, type = WireType}, + WireTypeEncoderAcc1 = #argo_wire_type_encoder{message = MessageEncoderAcc1} + ) -> MessageEncoderAcc2 = argo_message_encoder:encode_block_string(MessageEncoderAcc1, TypeName), WireTypeEncoderAcc2 = WireTypeEncoderAcc1#argo_wire_type_encoder{message = MessageEncoderAcc2}, WireTypeEncoderAcc3 = encode_wire_type(WireTypeEncoderAcc2, WireType), @@ -183,7 +188,7 @@ encode_field_wire_type( WireTypeEncoder2 = WireTypeEncoder1#argo_wire_type_encoder{message = MessageEncoder2}, WireTypeEncoder3 = #argo_wire_type_encoder{message = MessageEncoder3} = encode_wire_type( - WireTypeEncoder2, FieldWireType#argo_field_wire_type.type + WireTypeEncoder2, FieldWireType#argo_field_wire_type.'of' ), MessageEncoder4 = argo_message_encoder:encode_block_boolean( MessageEncoder3, FieldWireType#argo_field_wire_type.omittable diff --git a/apps/argo/src/wire/argo_wire_type_printer.erl b/apps/argo/src/wire/argo_wire_type_printer.erl index 5cb6e11..afc9ee7 100644 --- a/apps/argo/src/wire/argo_wire_type_printer.erl +++ b/apps/argo/src/wire/argo_wire_type_printer.erl @@ -17,15 +17,12 @@ -compile(warn_missing_spec_all). -oncall("whatsapp_clr"). --include_lib("argo/include/argo_header.hrl"). --include_lib("argo/include/argo_label.hrl"). --include_lib("argo/include/argo_message.hrl"). -include_lib("argo/include/argo_wire_type.hrl"). %% API -export([ - new_io_device/1, - new_string/0, + new_io_device/2, + new_string/1, finalize/1, print_wire_type/2, print_wire_type_store/2 @@ -34,13 +31,18 @@ %% Records -record(argo_wire_type_printer, { depth = 0 :: non_neg_integer(), - output = [] :: iolist() | io:device() + output = [] :: iolist() | io:device(), + strict = false :: boolean() }). %% Types +-type options() :: #{ + strict => boolean() +}. -type t() :: #argo_wire_type_printer{}. -export_type([ + options/0, t/0 ]). @@ -48,13 +50,15 @@ %%% API functions %%%============================================================================= --spec new_io_device(IoDevice) -> Printer when IoDevice :: io:device(), Printer :: t(). -new_io_device(IoDevice) when not is_list(IoDevice) -> - #argo_wire_type_printer{depth = 0, output = IoDevice}. +-spec new_io_device(IoDevice, Options) -> Printer when IoDevice :: io:device(), Options :: options(), Printer :: t(). +new_io_device(IoDevice, Options) when is_map(Options) -> + Strict = maps:get(strict, Options, false), + #argo_wire_type_printer{depth = 0, output = IoDevice, strict = Strict}. --spec new_string() -> Printer when Printer :: t(). -new_string() -> - #argo_wire_type_printer{depth = 0, output = []}. +-spec new_string(Options) -> Printer when Options :: options(), Printer :: t(). +new_string(Options) when is_map(Options) -> + Strict = maps:get(strict, Options, false), + #argo_wire_type_printer{depth = 0, output = [], strict = Strict}. -spec finalize(Printer) -> ok | iolist() when Printer :: t(). finalize(#argo_wire_type_printer{output = Output}) when is_list(Output) -> @@ -64,7 +68,7 @@ finalize(Printer = #argo_wire_type_printer{}) -> ok. -spec print_wire_type(Printer, WireType) -> Printer when Printer :: t(), WireType :: argo_wire_type:t(). -print_wire_type(Printer1 = #argo_wire_type_printer{}, WireType = #argo_wire_type{}) -> +print_wire_type(Printer1 = #argo_wire_type_printer{strict = Strict}, WireType = #argo_wire_type{}) -> case WireType#argo_wire_type.inner of ScalarWireType = #argo_scalar_wire_type{} -> print_scalar_wire_type(Printer1, ScalarWireType); @@ -91,12 +95,24 @@ print_wire_type(Printer1 = #argo_wire_type_printer{}, WireType = #argo_wire_type #argo_desc_wire_type{} -> Printer2 = write(Printer1, "DESC", []), Printer2; - #argo_error_wire_type{} -> - Printer2 = write(Printer1, "ERROR", []), - Printer2; - #argo_extensions_wire_type{} -> - Printer2 = write(Printer1, "EXTENSIONS", []), - Printer2; + ErrorWireType = #argo_error_wire_type{} -> + case Strict of + false -> + Printer2 = write(Printer1, "ERROR", []), + Printer2; + true -> + ExpandedErrorWireType = argo_error_wire_type:expand_wire_type(ErrorWireType), + print_wire_type(Printer1, ExpandedErrorWireType) + end; + ExtensionsWireType = #argo_extensions_wire_type{} -> + case Strict of + false -> + Printer2 = write(Printer1, "EXTENSIONS", []), + Printer2; + true -> + ExpandedExtensionsWireType = argo_extensions_wire_type:expand_wire_type(ExtensionsWireType), + print_wire_type(Printer1, ExpandedExtensionsWireType) + end; #argo_path_wire_type{} -> Printer2 = write(Printer1, "PATH", []), Printer2 @@ -108,8 +124,8 @@ print_wire_type_store(Printer1 = #argo_wire_type_printer{}, WireTypeStore = #arg Printer2 = write(Printer1, "{~n", []), Types = WireTypeStore#argo_wire_type_store.types, Printer3 = argo_index_map:foldl( - fun(_Index, TypeName, WireType, PrinterAcc) -> - print_wire_type_store_type(PrinterAcc, TypeName, WireType) + fun(_Index, _TypeName, WireTypeStoreEntry, PrinterAcc) -> + print_wire_type_store_entry(PrinterAcc, WireTypeStoreEntry) end, Printer2, Types @@ -119,9 +135,11 @@ print_wire_type_store(Printer1 = #argo_wire_type_printer{}, WireTypeStore = #arg Printer5. %% @private --spec print_wire_type_store_type(Printer, TypeName, WireType) -> Printer when - Printer :: t(), TypeName :: argo_types:name(), WireType :: argo_wire_type:t(). -print_wire_type_store_type(Printer1 = #argo_wire_type_printer{}, TypeName, WireType = #argo_wire_type{}) -> +-spec print_wire_type_store_entry(Printer, WireTypeStoreEntry) -> Printer when + Printer :: t(), WireTypeStoreEntry :: argo_wire_type_store_entry:t(). +print_wire_type_store_entry(Printer1 = #argo_wire_type_printer{}, #argo_wire_type_store_entry{ + name = TypeName, type = WireType = #argo_wire_type{} +}) -> Printer2 = Printer1#argo_wire_type_printer{depth = Printer1#argo_wire_type_printer.depth + 1}, Printer3 = indent(Printer2), Printer4 = write(Printer3, "~ts: ", [TypeName]), @@ -143,7 +161,7 @@ print_field_wire_type(Printer1 = #argo_wire_type_printer{}, FieldWireType = #arg true -> write(Printer3, "~ts?: ", [FieldWireType#argo_field_wire_type.name]) end, - Printer5 = print_wire_type(Printer4, FieldWireType#argo_field_wire_type.type), + Printer5 = print_wire_type(Printer4, FieldWireType#argo_field_wire_type.'of'), Printer6 = write(Printer5, "~n", []), Printer7 = Printer6#argo_wire_type_printer{depth = Printer6#argo_wire_type_printer.depth - 1}, Printer7. diff --git a/apps/argo/src/wire/argo_wire_type_store.erl b/apps/argo/src/wire/argo_wire_type_store.erl index 7d93fab..8a6859c 100644 --- a/apps/argo/src/wire/argo_wire_type_store.erl +++ b/apps/argo/src/wire/argo_wire_type_store.erl @@ -17,15 +17,19 @@ -compile(warn_missing_spec_all). -oncall("whatsapp_clr"). +-behaviour(argo_debug_type). + -include_lib("argo/include/argo_header.hrl"). -include_lib("argo/include/argo_wire_type.hrl"). +%% argo_debug_type callbacks +-export([ + display/3, + format/2 +]). + %% Codec API -export([ - display/1, - display/2, - format/1, - format_with_lines/1, from_json/1, from_reader/1, to_json/1, @@ -41,6 +45,8 @@ %% Instance API -export([ find/2, + find_entry/2, + insert/2, insert/3 ]). @@ -52,34 +58,34 @@ ]). %%%============================================================================= -%%% Codec API functions +%%% argo_debug_type callbacks %%%============================================================================= --spec display(WireTypeStore) -> ok when WireTypeStore :: t(). -display(WireTypeStore = #argo_wire_type_store{}) -> - display(standard_io, WireTypeStore). - --spec display(IoDevice, WireTypeStore) -> ok when IoDevice :: io:device(), WireTypeStore :: t(). -display(IoDevice, WireTypeStore = #argo_wire_type_store{}) when not is_list(IoDevice) -> - Printer1 = argo_wire_type_printer:new_io_device(IoDevice), +-spec display(IoDevice, WireTypeStore, Options) -> ok when + IoDevice :: io:device(), WireTypeStore :: t(), Options :: argo_wire_type_printer:options(). +display(IoDevice, WireTypeStore = #argo_wire_type_store{}, Options) when + not is_list(IoDevice) andalso is_map(Options) +-> + Printer1 = argo_wire_type_printer:new_io_device(IoDevice, Options), Printer2 = argo_wire_type_printer:print_wire_type_store(Printer1, WireTypeStore), case argo_wire_type_printer:finalize(Printer2) of ok -> ok end. --spec format(WireTypeStore) -> Output when WireTypeStore :: t(), Output :: iolist(). -format(WireTypeStore = #argo_wire_type_store{}) -> - Printer1 = argo_wire_type_printer:new_string(), +-spec format(WireTypeStore, Options) -> Output when + WireTypeStore :: t(), Options :: argo_wire_type_printer:options(), Output :: unicode:unicode_binary(). +format(WireTypeStore = #argo_wire_type_store{}, Options) when is_map(Options) -> + Printer1 = argo_wire_type_printer:new_string(Options), Printer2 = argo_wire_type_printer:print_wire_type_store(Printer1, WireTypeStore), case argo_wire_type_printer:finalize(Printer2) of Output when is_list(Output) -> - Output + argo_types:unicode_binary(Output) end. --spec format_with_lines(WireTypeStore) -> unicode:unicode_binary() when WireTypeStore :: t(). -format_with_lines(WireTypeStore = #argo_wire_type_store{}) -> - argo_types:format_with_lines(format(WireTypeStore)). +%%%============================================================================= +%%% Codec API functions +%%%============================================================================= -spec from_json(JsonValue) -> WireTypeStore when JsonValue :: argo_json:json_value(), WireTypeStore :: t(). @@ -132,14 +138,35 @@ new() -> -spec find(WireTypeStore, TypeName) -> {ok, WireType} | error when WireTypeStore :: t(), TypeName :: argo_types:name(), WireType :: argo_wire_type:t(). -find(#argo_wire_type_store{types = Types}, TypeName) when is_binary(TypeName) -> +find(WireTypeStore = #argo_wire_type_store{}, TypeName) when is_binary(TypeName) -> + case find_entry(WireTypeStore, TypeName) of + {ok, #argo_wire_type_store_entry{type = WireType}} -> + {ok, WireType}; + error -> + error + end. + +-spec find_entry(WireTypeStore, TypeName) -> {ok, WireTypeStoreEntry} | error when + WireTypeStore :: t(), TypeName :: argo_types:name(), WireTypeStoreEntry :: argo_wire_type_store_entry:t(). +find_entry(#argo_wire_type_store{types = Types}, TypeName) when is_binary(TypeName) -> argo_index_map:find(TypeName, Types). --spec insert(WireTypeStore, TypeName, WireType) -> WireTypeStore when - WireTypeStore :: t(), TypeName :: argo_types:name(), WireType :: argo_wire_type:t(). -insert(WireTypeStore1 = #argo_wire_type_store{types = Types1}, TypeName, WireType = #argo_wire_type{}) when +-spec insert(WireTypeStore, WireTypeStoreEntry) -> WireTypeStore when + WireTypeStore :: t(), WireTypeStoreEntry :: argo_wire_type_store_entry:t(). +insert( + WireTypeStore1 = #argo_wire_type_store{types = Types1}, + WireTypeStoreEntry = #argo_wire_type_store_entry{name = TypeName} +) when is_binary(TypeName) -> - Types2 = argo_index_map:put(TypeName, WireType, Types1), + Types2 = argo_index_map:put(TypeName, WireTypeStoreEntry, Types1), WireTypeStore2 = WireTypeStore1#argo_wire_type_store{types = Types2}, WireTypeStore2. + +-spec insert(WireTypeStore, TypeName, WireType) -> WireTypeStore when + WireTypeStore :: t(), TypeName :: argo_types:name(), WireType :: argo_wire_type:t(). +insert(WireTypeStore = #argo_wire_type_store{}, TypeName, WireType = #argo_wire_type{}) when + is_binary(TypeName) +-> + WireTypeStoreEntry = argo_wire_type_store_entry:new(TypeName, WireType), + insert(WireTypeStore, WireTypeStoreEntry). diff --git a/apps/argo/src/wire/argo_wire_type_store_entry.erl b/apps/argo/src/wire/argo_wire_type_store_entry.erl new file mode 100644 index 0000000..c259540 --- /dev/null +++ b/apps/argo/src/wire/argo_wire_type_store_entry.erl @@ -0,0 +1,41 @@ +%%%----------------------------------------------------------------------------- +%%% Copyright (c) Meta Platforms, Inc. and affiliates. +%%% Copyright (c) WhatsApp LLC +%%% +%%% This source code is licensed under the MIT license found in the +%%% LICENSE.md file in the root directory of this source tree. +%%% +%%% @author Andrew Bennett +%%% @copyright (c) Meta Platforms, Inc. and affiliates. +%%% @doc +%%% +%%% @end +%%% Created : 21 Mar 2024 by Andrew Bennett +%%%----------------------------------------------------------------------------- +%%% % @format +-module(argo_wire_type_store_entry). +-compile(warn_missing_spec_all). +-oncall("whatsapp_clr"). + +-include_lib("argo/include/argo_wire_type.hrl"). + +%% API +-export([ + new/2 +]). + +%% Types +-type t() :: #argo_wire_type_store_entry{}. + +-export_type([ + t/0 +]). + +%%%============================================================================= +%%% API functions +%%%============================================================================= + +-spec new(Name, Type) -> WireTypeStoreEntry when + Name :: argo_types:name(), Type :: argo_wire_type:t(), WireTypeStoreEntry :: t(). +new(Name, Type = #argo_wire_type{}) when is_binary(Name) -> + #argo_wire_type_store_entry{name = Name, type = Type}. diff --git a/apps/argo_test/src/proper_argo.erl b/apps/argo_test/src/proper_argo.erl index a1564ab..6b3b5d3 100644 --- a/apps/argo_test/src/proper_argo.erl +++ b/apps/argo_test/src/proper_argo.erl @@ -343,10 +343,10 @@ field_value() -> ?LET(FieldWireType, field_wire_type(), field_value(FieldWireType)). -spec field_value(FieldWireType :: argo_field_wire_type:t()) -> proper_types:type(). -field_value(FieldWireType = #argo_field_wire_type{type = Type, omittable = true}) -> +field_value(FieldWireType = #argo_field_wire_type{'of' = Of, omittable = true}) -> ?LET( OptionValue, - option(value(Type)), + option(value(Of)), case OptionValue of none -> argo_field_value:optional(FieldWireType, OptionValue); @@ -354,8 +354,8 @@ field_value(FieldWireType = #argo_field_wire_type{type = Type, omittable = true} argo_field_value:optional(FieldWireType, OptionValue) end ); -field_value(FieldWireType = #argo_field_wire_type{type = Type}) -> - ?LET(Value, value(Type), argo_field_value:required(FieldWireType, Value)). +field_value(FieldWireType = #argo_field_wire_type{'of' = Of}) -> + ?LET(Value, value(Of), argo_field_value:required(FieldWireType, Value)). -spec nullable_value() -> proper_types:type(). nullable_value() -> @@ -368,7 +368,12 @@ nullable_value(NullableWireType = #argo_nullable_wire_type{}) -> ?LET( Value, value(NullableWireType#argo_nullable_wire_type.'of'), - argo_nullable_value:non_null(NullableWireType, Value) + case Value of + #argo_value{inner = #argo_desc_value{inner = null}} -> + argo_nullable_value:null(NullableWireType); + #argo_value{} -> + argo_nullable_value:non_null(NullableWireType, Value) + end ) ]). @@ -557,7 +562,7 @@ field_wire_type(Name, Of = #argo_wire_type{}) when is_binary(Name) -> -spec nullable_wire_type() -> proper_types:type(). nullable_wire_type() -> - ?LET(Of, ?LAZY(wire_type()), argo_nullable_wire_type:new(Of)). + ?LET(Of, ?LAZY(wire_type()), argo_nullable_wire_type:new(unwrap_nullable_wire_type(Of))). -spec path_wire_type() -> proper_types:type(). path_wire_type() -> @@ -646,3 +651,14 @@ wire_type() -> #argo_scalar_wire_type{} -> argo_wire_type:scalar(InnerWireType) end ). + +%%%----------------------------------------------------------------------------- +%%% Internal functions +%%%----------------------------------------------------------------------------- + +%% @private +-spec unwrap_nullable_wire_type(WireType) -> WireType when WireType :: argo_wire_type:t(). +unwrap_nullable_wire_type(#argo_wire_type{inner = #argo_nullable_wire_type{'of' = Of}}) -> + unwrap_nullable_wire_type(Of); +unwrap_nullable_wire_type(WireType = #argo_wire_type{}) -> + WireType. diff --git a/apps/argo_test/src/proper_argo_typer.erl b/apps/argo_test/src/proper_argo_typer.erl index 1f5e6d7..12c79db 100644 --- a/apps/argo_test/src/proper_argo_typer.erl +++ b/apps/argo_test/src/proper_argo_typer.erl @@ -179,7 +179,7 @@ value( OptionOperationName, WireType = #argo_wire_type{inner = RecordWireType = #argo_record_wire_type{}} ) when ?is_option_binary(OptionOperationName) -> - {ok, #argo_field_wire_type{type = DataWireType}} = argo_record_wire_type:find(RecordWireType, <<"data">>), + {ok, #argo_field_wire_type{'of' = DataWireType}} = argo_record_wire_type:find(RecordWireType, <<"data">>), ?LET( Value, proper_argo:maybe_root_wire_type(DataWireType, proper_argo:value(WireType)), diff --git a/apps/argo_test/src/property_test/argo_graphql_executable_document_prop.erl b/apps/argo_test/src/property_test/argo_graphql_executable_document_prop.erl index 85a003d..70e8a5c 100644 --- a/apps/argo_test/src/property_test/argo_graphql_executable_document_prop.erl +++ b/apps/argo_test/src/property_test/argo_graphql_executable_document_prop.erl @@ -66,8 +66,8 @@ prop_roundtrip_executable_document(_Config) -> [ Expected, Actual, - argo_graphql:format_with_lines(Expected), - argo_graphql:format_with_lines(Actual) + argo:format_with_lines(Expected), + argo:format_with_lines(Actual) ] ) end, diff --git a/apps/argo_test/src/property_test/argo_graphql_language_prop.erl b/apps/argo_test/src/property_test/argo_graphql_language_prop.erl index b27f14b..bce5aca 100644 --- a/apps/argo_test/src/property_test/argo_graphql_language_prop.erl +++ b/apps/argo_test/src/property_test/argo_graphql_language_prop.erl @@ -70,8 +70,8 @@ prop_roundtrip_formatter_and_parser(_Config) -> [ Document, strip_location(ParsedDocument), - argo_graphql:format_with_lines(Document), - argo_graphql:format_with_lines(ParsedDocument) + argo:format_with_lines(Document), + argo:format_with_lines(ParsedDocument) ] ) end, @@ -84,7 +84,7 @@ prop_roundtrip_formatter_and_parser(_Config) -> "FAILURE: Parser Error due to ~0tp~n" "Expected (Record):~n~0tp~n" "Expected (String):~n~ts~n", - [ParserError, Document, argo_graphql:format_with_lines(Document)] + [ParserError, Document, argo:format_with_lines(Document)] ) end, false @@ -97,7 +97,7 @@ prop_roundtrip_formatter_and_parser(_Config) -> "FAILURE: Lexer Error due to ~0tp~n" "Expected (Record):~n~0tp~n" "Expected (String):~n~ts~n", - [LexerError, Document, argo_graphql:format_with_lines(Document)] + [LexerError, Document, argo:format_with_lines(Document)] ) end, false diff --git a/apps/argo_test/src/property_test/argo_graphql_service_document_prop.erl b/apps/argo_test/src/property_test/argo_graphql_service_document_prop.erl index 9a6ce17..ef1d8d7 100644 --- a/apps/argo_test/src/property_test/argo_graphql_service_document_prop.erl +++ b/apps/argo_test/src/property_test/argo_graphql_service_document_prop.erl @@ -66,8 +66,8 @@ prop_roundtrip_service_document(_Config) -> [ Expected, Actual, - argo_graphql:format_with_lines(Expected), - argo_graphql:format_with_lines(Actual) + argo:format_with_lines(Expected), + argo:format_with_lines(Actual) ] ) end, diff --git a/apps/argo_test/src/property_test/argo_typer_prop.erl b/apps/argo_test/src/property_test/argo_typer_prop.erl index c403e45..e0c12cd 100644 --- a/apps/argo_test/src/property_test/argo_typer_prop.erl +++ b/apps/argo_test/src/property_test/argo_typer_prop.erl @@ -87,10 +87,10 @@ prop_roundtrip(_Config) -> WireType, Value, Header, - argo_graphql:format_with_lines(ServiceDocument), - argo_graphql:format_with_lines(ExecutableDocument), - argo_wire_type:format(WireType), - argo_value:format(Value) + argo:format_with_lines(ServiceDocument), + argo:format_with_lines(ExecutableDocument), + argo:format(WireType), + argo:format(Value) ] ) end, diff --git a/apps/argo_test/src/property_test/argo_value_prop.erl b/apps/argo_test/src/property_test/argo_value_prop.erl index a647b66..bd1cc37 100644 --- a/apps/argo_test/src/property_test/argo_value_prop.erl +++ b/apps/argo_test/src/property_test/argo_value_prop.erl @@ -98,8 +98,8 @@ prop_roundtrip_json_encoder_and_json_decoder(_Config) -> [ ExpectedValue, ActualValue, - argo_value:format_with_lines(ExpectedValue), - argo_value:format_with_lines(ActualValue), + argo:format_with_lines(ExpectedValue), + argo:format_with_lines(ActualValue), json_encode_pretty(ExpectedValue), json_encode_pretty(ActualValue) ] diff --git a/apps/argo_test/src/property_test/argo_wire_type_prop.erl b/apps/argo_test/src/property_test/argo_wire_type_prop.erl index 692b309..91c00a3 100644 --- a/apps/argo_test/src/property_test/argo_wire_type_prop.erl +++ b/apps/argo_test/src/property_test/argo_wire_type_prop.erl @@ -84,8 +84,8 @@ prop_roundtrip_json_encoder_and_json_decoder(_Config) -> [ ExpectedWireType, ActualWireType, - argo_wire_type:format_with_lines(ExpectedWireType), - argo_wire_type:format_with_lines(ActualWireType), + argo:format_with_lines(ExpectedWireType), + argo:format_with_lines(ActualWireType), json_encode_pretty(ExpectedWireType), json_encode_pretty(ActualWireType) ] diff --git a/apps/argo_test/test/argo_typer_SUITE.erl b/apps/argo_test/test/argo_typer_SUITE.erl index ed0798c..5a41b22 100644 --- a/apps/argo_test/test/argo_typer_SUITE.erl +++ b/apps/argo_test/test/argo_typer_SUITE.erl @@ -120,7 +120,7 @@ test_issue_7_incorrect_type_for_fields_in_fragment(Config) -> {_, WireType} = argo_typer:derive_wire_type( ServiceDocument, ExecutableDocument, {some, <<"IncorrectTypeForFieldsInFragment">>} ), - Actual = erlang:iolist_to_binary(argo_wire_type:format(WireType)), + Actual = erlang:iolist_to_binary(argo:format(WireType)), Expected = << "{\n" @@ -144,7 +144,7 @@ test_issue_8_field_omittable(Config) -> ServiceDocument = test_server:lookup_config(service_document, Config), ExecutableDocument = test_server:lookup_config(executable_document, Config), {_, WireType} = argo_typer:derive_wire_type(ServiceDocument, ExecutableDocument, {some, <<"FieldOmittable">>}), - Actual = erlang:iolist_to_binary(argo_wire_type:format(WireType)), + Actual = erlang:iolist_to_binary(argo:format(WireType)), Expected = << "{\n" @@ -173,7 +173,7 @@ test_issue_8_fragment_spread_omittable(Config) -> {_, WireType} = argo_typer:derive_wire_type( ServiceDocument, ExecutableDocument, {some, <<"FragmentSpreadOmittable">>} ), - Actual = erlang:iolist_to_binary(argo_wire_type:format(WireType)), + Actual = erlang:iolist_to_binary(argo:format(WireType)), Expected = << "{\n" @@ -221,7 +221,7 @@ test_issue_8_inline_fragment_omittable(Config) -> {_, WireType} = argo_typer:derive_wire_type( ServiceDocument, ExecutableDocument, {some, <<"InlineFragmentOmittable">>} ), - Actual = erlang:iolist_to_binary(argo_wire_type:format(WireType)), + Actual = erlang:iolist_to_binary(argo:format(WireType)), Expected = << "{\n" 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 503fcea..2112337 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 @@ -102,3 +102,8 @@ fragment RootFragmentUnreachable1 on Child { fragment RootFragmentUnreachable2 on Child { ...RootFragmentUnreachable } + +query SimpleQuery { + x + y +} 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 7a08065..8bda1fd 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 @@ -29,4 +29,6 @@ type Object implements Child { type Query { hero: Character root: Child + x: Int! + y: Int }